Skip to content

Commit

Permalink
Merge pull request #595 from getodk/repeat-relevance
Browse files Browse the repository at this point in the history
  • Loading branch information
lognaturel authored Sep 17, 2020
2 parents 9a1ddc6 + 1e87049 commit 3a7f077
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 143 deletions.
10 changes: 9 additions & 1 deletion src/main/java/org/javarosa/core/model/FormDef.java
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,15 @@ public void processResultOfAction(TreeReference refSetByAction, String event) {

public boolean isRepeatRelevant(TreeReference repeatRef) {
TreeElement repeatNode = mainInstance.resolveReference(repeatRef);
return repeatNode == null || repeatNode.isRelevant();

if (repeatNode != null) {
return repeatNode.isRelevant();
} else {
// We are dealing with a repeat that doesn't exist yet so get
// relevance from the template
TreeElement template = mainInstance.getTemplate(repeatRef);
return template.isRelevant();
}
}

public boolean canCreateRepeat(TreeReference repeatRef, FormIndex repeatIndex) {
Expand Down
17 changes: 14 additions & 3 deletions src/main/java/org/javarosa/core/model/TriggerableDag.java
Original file line number Diff line number Diff line change
Expand Up @@ -528,19 +528,30 @@ private void evaluateTriggerable(FormInstance mainInstance, EvaluationContext ev
// the repeat are only triggered for the changed instance. This is important for performance.
TreeReference contextRef = affectsAllRepeatInstances ? toTrigger.getContext() : toTrigger.getContext().contextualize(changedRef);

List<EvaluationResult> evaluationResults = new ArrayList<>(0);
// In general, expansion will have no effect. It only makes a difference if affectsAllRepeatInstances is true in
// which case the triggerable will be applied for every repeat instance.
for (TreeReference qualified : evalContext.expandReference(contextRef))
List<TreeReference> qualifiedReferences = evalContext.expandReference(contextRef);

// We want to update the repeat's template as well
TreeElement template = mainInstance.getTemplate(contextRef);
if (template != null) {
TreeReference templateRef = template.getRef();
qualifiedReferences.add(templateRef);
}

List<EvaluationResult> evaluationResults = new ArrayList<>(0);
for (TreeReference qualified : qualifiedReferences) {
try {
// apply evaluates the expression in the given context and saves the result in the contextualized target(s).
evaluationResults.addAll(toTrigger.apply(mainInstance, new EvaluationContext(evalContext, qualified), qualified));
} catch (Exception e) {
throw new RuntimeException("Error evaluating field '" + contextRef.getNameLast() + "' (" + qualified + "): " + e.getMessage(), e);
}
}

if (evaluationResults.size() > 0)
if (evaluationResults.size() > 0) {
accessor.getEventNotifier().publishEvent(new Event(toTrigger.isCondition() ? "Condition" : "Recalculate", evaluationResults));
}
}

private void evaluateChildrenTriggerables(FormInstance mainInstance, EvaluationContext evalContext, TreeElement newNode, boolean createdOrDeleted, Set<QuickTriggerable> alreadyEvaluated) {
Expand Down
113 changes: 55 additions & 58 deletions src/main/java/org/javarosa/form/api/FormEntryModel.java
Original file line number Diff line number Diff line change
Expand Up @@ -75,19 +75,19 @@ public FormEntryModel(FormDef form) {
*
* @param form
* @param repeatStructure The structure of repeats (the repeat signals which should
* be sent during form entry)
* be sent during form entry)
* @throws IllegalArgumentException If repeatStructure is not valid
*/
public FormEntryModel(FormDef form, int repeatStructure) {
this.form = form;
if(repeatStructure != REPEAT_STRUCTURE_LINEAR && repeatStructure != REPEAT_STRUCTURE_NON_LINEAR) {
throw new IllegalArgumentException(repeatStructure +": does not correspond to a valid repeat structure");
if (repeatStructure != REPEAT_STRUCTURE_LINEAR && repeatStructure != REPEAT_STRUCTURE_NON_LINEAR) {
throw new IllegalArgumentException(repeatStructure + ": does not correspond to a valid repeat structure");
}
//We need to see if there are any guessed repeat counts in the form, which prevents
//us from being able to use the new repeat style
//Unfortunately this is probably (A) slow and (B) might overflow the stack. It's not the only
//recursive walk of the form, though, so (B) isn't really relevant
if(repeatStructure == REPEAT_STRUCTURE_NON_LINEAR && containsRepeatGuesses(form)) {
if (repeatStructure == REPEAT_STRUCTURE_NON_LINEAR && containsRepeatGuesses(form)) {
repeatStructure = REPEAT_STRUCTURE_LINEAR;
}
this.repeatStructure = repeatStructure;
Expand Down Expand Up @@ -131,7 +131,6 @@ public int getEvent(FormIndex index) {
}

/**
*
* @param index
* @return
*/
Expand All @@ -158,25 +157,23 @@ public String getFormTitle() {


/**
*
* @param index
* @return Returns the FormEntryPrompt for the specified FormIndex if the
* index represents a question.
* index represents a question.
*/
public FormEntryPrompt getQuestionPrompt(FormIndex index) {
if (form.getChild(index) instanceof QuestionDef) {
return new FormEntryPrompt(form, index);
} else {
throw new RuntimeException(
"Invalid query for Question prompt. Non-Question object at the form index");
"Invalid query for Question prompt. Non-Question object at the form index");
}
}


/**
*
* @return Returns the FormEntryPrompt for the current FormIndex if the
* index represents a question.
* index represents a question.
*/
public FormEntryPrompt getQuestionPrompt() {
return getQuestionPrompt(currentFormIndex);
Expand All @@ -189,30 +186,28 @@ public FormEntryPrompt getQuestionPrompt() {
*
* @param index
* @return Returns the FormEntryCaption for the given FormIndex if is not a
* question.
* question.
*/
public FormEntryCaption getCaptionPrompt(FormIndex index) {
return new FormEntryCaption(form, index);
}



/**
* When you have a non-question event, a CaptionPrompt will have all the
* information needed to display to the user.
*
* @return Returns the FormEntryCaption for the current FormIndex if is not
* a question.
* a question.
*/
public FormEntryCaption getCaptionPrompt() {
return getCaptionPrompt(currentFormIndex);
}


/**
*
* @return an array of Strings of the current langauges. Null if there are
* none.
* none.
*/
public String[] getLanguages() {
if (form.getLocalizer() != null) {
Expand All @@ -224,7 +219,7 @@ public String[] getLanguages() {

/**
* Not yet implemented
*
* <p>
* Should get the number of completed questions to this point.
*/
public int getCompletedRelevantQuestionCount() {
Expand All @@ -235,7 +230,7 @@ public int getCompletedRelevantQuestionCount() {

/**
* Not yet implemented
*
* <p>
* Should get the total possible questions given the current path through the form.
*/
public int getTotalRelevantQuestionCount() {
Expand All @@ -252,7 +247,6 @@ public int getNumQuestions() {


/**
*
* @return Returns the current FormIndex referenced by the FormEntryModel.
*/
public FormIndex getFormIndex() {
Expand All @@ -268,7 +262,6 @@ protected void setLanguage(String language) {


/**
*
* @return Returns the currently selected language.
*/
public String getLanguage() {
Expand All @@ -292,7 +285,6 @@ public void setQuestionIndex(FormIndex index) {


/**
*
* @return
*/
public FormDef getForm() {
Expand Down Expand Up @@ -352,7 +344,7 @@ public boolean isIndexReadonly(FormIndex index) {

TreeReference ref = form.getChildInstanceRef(index);
boolean isAskNewRepeat = (getEvent(index) == FormEntryController.EVENT_PROMPT_NEW_REPEAT ||
getEvent(index) == FormEntryController.EVENT_REPEAT_JUNCTURE);
getEvent(index) == FormEntryController.EVENT_REPEAT_JUNCTURE);

if (isAskNewRepeat) {
return false;
Expand Down Expand Up @@ -383,19 +375,20 @@ public boolean isIndexRelevant(FormIndex index) {
boolean isAskNewRepeat = (getEvent(index) == FormEntryController.EVENT_PROMPT_NEW_REPEAT);
boolean isRepeatJuncture = (getEvent(index) == FormEntryController.EVENT_REPEAT_JUNCTURE);

boolean relevant;
if (isAskNewRepeat) {
relevant = form.isRepeatRelevant(ref) && form.canCreateRepeat(ref, index);
if (!form.canCreateRepeat(ref, index)) {
return false;
}

return form.isRepeatRelevant(ref);
} else if (isRepeatJuncture) {
//repeat junctures are still relevant if no new repeat can be created; that option
//is simply missing from the menu
} else if (isRepeatJuncture) {
relevant = form.isRepeatRelevant(ref);
return form.isRepeatRelevant(ref);
} else {
TreeElement node = form.getMainInstance().resolveReference(ref);
relevant = node != null && node.isRelevant();
return node != null && node.isRelevant();
}

return relevant;
}


Expand All @@ -414,17 +407,17 @@ public boolean isIndexRelevant() {
* For the current index: Checks whether the index represents a node which
* should exist given a non-interactive repeat, along with a count for that
* repeat which is beneath the dynamic level specified.
*
* <p>
* If this index does represent such a node, the new model for the repeat is
* created behind the scenes and the index for the initial question is
* returned.
*
* <p>
* Note: This method will not prevent the addition of new repeat elements in
* the interface, it will merely use the xforms repeat hint to create new
* nodes that are assumed to exist
*
* @param index The index to be evaluated as to whether the underlying model is
* hinted to exist
* hinted to exist
*/
private void createModelIfNecessary(FormIndex index) {
if (index.isInForm()) {
Expand All @@ -443,12 +436,12 @@ private void createModelIfNecessary(FormIndex index) {
if (element == null) {
if (index.getTerminal().getInstanceIndex() < fullcount) {

try {
getForm().createNewRepeat(index);
} catch (InvalidReferenceException ire) {
logger.error("Error", ire);
throw new RuntimeException("Invalid Reference while creating new repeat!" + ire.getMessage());
}
try {
getForm().createNewRepeat(index);
} catch (InvalidReferenceException ire) {
logger.error("Error", ire);
throw new RuntimeException("Invalid Reference while creating new repeat!" + ire.getMessage());
}
}
}
}
Expand All @@ -465,8 +458,8 @@ public boolean isIndexCompoundContainer() {
public boolean isIndexCompoundContainer(FormIndex index) {
FormEntryCaption caption = getCaptionPrompt(index);
return getEvent(index) == FormEntryController.EVENT_GROUP &&
caption.getAppearanceHint() != null &&
caption.getAppearanceHint().toLowerCase(Locale.ENGLISH).equals("full");
caption.getAppearanceHint() != null &&
caption.getAppearanceHint().toLowerCase(Locale.ENGLISH).equals("full");
}

public boolean isIndexCompoundElement() {
Expand All @@ -475,16 +468,16 @@ public boolean isIndexCompoundElement() {

public boolean isIndexCompoundElement(FormIndex index) {
//Can't be a subquestion if it's not even a question!
if(getEvent(index) != FormEntryController.EVENT_QUESTION) {
if (getEvent(index) != FormEntryController.EVENT_QUESTION) {
return false;
}

//get the set of nested groups that this question is in.
FormEntryCaption[] captions = getCaptionHierarchy(index);
for(FormEntryCaption caption : captions) {
for (FormEntryCaption caption : captions) {

//If one of this question's parents is a group, this question is inside of it.
if(isIndexCompoundContainer(caption.getIndex())) {
if (isIndexCompoundContainer(caption.getIndex())) {
return true;
}
}
Expand All @@ -497,16 +490,16 @@ public FormIndex[] getCompoundIndices() {

public FormIndex[] getCompoundIndices(FormIndex container) {
//ArrayLists are a no-go for J2ME
List<FormIndex> indices = new ArrayList<FormIndex>();
List<FormIndex> indices = new ArrayList<FormIndex>();
FormIndex walker = incrementIndex(container);
while(FormIndex.isSubElement(container, walker)) {
if(isIndexRelevant(walker)) {
while (FormIndex.isSubElement(container, walker)) {
if (isIndexRelevant(walker)) {
indices.add(walker);
}
walker = incrementIndex(walker);
}
FormIndex[] array = new FormIndex[indices.size()];
for(int i = 0 ; i < indices.size() ; ++i) {
for (int i = 0; i < indices.size(); ++i) {
array[i] = indices.get(i);
}
return array;
Expand Down Expand Up @@ -548,7 +541,7 @@ public FormIndex incrementIndex(FormIndex index, boolean descend) {
}
}

private void incrementHelper(List<Integer> indexes, List<Integer> multiplicities, List<IFormElement> elements, boolean descend) {
private void incrementHelper(List<Integer> indexes, List<Integer> multiplicities, List<IFormElement> elements, boolean descend) {
int i = indexes.size() - 1;
boolean exitRepeat = false; //if exiting a repetition? (i.e., go to next repetition instead of one level up)

Expand All @@ -571,7 +564,7 @@ private void incrementHelper(List<Integer> indexes, List<Integer> multiplicities

} else {

if (form.getMainInstance().resolveReference(form.getChildInstanceRef(elements, multiplicities)) == null) {
if (form.getMainInstance().resolveReference(form.getChildInstanceRef(elements, multiplicities)) == null) {
descend = false; // repeat instance does not exist; do not descend into it
exitRepeat = true;
}
Expand All @@ -588,7 +581,7 @@ private void incrementHelper(List<Integer> indexes, List<Integer> multiplicities
elements.add((i == -1 ? form : elements.get(i)).getChild(0));

if (repeatStructure == REPEAT_STRUCTURE_NON_LINEAR) {
if (elements.get(elements.size() - 1) instanceof GroupDef && ((GroupDef)elements.get(elements.size() - 1)).getRepeat()) {
if (elements.get(elements.size() - 1) instanceof GroupDef && ((GroupDef) elements.get(elements.size() - 1)).getRepeat()) {
multiplicities.set(multiplicities.size() - 1, TreeReference.INDEX_REPEAT_JUNCTURE);
}
}
Expand Down Expand Up @@ -634,7 +627,7 @@ private void incrementHelper(List<Integer> indexes, List<Integer> multiplicities
elements.set(i, parent.getChild(curIndex + 1));

if (repeatStructure == REPEAT_STRUCTURE_NON_LINEAR) {
if (elements.get(elements.size() - 1) instanceof GroupDef && ((GroupDef)elements.get(elements.size() - 1)).getRepeat()) {
if (elements.get(elements.size() - 1) instanceof GroupDef && ((GroupDef) elements.get(elements.size() - 1)).getRepeat()) {
multiplicities.set(multiplicities.size() - 1, TreeReference.INDEX_REPEAT_JUNCTURE);
}
}
Expand Down Expand Up @@ -676,8 +669,8 @@ private void decrementHelper(List<Integer> indexes, List<Integer> multiplicities
int curMult = multiplicities.get(i);

if (repeatStructure == REPEAT_STRUCTURE_NON_LINEAR &&
elements.get(elements.size() - 1) instanceof GroupDef && ((GroupDef) elements.get(elements.size() - 1)).getRepeat() &&
multiplicities.get(multiplicities.size() - 1) != TreeReference.INDEX_REPEAT_JUNCTURE) {
elements.get(elements.size() - 1) instanceof GroupDef && ((GroupDef) elements.get(elements.size() - 1)).getRepeat() &&
multiplicities.get(multiplicities.size() - 1) != TreeReference.INDEX_REPEAT_JUNCTURE) {
multiplicities.set(i, TreeReference.INDEX_REPEAT_JUNCTURE);
return;
} else if (repeatStructure != REPEAT_STRUCTURE_NON_LINEAR && curMult > 0) {
Expand All @@ -701,7 +694,7 @@ private void decrementHelper(List<Integer> indexes, List<Integer> multiplicities

IFormElement element = (i < 0 ? form : elements.get(i));
while (!(element instanceof QuestionDef)) {
if(element.getChildren() == null || element.getChildren().size() == 0) {
if (element.getChildren() == null || element.getChildren().size() == 0) {
//if there are no children we just return the current index (the group itself)
return;
}
Expand Down Expand Up @@ -751,17 +744,21 @@ private boolean setRepeatNextMultiplicity(List<IFormElement> elements, List<Inte
* which has a count guess, false otherwise.
*/
private boolean containsRepeatGuesses(IFormElement parent) {
if(parent instanceof GroupDef) {
GroupDef g = (GroupDef)parent;
if (parent instanceof GroupDef) {
GroupDef g = (GroupDef) parent;
if (g.getRepeat() && g.getCountReference() != null) {
return true;
}
}

List<IFormElement> children = parent.getChildren();
if(children == null) { return false; }
for (IFormElement child : children) {
if(containsRepeatGuesses(child)) {return true;}
if (children == null) {
return false;
}
for (IFormElement child : children) {
if (containsRepeatGuesses(child)) {
return true;
}
}
return false;
}
Expand Down
Loading

0 comments on commit 3a7f077

Please sign in to comment.