/*
* 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;
import org.openrosa.client.jr.core.model.instance.TreeReference;
/**
* A Form Index is an immutable index into a specific question definition that
* will appear in an interaction with a user.
*
* An index is represented by different levels into hierarchical groups.
*
* Indices can represent both questions and groups.
*
* It is absolutely essential that there be no circularity of reference in
* FormIndex's, IE, no form index's ancestor can be itself.
*
* Datatype Productions:
* FormIndex = BOF | EOF | CompoundIndex(nextIndex:FormIndex,Location)
* Location = Empty | Simple(localLevel:int) | WithMult(localLevel:int, multiplicity:int)
*
* @author Clayton Sims
*
*/
public class FormIndex {
private boolean beginningOfForm = false;
private boolean endOfForm = false;
/** The index of the questiondef in the current context */
private int localIndex;
/** The multiplicity of the current instance of a repeated question or group */
private int instanceIndex = -1;
/** The next level of this index */
private FormIndex nextLevel;
private TreeReference reference;
public static FormIndex createBeginningOfFormIndex() {
FormIndex begin = new FormIndex(-1, null);
begin.beginningOfForm = true;
return begin;
}
public static FormIndex createEndOfFormIndex() {
FormIndex end = new FormIndex(-1,null);
end.endOfForm = true;
return end;
}
/**
* Constructs a simple form index that references a specific element in
* a list of elements.
* @param localIndex An integer index into a flat list of elements
* @param reference A reference to the instance element identified by this index;
*/
public FormIndex(int localIndex, TreeReference reference) {
this.localIndex = localIndex;
this.reference = reference;
}
/**
* Constructs a simple form index that references a specific element in
* a list of elements.
* @param localIndex An integer index into a flat list of elements
* @param instanceIndex An integer index expressing the multiplicity
* of the current level
* @param reference A reference to the instance element identified by this index;
*
*/
public FormIndex(int localIndex, int instanceIndex,TreeReference reference) {
this.localIndex = localIndex;
this.instanceIndex = instanceIndex;
this.reference = reference;
}
/**
* Constructs an index which indexes an element, and provides an index
* into that elements children
*
* @param nextLevel An index into the referenced element's index
* @param localIndex An index to an element at the current level, a child
* element of which will be referenced by the nextLevel index.
* @param reference A reference to the instance element identified by this index;
*/
public FormIndex(FormIndex nextLevel, int localIndex,TreeReference reference) {
this(localIndex, reference);
this.nextLevel = nextLevel;
}
/**
* Constructs an index which references an element past the level of
* specificity of the current context, founded by the currentLevel
* index.
* (currentLevel, (nextLevel...))
*/
public FormIndex(FormIndex nextLevel, FormIndex currentLevel) {
if(currentLevel == null) {
this.nextLevel = nextLevel.nextLevel;
this.localIndex = nextLevel.localIndex;
this.instanceIndex = nextLevel.instanceIndex;
this.reference = nextLevel.reference;
} else {
this.nextLevel = nextLevel;
this.localIndex = currentLevel.getLocalIndex();
this.instanceIndex = currentLevel.getInstanceIndex();
this.reference = currentLevel.reference;
}
}
/**
* Constructs an index which indexes an element, and provides an index
* into that elements children, along with the current index of a
* repeated instance.
*
* @param nextLevel An index into the referenced element's index
* @param localIndex An index to an element at the current level, a child
* element of which will be referenced by the nextLevel index.
* @param instanceIndex How many times the element referenced has been
* repeated.
* @param reference A reference to the instance element identified by this index;
*/
public FormIndex(FormIndex nextLevel, int localIndex, int instanceIndex, TreeReference reference) {
this(nextLevel, localIndex, reference);
this.instanceIndex = instanceIndex;
}
public boolean isInForm () {
return !beginningOfForm && !endOfForm;
}
/**
* @return The index of the element in the current context
*/
public int getLocalIndex() {
return localIndex;
}
/**
* @return The multiplicity of the current instance of a repeated question or group
*/
public int getInstanceIndex() {
return instanceIndex;
}
/**
* For the fully qualified element, get the multiplicity of the element's reference
* @return The terminal element (fully qualified)'s instance index
*/
public int getElementMultiplicity() {
return getTerminal().instanceIndex;
}
/**
* @return An index into the next level of specificity past the current context. An
* example would be an index into an element that is a child of the element referenced
* by the local index.
*/
public FormIndex getNextLevel() {
return nextLevel;
}
public TreeReference getLocalReference() {
return reference;
}
/**
* @return The TreeReference of the fully qualified element described by this
* FormIndex.
*/
public TreeReference getReference() {
return getTerminal().reference;
}
public FormIndex getTerminal() {
FormIndex walker = this;
while(walker.nextLevel != null) {
walker = walker.nextLevel;
}
return walker;
}
/**
* Identifies whether this is a terminal index, in other words whether this
* index references with more specificity than the current context
*/
public boolean isTerminal() {
return nextLevel == null;
}
public boolean isEndOfFormIndex() {
return endOfForm;
}
public boolean isBeginningOfFormIndex() {
return beginningOfForm;
}
public boolean equals(Object o) {
if(!(o instanceof FormIndex))
return false;
FormIndex a = this;
FormIndex b = (FormIndex)o;
return (a.compareTo(b) == 0);
// //TODO: while(true) loops freak me out, this should probably
// //get written more safely. -ctsims
//
// //Iterate over each level of reference, and identify whether
// //each object stays in sync
// while(true) {
// if(index.isTerminal() != local.isTerminal() ||
// index.getLocalIndex() != local.getLocalIndex() ||
// index.getInstanceIndex() != local.getInstanceIndex()) {
// return false;
// }
// if(index.isTerminal()) {
// return true;
// }
// local = local.getNextLevel();
// index = index.getNextLevel();
// }
//
}
public int compareTo(Object o) {
if(!(o instanceof FormIndex))
throw new IllegalArgumentException("Attempt to compare Object of type " + o.getClass().getName() + " to a FormIndex");
FormIndex a = this;
FormIndex b = (FormIndex)o;
if (a.beginningOfForm) {
return (b.beginningOfForm ? 0 : -1);
} else if (a.endOfForm) {
return (b.endOfForm ? 0 : 1);
} else {
//a is in form
if (b.beginningOfForm) {
return 1;
} else if (b.endOfForm) {
return -1;
}
}
if (a.localIndex != b.localIndex) {
return (a.localIndex < b.localIndex ? -1 : 1);
} else if (a.instanceIndex != b.instanceIndex) {
return (a.instanceIndex < b.instanceIndex ? -1 : 1);
} else if ((a.getNextLevel() == null) != (b.getNextLevel() == null)) {
return (a.getNextLevel() == null ? -1 : 1);
} else if (a.getNextLevel() != null) {
return a.getNextLevel().compareTo(b.getNextLevel());
} else {
return 0;
}
// int comp = 0;
//
// //TODO: while(true) loops freak me out, this should probably
// //get written more safely. -ctsims
// while(comp == 0) {
// if(index.isTerminal() != local.isTerminal() ||
// index.getLocalIndex() != local.getLocalIndex() ||
// index.getInstanceIndex() != local.getInstanceIndex()) {
// if(local.localIndex > index.localIndex) {
// return 1;
// } else if(local.localIndex < index.localIndex) {
// return -1;
// } else if (local.instanceIndex > index.instanceIndex) {
// return 1;
// } else if (local.instanceIndex < index.instanceIndex) {
// return -1;
// }
//
// //This case is here as a fallback, but it shouldn't really
// //ever be the case that two references have the same chain
// //of indices without terminating at the same level.
// else if (local.isTerminal() && !index.isTerminal()) {
// return -1;
// } else {
// return 1;
// }
// }
// else if(local.isTerminal()) {
// break;
// }
// local = local.getNextLevel();
// index = index.getNextLevel();
// }
// return comp;
}
/**
* @return Only the local component of this Form Index.
*/
public FormIndex snip() {
FormIndex retval = new FormIndex(localIndex, instanceIndex,reference);
return retval;
}
/**
* Takes in a form index which is a subset of this index, and returns the
* total difference between them. This is useful for stepping up the level
* of index specificty. If the subIndex is not a valid subIndex of this index,
* null is returned. Since the FormIndex represented by null is always a subset,
* if null is passed in as a subIndex, the full index is returned
*
* For example:
* Indices
* a = 1_0,2,1,3
* b = 1,3
*
* a.diff(b) = 1_0,2
*
* @param subIndex
* @return
*/
public FormIndex diff(FormIndex subIndex) {
if(subIndex == null) {
return this;
}
if(!isSubIndex(this,subIndex)) {
return null;
}
if(subIndex.equals(this)) {
return null;
}
return new FormIndex(nextLevel.diff(subIndex),this.snip());
}
public String toString() {
String ret = "";
FormIndex ref = this;
while (ref != null) {
ret += ref.getLocalIndex();
ret += ref.getInstanceIndex()== -1? ", " : "_" + ref.getInstanceIndex() + ", ";
ref = ref.nextLevel;
}
return ret;
}
/**
* @return the level of this index relative to the top level of the form
*/
public int getDepth() {
int depth = 0;
FormIndex ref = this;
while (ref != null) {
ref = ref.nextLevel;
depth++;
}
return depth;
}
/**
* Trims any negative indices from the end of the passed in index.
*
* @param index
* @return
*/
public static FormIndex trimNegativeIndices(FormIndex index) {
if(!index.isTerminal()) {
return new FormIndex(trimNegativeIndices(index.nextLevel),index);
} else {
if(index.getLocalIndex() < 0) {
return null;
} else {
return index;
}
}
}
public static boolean isSubIndex(FormIndex parent, FormIndex child) {
if(child.equals(parent)) {
return true;
} else {
if(parent == null) {
return false;
}
return isSubIndex(parent.nextLevel, child);
}
}
public static boolean isSubElement(FormIndex parent, FormIndex child) {
while(!parent.isTerminal() && !child.isTerminal()) {
if(parent.getLocalIndex() != child.getLocalIndex()) {
return false;
}
if(parent.getInstanceIndex() != child.getInstanceIndex()) {
return false;
}
parent = parent.nextLevel;
child = child.nextLevel;
}
//If we've gotten this far, at least one of the two is terminal
if(!parent.isTerminal() && child.isTerminal()) {
//can't be the parent if the child is earlier on
return false;
}
else if(parent.getLocalIndex() != child.getLocalIndex()) {
//Either they're at the same level, in which case only
//identical indices should match, or they should have
//the same root
return false;
}
else if(parent.getInstanceIndex() != -1 && (parent.getInstanceIndex() != child.getInstanceIndex())) {
return false;
}
//Barring all of these cases, it should be true.
return true;
}
}