Staging
v0.8.1
v0.8.1
https://repo1.maven.org/maven2/org/pulloid/pulloid
ElementContext.java
package org.pulloid;
import java.io.IOException;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
/**
* Represents the context of a specific XML element. This class is one of the few central classes of Pulloid,
* it allows to define a {@linkplain CursorDef cursor definition} (that means that the parser will create a new instance of class T
* every time it encounters such an XML element), it also allows to select children elements and assign them to fields in class T, and
* the same cna be done for XML attributes.
*
* @author Romain Laboisse labrom@gmail.com
*
* @param <T> The type of objects that Pulloid will create. This class must be written by developers.
*/
public class ElementContext<T> extends NodeContext<T> {
private int depth;
private CursorDef<T> cursorDef;
private ElementContext<T> lastChild;
private ElementContext<T> firstChild;
private AttributeContext<T> attrChain;
protected ElementContext(ElementContext<T> parent, String name) {
super(parent, name);
}
protected void setCursorDef(CursorDef<T> def) {
this.cursorDef = def;
}
protected CursorDef<T> getCursorDef() {
return this.cursorDef;
}
protected void setDepth(int depth) {
this.depth = depth;
}
protected int getDepth() {
return this.depth;
}
public ElementContext<T> selectElement(String name) {
ElementContext<T> ctx = createChildElementContext(name);
selectElement(ctx);
return ctx;
}
protected void selectElement(ElementContext<T> selected) {
selected.depth = this.depth + 1;
if(firstChild == null)
firstChild = selected;
else
lastChild.nextSibling = selected;
lastChild = selected;
}
protected ElementContext<T> createChildElementContext(String name) {
return new ElementContext<T>(this, name);
}
public ElementContext<T> createFieldOnAttribute(String fieldName, String attributeName) {
AttributeContext<T> ctx = new AttributeContext<T>(this, attributeName);
ctx.registerField(fieldName);
if(attrChain != null) // We don't care about the order for attributes, because we can get all of them while the parser is on the start tag
ctx.nextSibling = attrChain;
attrChain = ctx;
return this;
}
public ElementContext<T> createFieldOnAttribute(String fieldName, String attributeName, Class<?> type) {
AttributeContext<T> ctx = new AttributeContext<T>(this, attributeName);
ctx.registerField(fieldName, type);
if(attrChain != null) // We don't care about the order for attributes, because we can get all of them while the parser is on the start tag
ctx.nextSibling = attrChain;
attrChain = ctx;
return this;
}
public <TT> ElementContext<T> createFieldOnAttribute(String fieldName, String attributeName, Class<TT> type, StringTransformer<TT> tr) {
AttributeContext<T> ctx = new AttributeContext<T>(this, attributeName);
ctx.registerField(fieldName, type, tr);
if(attrChain != null) // We don't care about the order for attributes, because we can get all of them while the parser is on the start tag
ctx.nextSibling = attrChain;
attrChain = ctx;
return this;
}
public ElementContext<T> createField(String name) {
registerField(name);
return this;
}
public ElementContext<T> createField(String name, Class<?> type) {
registerField(name, type);
return this;
}
public <TT> ElementContext<T> createField(String name, Class<TT> type, StringTransformer<TT> tr) {
registerField(name, type, tr);
return this;
}
public CursorDef<T> defineCursor(Class<T> cursorType) {
CursorDef<T> def = new CursorDef<T>(cursorType, this);
setCursorDef(def);
return def;
}
public CursorDef<T> defineCursor() {
CursorDef<T> def = new CursorDef<T>(this);
setCursorDef(def);
return def;
}
@Override
CursorDef<T> findCursorDef() {
if(this.cursorDef != null)
return this.cursorDef;
return super.findCursorDef();
}
/**
* Move parser cursor to the next occurrence of this element.
* If there is no such occurrence, will try to position to the next sibling, and so on.
* @param p
* @return
* @throws XmlPullParserException
* @throws IOException
*/
ElementContext<T> position(XmlPullParser p) throws XmlPullParserException, IOException {
/*
* This context is the cursor, move forward until we encounter the next cursor tag
* There's no need to look for next sibling match
*/
if(cursorDef != null) {
try {
while(this.depth != p.getDepth() || !name.equals(p.getName()) || !ParserUtil.isStartTag(p)) {
if(ParserUtil.isEndDocument(p))
return null;
p.next();
}
} catch(XmlPullParserException e) {
return null;
}
return this;
}
/*
* Starting from here is the non-cursor case: some elements may be missing, other elements may be ignored
*/
// Go to the right depth
try {
while(this.depth != p.getDepth() || ParserUtil.isEndTag(p)) {
p.nextTag();
if(p.getName().equals(parent.name))
return null;
}
} catch(XmlPullParserException e) {
return null;
}
// Try to match self, or siblings
do {
ElementContext<T> pos = matchSelfOrSibling(p);
if(pos != null)
return pos;
} while(ParserUtil.moveToNextSibling(p)); // Skip sibling
return null;
}
/**
* Figures out if one of the next siblings of this element might
* match the parser current cursor position.
* @param p
* @return
* @throws XmlPullParserException
*/
ElementContext<T> matchSelfOrSibling(XmlPullParser p) throws XmlPullParserException {
if(p.getName().equals(name) && ParserUtil.isStartTag(p)) {
return this;
}
if(nextSibling != null)
return ((ElementContext<T>)nextSibling).matchSelfOrSibling(p);
return null;
}
/**
* Moves the parser cursor to the next occurrence of this element
* and fills the current record. If this element has children and/or siblings,
* then calls the same method on those elements. Does the same for attributes if there
* are any.
* @see #position(Puller)
*/
@Override
void hydrate(XmlPullParser p, Reflector<T> record) throws XmlPullParserException, IOException {
ElementContext<T> pos = null;
if(cursorDef == null) { // If we are in the element that created the cursor there's no need to position() as it's already been done from the cursor
pos = position(p);
if(pos == null)
return;
} else {
pos = this;
}
if(pos == this) {
if(attrChain != null)
attrChain.hydrate(p, record); // Do all attributes, no need to position, we're at the right position already
if(firstChild != null) {
firstChild.hydrate(p, record); // Send to children
} else if(setter != null) {
String strval = p.nextText(); // Move to my end tag
Object val = transformer != null ? transformer.transform(strval) : strval;
record.set(setter, val);
}
if(cursorDef == null && nextSibling != null) {
nextSibling.hydrate(p, record);
}
} else {
pos.hydrate(p, record);
}
}
}