/* * Copyright (C) 2009 JavaRosa * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package org.openrosa.client.jr.xpath.expr; import java.io.IOException; import java.util.Vector; import org.openrosa.client.java.io.DataInputStream; import org.openrosa.client.java.io.DataOutputStream; import org.openrosa.client.jr.core.model.condition.EvaluationContext; import org.openrosa.client.jr.core.model.data.BooleanData; import org.openrosa.client.jr.core.model.data.DateData; import org.openrosa.client.jr.core.model.data.DecimalData; import org.openrosa.client.jr.core.model.data.IAnswerData; import org.openrosa.client.jr.core.model.data.IntegerData; import org.openrosa.client.jr.core.model.data.SelectMultiData; import org.openrosa.client.jr.core.model.data.SelectOneData; import org.openrosa.client.jr.core.model.data.StringData; import org.openrosa.client.jr.core.model.data.helper.Selection; import org.openrosa.client.jr.core.model.instance.FormInstance; import org.openrosa.client.jr.core.model.instance.TreeElement; import org.openrosa.client.jr.core.model.instance.TreeReference; import org.openrosa.client.jr.core.util.externalizable.DeserializationException; import org.openrosa.client.jr.core.util.externalizable.ExtUtil; import org.openrosa.client.jr.core.util.externalizable.ExtWrapList; import org.openrosa.client.jr.core.util.externalizable.PrototypeFactory; import org.openrosa.client.jr.xforms.util.XFormAnswerDataSerializer; import org.openrosa.client.jr.xpath.XPathTypeMismatchException; import org.openrosa.client.jr.xpath.XPathUnsupportedException; public class XPathPathExpr extends XPathExpression { public static final int INIT_CONTEXT_ROOT = 0; public static final int INIT_CONTEXT_RELATIVE = 1; public static final int INIT_CONTEXT_EXPR = 2; public int init_context; public XPathStep[] steps; //for INIT_CONTEXT_EXPR only public XPathFilterExpr filtExpr; public XPathPathExpr () { } //for deserialization public XPathPathExpr (int init_context, XPathStep[] steps) { this.init_context = init_context; this.steps = steps; } public XPathPathExpr (XPathFilterExpr filtExpr, XPathStep[] steps) { this(INIT_CONTEXT_EXPR, steps); this.filtExpr = filtExpr; } public TreeReference getReference () throws XPathUnsupportedException { return getReference(false); } /** * translate an xpath path reference into a TreeReference * TreeReferences only support a subset of true xpath paths; restrictions are: * simple child name tests 'child::name', '.', and '..' allowed only * no predicates * all '..' steps must come before anything else */ public TreeReference getReference (boolean allowPredicates) throws XPathUnsupportedException { TreeReference ref = new TreeReference(); boolean parentsAllowed; switch (init_context) { case XPathPathExpr.INIT_CONTEXT_ROOT: ref.setRefLevel(TreeReference.REF_ABSOLUTE); parentsAllowed = false; break; case XPathPathExpr.INIT_CONTEXT_RELATIVE: ref.setRefLevel(0); parentsAllowed = true; break; default: throw new XPathUnsupportedException("filter expression"); } for (int i = 0; i < steps.length; i++) { XPathStep step = steps[i]; if (!allowPredicates && step.predicates.length > 0) { throw new XPathUnsupportedException("predicates"); } if (step.axis == XPathStep.AXIS_SELF) { if (step.test != XPathStep.TEST_TYPE_NODE) { throw new XPathUnsupportedException("step other than 'child::name', '.', '..'"); } } else if (step.axis == XPathStep.AXIS_PARENT) { if (!parentsAllowed || step.test != XPathStep.TEST_TYPE_NODE) { throw new XPathUnsupportedException("step other than 'child::name', '.', '..'"); } else { ref.incrementRefLevel(); } } else if (step.axis == XPathStep.AXIS_CHILD) { if (step.test == XPathStep.TEST_NAME) { ref.add(step.name.toString(), TreeReference.INDEX_UNBOUND); parentsAllowed = false; } else if(step.test == XPathStep.TEST_NAME_WILDCARD) { ref.add(TreeReference.NAME_WILDCARD, TreeReference.INDEX_UNBOUND); parentsAllowed = false; } else { throw new XPathUnsupportedException("step other than 'child::name', '.', '..'"); } } else { throw new XPathUnsupportedException("step other than 'child::name', '.', '..'"); } } return ref; } public Object eval (FormInstance m, EvaluationContext evalContext) { return eval(m, evalContext, false); } public Object eval (FormInstance m, EvaluationContext evalContext, boolean forceNodeset) { TreeReference ref = getReference().contextualize(evalContext.getContextRef()); //ITEMSET TODO: need to update this; for itemset/copy constraints, need to simulate a whole xml sub-tree here if (evalContext.isConstraint && ref.equals(evalContext.getContextRef())) { return unpackValue(evalContext.candidateValue); } boolean nodeset = forceNodeset; if (!nodeset) { //is this a nodeset? it is if the ref contains any unbound multiplicities AND the unbound nodes are repeatable //the way i'm calculating this sucks; there has got to be an easier way to find out if a node is repeatable TreeReference repeatTestRef = TreeReference.rootRef(); for (int i = 0; i < ref.size(); i++) { repeatTestRef.add(ref.getName(i), ref.getMultiplicity(i)); if (ref.getMultiplicity(i) == TreeReference.INDEX_UNBOUND) { if (m.getTemplate(repeatTestRef) != null) { nodeset = true; break; } } } } if (nodeset) { Vector nodesetRefs = m.expandReference(ref); //to fix conditions based on non-relevant data, filter the nodeset by relevancy for (int i = 0; i < nodesetRefs.size(); i++) { if (!m.resolveReference((TreeReference)nodesetRefs.elementAt(i)).isRelevant()) { nodesetRefs.removeElementAt(i); i--; } } return nodesetRefs; } else { return getRefValue(m, ref); } } public static Object getRefValue (FormInstance model, TreeReference ref) { TreeElement node = model.resolveReference(ref); if (node == null) { throw new XPathTypeMismatchException("Node " + ref.toString() + " does not exist!"); } return unpackValue(node.isRelevant() ? node.getValue() : null); } private static Object unpackValue (IAnswerData val) { if (val == null) { return ""; } else if (val instanceof IntegerData) { return new Double(((Integer)val.getValue()).doubleValue()); } else if (val instanceof DecimalData) { return val.getValue(); } else if (val instanceof StringData) { return val.getValue(); } else if (val instanceof SelectOneData) { return ((Selection)val.getValue()).getValue(); } else if (val instanceof SelectMultiData) { return (new XFormAnswerDataSerializer()).serializeAnswerData(val); } else if (val instanceof DateData) { return val.getValue(); } else if (val instanceof BooleanData) { return val.getValue(); } else { System.out.println("warning: unrecognized data type in xpath expr: " + val.getClass().getName()); return val.getValue(); //is this a good idea? } } public String toString () { StringBuffer sb = new StringBuffer(); sb.append("{path-expr:"); switch (init_context) { case INIT_CONTEXT_ROOT: sb.append("abs"); break; case INIT_CONTEXT_RELATIVE: sb.append("rel"); break; case INIT_CONTEXT_EXPR: sb.append(filtExpr.toString()); break; } sb.append(",{"); for (int i = 0; i < steps.length; i++) { sb.append(steps[i].toString()); if (i < steps.length - 1) sb.append(","); } sb.append("}}"); return sb.toString(); } public boolean equals (Object o) { if (o instanceof XPathPathExpr) { XPathPathExpr x = (XPathPathExpr)o; //Shortcuts for easily comparable values if(init_context != x.init_context || steps.length != x.steps.length) { return false; } return ExtUtil.arrayEquals(steps, x.steps) && (init_context == INIT_CONTEXT_EXPR ? filtExpr.equals(x.filtExpr) : true); } else { return false; } } public void readExternal(DataInputStream in, PrototypeFactory pf) throws IOException, DeserializationException { init_context = ExtUtil.readInt(in); if (init_context == INIT_CONTEXT_EXPR) { filtExpr = (XPathFilterExpr)ExtUtil.read(in, XPathFilterExpr.class, pf); } Vector v = (Vector)ExtUtil.read(in, new ExtWrapList(XPathStep.class), pf); steps = new XPathStep[v.size()]; for (int i = 0; i < steps.length; i++) steps[i] = (XPathStep)v.elementAt(i); } public void writeExternal(DataOutputStream out) throws IOException { ExtUtil.writeNumeric(out, init_context); if (init_context == INIT_CONTEXT_EXPR) { ExtUtil.write(out, filtExpr); } Vector v = new Vector(); for (int i = 0; i < steps.length; i++) v.addElement(steps[i]); ExtUtil.write(out, new ExtWrapList(v)); } public static XPathPathExpr fromRef (TreeReference ref) { XPathPathExpr path = new XPathPathExpr(); path.init_context = (ref.isAbsolute() ? INIT_CONTEXT_ROOT : INIT_CONTEXT_RELATIVE); path.steps = new XPathStep[ref.size()]; for (int i = 0; i < path.steps.length; i++) { if (ref.getName(i).equals(TreeReference.NAME_WILDCARD)) { path.steps[i] = new XPathStep(XPathStep.AXIS_CHILD, XPathStep.TEST_NAME_WILDCARD); } else { path.steps[i] = new XPathStep(XPathStep.AXIS_CHILD, new XPathQName(ref.getName(i))); } } return path; } }