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 The type of objects that Pulloid will create. This class must be written by developers. */ public class ElementContext extends NodeContext { private int depth; private CursorDef cursorDef; private ElementContext lastChild; private ElementContext firstChild; private AttributeContext attrChain; protected ElementContext(ElementContext parent, String name) { super(parent, name); } protected void setCursorDef(CursorDef def) { this.cursorDef = def; } protected CursorDef getCursorDef() { return this.cursorDef; } protected void setDepth(int depth) { this.depth = depth; } protected int getDepth() { return this.depth; } public ElementContext selectElement(String name) { ElementContext ctx = new ElementContext(this, name); selectElement(ctx); return ctx; } protected void selectElement(ElementContext selected) { selected.depth = this.depth + 1; if(firstChild == null) firstChild = selected; else lastChild.nextSibling = selected; lastChild = selected; } public ElementContext createFieldOnAttribute(String fieldName, String attributeName) { AttributeContext ctx = new AttributeContext(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 createFieldOnAttribute(String fieldName, String attributeName, Class type) { AttributeContext ctx = new AttributeContext(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 ElementContext createFieldOnAttribute(String fieldName, String attributeName, Class type, StringTransformer tr) { AttributeContext ctx = new AttributeContext(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 createField(String name) { registerField(name); return this; } public ElementContext createField(String name, Class type) { registerField(name, type); return this; } public ElementContext createField(String name, Class type, StringTransformer tr) { registerField(name, type, tr); return this; } public CursorDef defineCursor(Class cursorType) { cursorDef = new CursorDef(cursorType, this); return cursorDef; } public CursorDef defineCursor() { cursorDef = new CursorDef(this); return cursorDef; } @Override CursorDef 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 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 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 matchSelfOrSibling(XmlPullParser p) throws XmlPullParserException { if(p.getName().equals(name) && ParserUtil.isStartTag(p)) { return this; } if(nextSibling != null) return ((ElementContext)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 record) throws XmlPullParserException, IOException { ElementContext 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) { record.set(setter, p.nextText(), transformer); // Move to my end tag } if(cursorDef == null && nextSibling != null) { nextSibling.hydrate(p, record); } } else { pos.hydrate(p, record); } } }