/* * 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.core.model.instance; 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.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.Externalizable; import org.openrosa.client.jr.core.util.externalizable.PrototypeFactory; public class TreeReference implements Externalizable { public static final int DEFAULT_MUTLIPLICITY = 0;//multiplicity public static final int INDEX_UNBOUND = -1;//multiplicity public static final int INDEX_TEMPLATE = -2;//multiplicity public static final int REF_ABSOLUTE = -1; public static final String NAME_WILDCARD = "*"; private int refLevel; //0 = context node, 1 = parent, 2 = grandparent ... private Vector names; //Vector<String> private Vector multiplicity; //Vector<Integer> public static TreeReference rootRef () { TreeReference root = new TreeReference(); root.refLevel = REF_ABSOLUTE; return root; } public static TreeReference selfRef () { TreeReference self = new TreeReference(); self.refLevel = 0; return self; } public TreeReference () { names = new Vector(); multiplicity = new Vector(); } public int getMultiplicity(int index) { return ((Integer)multiplicity.elementAt(index)).intValue(); } public String getName(int index) { return (String)names.elementAt(index); } public int getMultLast () { return ((Integer)multiplicity.lastElement()).intValue(); } public String getNameLast () { return (String)names.lastElement(); } public void setMultiplicity (int i, int mult) { multiplicity.setElementAt(new Integer(mult), i); } public int size () { return names.size(); } public void add (String name, int index) { names.addElement(name); multiplicity.addElement(new Integer(index)); } public int getRefLevel () { return refLevel; } public void setRefLevel (int refLevel) { this.refLevel = refLevel; } public void incrementRefLevel () { if (!isAbsolute()) { refLevel++; } } public boolean isAbsolute () { return refLevel == REF_ABSOLUTE; } //return true if this ref contains any unbound multiplicities... ie, there is ANY chance this ref //could ambiguously refer to more than one instance node. public boolean isAmbiguous () { //ignore level 0, as /data implies /data[0] for (int i = 1; i < size(); i++) { if (getMultiplicity(i) == INDEX_UNBOUND) { return true; } } return false; } //return a copy of the ref public TreeReference clone () { TreeReference newRef = new TreeReference(); newRef.setRefLevel(this.refLevel); for (int i = 0; i < this.size(); i++) { newRef.add(this.getName(i), this.getMultiplicity(i)); } return newRef; } /* * chop the lowest level off the ref so that the ref now represents the parent of the original ref * return true if we successfully got the parent, false if there were no higher levels */ public boolean removeLastLevel () { int size = size(); if (size == 0) { if (isAbsolute()) { return false; } else { refLevel++; return true; } } else { names.removeElementAt(size - 1); multiplicity.removeElementAt(size - 1); return true; } } public TreeReference getParentRef () { TreeReference ref = this.clone(); if (ref.removeLastLevel()) { return ref; } else { return null; } } //return a new reference that is this reference anchored to a passed-in parent reference //if this reference is absolute, return self //if this ref has 'parent' steps (..), it can only be anchored if the parent ref is a relative ref consisting only of other 'parent' steps //return null in these invalid situations public TreeReference parent (TreeReference parentRef) { if (isAbsolute()) { return this; } else { TreeReference newRef = parentRef.clone(); if (refLevel > 0) { if (!parentRef.isAbsolute() && parentRef.size() == 0) { parentRef.refLevel += refLevel; } else { return null; } } for (int i = 0; i < names.size(); i++) { newRef.add(this.getName(i), this.getMultiplicity(i)); } return newRef; } } //very similar to parent(), but assumes contextRef refers to a singular, existing node in the model //this means we can do '/a/b/c + ../../d/e/f = /a/d/e/f', which we couldn't do in parent() //return null if context ref is not absolute, or we parent up past the root node //NOTE: this function still works even when contextRef contains INDEX_UNBOUND multiplicites... conditions depend on this behavior, // even though it's slightly icky public TreeReference anchor (TreeReference contextRef) { if (isAbsolute()) { return this.clone(); } else if (!contextRef.isAbsolute()) { return null; } else { TreeReference newRef = contextRef.clone(); int contextSize = contextRef.size(); if (refLevel > contextSize) { return null; //tried to do '/..' } else { for (int i = 0; i < refLevel; i++) { newRef.removeLastLevel(); } for (int i = 0; i < size(); i++) { newRef.add(this.getName(i), this.getMultiplicity(i)); } return newRef; } } } //TODO: merge anchor() and parent() public TreeReference contextualize (TreeReference contextRef) { if (!contextRef.isAbsolute()) return null; TreeReference newRef = anchor(contextRef); for (int i = 0; i < contextRef.size() && i < newRef.size(); i++) { if (contextRef.getName(i).equals(newRef.getName(i))) { newRef.setMultiplicity(i, contextRef.getMultiplicity(i)); } else { break; } } return newRef; } public TreeReference relativize (TreeReference parent) { if (parent.isParentOf(this, false)) { TreeReference relRef = selfRef(); for (int i = parent.size(); i < this.size(); i++) { relRef.add(this.getName(i), INDEX_UNBOUND); } return relRef; } else { return null; } } //turn unambiguous ref into a generic ref public TreeReference genericize () { TreeReference genericRef = clone(); for (int i = 0; i < genericRef.size(); i++) { genericRef.setMultiplicity(i, INDEX_UNBOUND); } return genericRef; } //returns true if 'this' is parent of 'child' //return true if 'this' equals 'child' only if properParent is false public boolean isParentOf (TreeReference child, boolean properParent) { if (refLevel != child.refLevel) return false; if (child.size() < size() + (properParent ? 1 : 0)) return false; for (int i = 0; i < size(); i++) { if (!this.getName(i).equals(child.getName(i))) { return false; } int parMult = this.getMultiplicity(i); int childMult = child.getMultiplicity(i); if (parMult != INDEX_UNBOUND && parMult != childMult && !(i == 0 && parMult == 0 && childMult == INDEX_UNBOUND)) { return false; } } return true; } /** * clone and extend a reference by one level * @param ref * @param name * @param mult * @return */ public TreeReference extendRef (String name, int mult) { TreeReference childRef = this.clone(); childRef.add(name, mult); return childRef; } public boolean equals (Object o) { if (this == o) { return true; } else if (o instanceof TreeReference) { TreeReference ref = (TreeReference)o; if (this.refLevel == ref.refLevel && this.size() == ref.size()) { for (int i = 0; i < this.size(); i++) { String nameA = this.getName(i); String nameB = ref.getName(i); int multA = this.getMultiplicity(i); int multB = ref.getMultiplicity(i); if (!nameA.equals(nameB)) { return false; } else if (multA != multB) { if (i == 0 && (multA == 0 || multA == INDEX_UNBOUND) && (multB == 0 || multB == INDEX_UNBOUND)) { // /data and /data[0] are functionally the same } else { return false; } } } return true; } else { return false; } } else { return false; } } public int hashCode () { int hash = (new Integer(refLevel)).hashCode(); for (int i = 0; i < size(); i++) { //NOTE(ctsims): It looks like this is only using Integer to //get the hashcode method, but that method //is just returning the int value, I think, so //this should potentially just be replaced by //an int. Integer mult = new Integer(getMultiplicity(i)); if (i == 0 && mult.intValue() == INDEX_UNBOUND) mult = new Integer(0); hash ^= getName(i).hashCode(); hash ^= mult.hashCode(); } return hash; } public String toString () { return toString(true); } public String toString (boolean includePredicates) { StringBuffer sb = new StringBuffer(); if (isAbsolute()) { sb.append("/"); } else { for (int i = 0; i < refLevel; i++) sb.append("../"); } for (int i = 0; i < size(); i++) { String name = getName(i); int mult = getMultiplicity(i); sb.append(name); if (includePredicates) { switch (mult) { case INDEX_UNBOUND: break; case INDEX_TEMPLATE: sb.append("[@template]"); break; default: if (i > 0 || mult != 0) sb.append("[" + (mult + 1) + "]"); break; } } if (i < size() - 1) sb.append("/"); } return sb.toString(); } public void readExternal(DataInputStream in, PrototypeFactory pf) throws IOException, DeserializationException { refLevel = ExtUtil.readInt(in); names = (Vector)ExtUtil.read(in, new ExtWrapList(String.class), pf); multiplicity = (Vector)ExtUtil.read(in, new ExtWrapList(Integer.class), pf); } public void writeExternal(DataOutputStream out) throws IOException { ExtUtil.writeNumeric(out, refLevel); ExtUtil.write(out, new ExtWrapList(names)); ExtUtil.write(out, new ExtWrapList(multiplicity)); } }