diff --git a/src/cli/java/org/commcare/util/engine/CommCareConfigEngine.java b/src/cli/java/org/commcare/util/engine/CommCareConfigEngine.java index a1f403a1ab..c0bd105c1a 100644 --- a/src/cli/java/org/commcare/util/engine/CommCareConfigEngine.java +++ b/src/cli/java/org/commcare/util/engine/CommCareConfigEngine.java @@ -200,9 +200,11 @@ private void init(String profileRef) throws InstallCancelledException, installAppFromReference(profileRef); } - public void installAppFromReference(String profileReference) throws UnresolvedResourceException, + public void installAppFromReference(String profileReference) + throws UnresolvedResourceException, UnfullfilledRequirementsException, InstallCancelledException { - ResourceManager.installAppResources(platform, profileReference, this.table, true); + ResourceManager.installAppResources(platform, profileReference, this.table, true, + Resource.RESOURCE_AUTHORITY_LOCAL); } public void initEnvironment() { diff --git a/src/main/java/org/commcare/cases/util/StringUtils.java b/src/main/java/org/commcare/cases/util/StringUtils.java index 3e30674918..4b7f008e40 100644 --- a/src/main/java/org/commcare/cases/util/StringUtils.java +++ b/src/main/java/org/commcare/cases/util/StringUtils.java @@ -1,8 +1,11 @@ package org.commcare.cases.util; import org.commcare.modern.util.Pair; +import org.javarosa.core.util.ArrayUtilities; import java.text.Normalizer; +import java.util.ArrayList; +import java.util.List; import java.util.regex.Pattern; /** @@ -45,9 +48,9 @@ public synchronized static String normalize(String input) { //if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { normalized = Normalizer.normalize(input, Normalizer.Form.NFD); //} else { - //TODO: I doubt it's worth it, but in theory we could run - //some other normalization for the minority of pre-API9 - //devices. + //TODO: I doubt it's worth it, but in theory we could run + //some other normalization for the minority of pre-API9 + //devices. //} String output = diacritics.matcher(normalized).replaceAll("").toLowerCase(); @@ -134,4 +137,18 @@ private static int LevenshteinDistance(String s0, String s1) { // the distance is the cost for transforming all letters in both strings return cost[len0 - 1]; } + + /** + * Converts a string to a list of Characters. + */ + public static ArrayList toList(String str) { + + ArrayList myArrayList = new ArrayList<>(str.length()); + + for (int i = 0; i < str.length(); i++) { + myArrayList.add(i, str.charAt(i)); + } + + return myArrayList; + } } diff --git a/src/main/java/org/commcare/resources/ResourceManager.java b/src/main/java/org/commcare/resources/ResourceManager.java index ecd6abd7ae..7017f4d063 100644 --- a/src/main/java/org/commcare/resources/ResourceManager.java +++ b/src/main/java/org/commcare/resources/ResourceManager.java @@ -50,7 +50,8 @@ public ResourceManager(CommCarePlatform platform, * version numbers? */ public static void installAppResources(CommCarePlatform platform, String profileReference, - ResourceTable global, boolean forceInstall) + ResourceTable global, boolean forceInstall, + int authorityForProfile) throws UnfullfilledRequirementsException, UnresolvedResourceException, InstallCancelledException { @@ -64,11 +65,10 @@ public static void installAppResources(CommCarePlatform platform, String profile global.getResourceWithId(CommCarePlatform.APP_PROFILE_RESOURCE_ID); if (profile == null) { - // grab the local profile and parse it + // Create a stub for the profile resource that points to the authority and location + // from which we will install it Vector locations = new Vector<>(); - locations.addElement(new ResourceLocation(Resource.RESOURCE_AUTHORITY_LOCAL, profileReference)); - - // We need a way to identify this version... + locations.addElement(new ResourceLocation(authorityForProfile, profileReference)); Resource r = new Resource(Resource.RESOURCE_VERSION_UNKNOWN, CommCarePlatform.APP_PROFILE_RESOURCE_ID, locations, "Application Descriptor"); diff --git a/src/main/java/org/javarosa/xpath/expr/FunctionUtils.java b/src/main/java/org/javarosa/xpath/expr/FunctionUtils.java index fb6251ccc5..3b340f59a7 100644 --- a/src/main/java/org/javarosa/xpath/expr/FunctionUtils.java +++ b/src/main/java/org/javarosa/xpath/expr/FunctionUtils.java @@ -80,6 +80,7 @@ public class FunctionUtils { funcList.put(XPathUuidFunc.NAME, XPathUuidFunc.class); funcList.put(XPathIdCompressFunc.NAME, XPathIdCompressFunc.class); funcList.put(XPathJoinChunkFunc.NAME, XPathJoinChunkFunc.class); + funcList.put(XPathChecksumFunc.NAME, XPathChecksumFunc.class); funcList.put(XPathSortFunc.NAME, XPathSortFunc.class); funcList.put(XPathSortByFunc.NAME, XPathSortByFunc.class); } diff --git a/src/main/java/org/javarosa/xpath/expr/XPathChecksumFunc.java b/src/main/java/org/javarosa/xpath/expr/XPathChecksumFunc.java new file mode 100644 index 0000000000..79dde2716b --- /dev/null +++ b/src/main/java/org/javarosa/xpath/expr/XPathChecksumFunc.java @@ -0,0 +1,101 @@ +package org.javarosa.xpath.expr; + + +import org.commcare.cases.util.StringUtils; +import org.javarosa.core.model.condition.EvaluationContext; +import org.javarosa.core.model.instance.DataInstance; +import org.javarosa.core.util.ArrayUtilities; +import org.javarosa.xpath.XPathUnsupportedException; +import org.javarosa.xpath.parser.XPathSyntaxException; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; + +public class XPathChecksumFunc extends XPathFuncExpr { + public static final String NAME = "checksum"; + private static final int EXPECTED_ARG_COUNT = 2; + + public static final String ALGORITHM_KEY_VERHOEFF = "verhoeff"; + + public XPathChecksumFunc() { + name = NAME; + expectedArgCount = EXPECTED_ARG_COUNT; + } + + public XPathChecksumFunc(XPathExpression[] args) throws XPathSyntaxException { + super(NAME, args, EXPECTED_ARG_COUNT, true); + } + + @Override + public Object evalBody(DataInstance model, EvaluationContext evalContext, Object[] evaluatedArgs) { + return checksum(evaluatedArgs[0], evaluatedArgs[1]); + } + + /** + * @param o1 algorithm type used to calculate checksum. We only support 'verhoeff' for now. + * @param o2 input we are calculating checksum for + * @return checksum of {@code o2} calculated using {@code o1} type algorithm + */ + private static String checksum(Object o1, Object o2) { + String algorithmKey = FunctionUtils.toString(o1); + String input = FunctionUtils.toString(o2); + + switch (algorithmKey) { + case ALGORITHM_KEY_VERHOEFF: + return verhoeffChecksum(input); + default: + throw new XPathUnsupportedException("Bad algorithm key " + algorithmKey + ". We only support 'verhoeff' as algorithm key right now."); + } + } + + /** + * Calculates Verhoeff checksum for the given {@code input} string

+ * + * @param input input string to calculate verhoeff checksum for + * @return Verhoeff checksum value for {@code input} + * @see Based on Verhoeff checksum implementation here + */ + private static String verhoeffChecksum(String input) { + + // The multiplication table + int[][] op = new int[][]{ + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, + {1, 2, 3, 4, 0, 6, 7, 8, 9, 5}, + {2, 3, 4, 0, 1, 7, 8, 9, 5, 6}, + {3, 4, 0, 1, 2, 8, 9, 5, 6, 7}, + {4, 0, 1, 2, 3, 9, 5, 6, 7, 8}, + {5, 9, 8, 7, 6, 0, 4, 3, 2, 1}, + {6, 5, 9, 8, 7, 1, 0, 4, 3, 2}, + {7, 6, 5, 9, 8, 2, 1, 0, 4, 3}, + {8, 7, 6, 5, 9, 3, 2, 1, 0, 4}, + {9, 8, 7, 6, 5, 4, 3, 2, 1, 0} + }; + + // The permutation table + int[][] p = new int[][]{ + {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, + {1, 5, 7, 6, 2, 8, 3, 0, 9, 4}, + {5, 8, 0, 3, 7, 9, 6, 1, 4, 2}, + {8, 9, 1, 6, 0, 4, 3, 5, 2, 7}, + {9, 4, 5, 3, 1, 2, 6, 8, 7, 0}, + {4, 2, 8, 6, 5, 7, 3, 9, 0, 1}, + {2, 7, 9, 3, 8, 0, 6, 4, 1, 5}, + {7, 0, 4, 6, 9, 1, 3, 2, 5, 8} + }; + + // The inverse table + int[] inv = {0, 4, 3, 2, 1, 5, 6, 7, 8, 9}; + + ArrayList inputList = StringUtils.toList(input); + Collections.reverse(inputList); + + int check = 0; + for (int i = 0; i < inputList.size(); i++) { + check = op[check][p[((i + 1) % 8)][Character.getNumericValue(inputList.get(i))]]; + } + + return Integer.toString(inv[check]); + } + +} diff --git a/src/main/java/org/javarosa/xpath/parser/ast/ASTNodeFunctionCall.java b/src/main/java/org/javarosa/xpath/parser/ast/ASTNodeFunctionCall.java index 7f0b5fff8e..a0177d2abf 100755 --- a/src/main/java/org/javarosa/xpath/parser/ast/ASTNodeFunctionCall.java +++ b/src/main/java/org/javarosa/xpath/parser/ast/ASTNodeFunctionCall.java @@ -163,6 +163,8 @@ private static XPathFuncExpr buildFuncExpr(String name, XPathExpression[] args) return new XPathJoinChunkFunc(args); case "id-compress": return new XPathIdCompressFunc(args); + case "checksum": + return new XPathChecksumFunc(args); case "sort": return new XPathSortFunc(args); case "sort-by": diff --git a/src/test/java/org/javarosa/xpath/test/XPathEvalTest.java b/src/test/java/org/javarosa/xpath/test/XPathEvalTest.java index f74ca125a8..fcc8ff540c 100755 --- a/src/test/java/org/javarosa/xpath/test/XPathEvalTest.java +++ b/src/test/java/org/javarosa/xpath/test/XPathEvalTest.java @@ -87,7 +87,7 @@ private void assertExceptionExpected(boolean exceptionExpected, Object expected, } @Test - public void testTypeCoercion(){ + public void testTypeCoercion() { Object str = FunctionUtils.InferType("notadouble"); Assert.assertTrue("'notadouble' coerced to the wrong type, " + str.getClass().toString(), str instanceof String); @@ -541,6 +541,10 @@ public void doTests() { testEval("id-compress(0, 'CD','','ABCDE',1)", null, ec, new XPathException()); testEval("id-compress(0, 'CD','CD','ABCDE',1)", null, ec, new XPathException()); + testEval("checksum('verhoeff','41310785898')", null, null, "4"); + testEval("checksum('verhoeff','66671496237')", null, null, "3"); + testEval("checksum('verhoefffff','41310785898')", null, null, new XPathUnsupportedException()); + //Variables EvaluationContext varContext = getVariableContext(); testEval("$var_float_five", null, varContext, new Double(5.0)); @@ -643,24 +647,24 @@ public void testOverrideNow() { ec.addFunctionHandler(new IFunctionHandler() { @Override public String getName() { - return "now"; + return "now"; } @Override public Vector getPrototypes() { - Vector p = new Vector<>(); - p.addElement(new Class[0]); - return p; + Vector p = new Vector<>(); + p.addElement(new Class[0]); + return p; } @Override public boolean rawArgs() { - return false; + return false; } @Override public Object eval(Object[] args, EvaluationContext ec) { - return "pass"; + return "pass"; } });