Github user GregAlbiston commented on a diff in the pull request:

    https://github.com/apache/jena/pull/449#discussion_r207248104
  
    --- Diff: 
jena-arq/src/main/java/org/apache/jena/query/ParameterizedSparqlString.java ---
    @@ -1734,4 +1739,250 @@ public String toString() {
             }
     
         }
    +    
    +    /**
    +     * Assign a VALUES varName with a multiple items.<br>
    +     * Can be used to assign multiple values to a single variable or single
    +     * value to multiple variables (if using a List) in the SPARQL 
query.<br>
    +     * See setGroupedValues to assign multiple values to multiple 
variables.<br>
    +     * Using "var" with list(prop_A, obj_A) on query "VALUES (?p ?o) 
{?var}"
    +     * would produce "VALUES (?p ?o) {(prop_A obj_A)}".
    +     *
    +     *
    +     * @param varName
    +     * @param items
    +     */
    +    public void setValues(String varName, Collection<? extends RDFNode> 
items) {
    +        items.forEach(item -> validateParameterValue(item.asNode()));
    +        this.valuesReplacements.put(varName, new ValueReplacement(varName, 
items));
    +    }
    +
    +    /**
    +     * Assign a VALUES varName with a single item.<br>
    +     * Using "var" with Literal obj_A on query "VALUES ?o {?var}" would 
produce
    +     * "VALUES ?o {obj_A}".
    +     *
    +     * @param varName
    +     * @param item
    +     */
    +    public void setValues(String varName, RDFNode item) {
    +        setValues(varName, Arrays.asList(item));
    +    }
    +
    +    /**
    +     * **
    +     * Sets a map of VALUES varNames and their items.<br>
    +     * Can be used to assign multiple values to a single variable or single
    +     * value to multiple variables (if using a List) in the SPARQL 
query.<br>
    +     * See setGroupedValues to assign multiple values to multiple 
variables.
    +     *
    +     * @param itemsMap
    +     */
    +    public void setValues(Map<String, Collection<? extends RDFNode>> 
itemsMap) {
    +        itemsMap.forEach(this::setValues);
    +    }
    +
    +    /**
    +     * Allocate multiple lists of variables to a single VALUES varName.<br>
    +     * Using "vars" with list(list(prop_A, obj_A), list(prop_B, obj_B)) on 
query
    +     * "VALUES (?p ?o) {?vars}" would produce "VALUES (?p ?o) {(prop_A 
obj_A)
    +     * (prop_B obj_B)}".
    +     *
    +     * @param varName
    +     * @param groupedItems
    +     */
    +    public void setGroupedValues(String varName, Collection<List<? extends 
RDFNode>> groupedItems) {
    +        groupedItems.forEach(collection -> collection.forEach(item -> 
validateParameterValue(item.asNode())));
    +        this.valuesReplacements.put(varName, new ValueReplacement(varName, 
groupedItems, true));
    +    }
    +
    +    private String applyValues(String command) {
    +
    +        for (ValueReplacement valueReplacement : 
valuesReplacements.values()) {
    +            command = valueReplacement.apply(command);
    +        }
    +        return command;
    +    }
    +
    +    private static final String VALUES_KEYWORD = "values";
    +
    +    protected static String[] extractTargetVars(String command, String 
varName) {
    +        String[] targetVars;
    +
    +        int varIndex = command.indexOf(varName);
    +        if (varIndex > -1) {
    +            String subCmd = command.substring(0, varIndex).toLowerCase(); 
//Truncate the command at the varName. Lowercase to search both types of values.
    +            int valuesIndex = subCmd.lastIndexOf(VALUES_KEYWORD);
    +            int bracesIndex = subCmd.lastIndexOf("{");
    +            String vars = command.substring(valuesIndex + 
VALUES_KEYWORD.length(), bracesIndex);
    +            targetVars = vars.replaceAll("[(?)]", "").trim().split(" ");
    +        } else {
    +            targetVars = new String[]{};
    +        }
    +        return targetVars;
    +    }
    +
    +    protected static boolean checkParenthesis(String command, String 
varName) {
    +        boolean isNeeded;
    +
    +        int varIndex = command.indexOf(varName);
    +        if (varIndex > -1) {
    +            String subCmd = command.substring(0, varIndex).toLowerCase(); 
//Truncate the command at the varName. Lowercase to search both types of values.
    +            int valuesIndex = subCmd.lastIndexOf(VALUES_KEYWORD);
    +            int parenthesisIndex = subCmd.indexOf("(", valuesIndex + 
VALUES_KEYWORD.length());
    +            isNeeded = parenthesisIndex > -1;
    +        } else {
    +            isNeeded = false;
    +        }
    +        return isNeeded;
    +    }
    +
    +    /**
    +     * Performs replacement of VALUES in query string.
    +     *
    +     */
    +    private class ValueReplacement {
    +
    +        private final String varName;
    +        private final Collection<? extends RDFNode> items;
    +        private final Collection<List<? extends RDFNode>> groupedItems;
    +        private final Boolean isGrouped;
    +
    +        public ValueReplacement(String varName, Collection<? extends 
RDFNode> items) {
    +            this.varName = varName;
    +            this.items = items;
    +            this.groupedItems = new ArrayList<>();
    +            this.isGrouped = false;
    +        }
    +
    +        public ValueReplacement(String varName, Collection<List<? extends 
RDFNode>> groupedItems, Boolean isGrouped) {
    +            this.varName = varName;
    +            this.items = new ArrayList<>();
    +            this.groupedItems = groupedItems;
    +            this.isGrouped = isGrouped;
    +        }
    +
    +        public String apply(String command) {
    +
    +            if (items.isEmpty() && groupedItems.isEmpty()) {
    +                return command;
    +            }
    +
    +            String[] targetVars = extractTargetVars(command, varName);
    +            validateValuesSafeToInject(command, targetVars);
    +
    +            String target = createTarget();
    +
    +            StringBuilder replacement;
    +            if (isGrouped) {
    +                replacement = groupedApply();
    +            } else {
    +
    +                replacement = ungroupedApply(command, targetVars.length);
    +            }
    +
    +            return command.replace(target, replacement);
    +        }
    +
    +        private StringBuilder groupedApply() {
    +            StringBuilder replacement = new StringBuilder("");
    +
    +            for (List<? extends RDFNode> group : groupedItems) {
    +                replacement.append("(");
    +
    +                for (RDFNode item : group) {
    +                    String insert = FmtUtils.stringForNode(item.asNode(), 
(PrefixMapping) null);
    +                    replacement.append(insert);
    +                    replacement.append(" ");
    +                }
    +
    +                replacement.deleteCharAt(replacement.length() - 1);
    +                replacement.append(") ");
    +            }
    +
    +            replacement.deleteCharAt(replacement.length() - 1);
    +            return replacement;
    +        }
    +
    +        private StringBuilder ungroupedApply(String command, int 
targetVarCount) {
    +
    +            StringBuilder replacement = new StringBuilder("");
    +
    +            if (targetVarCount == 1) {
    +                boolean isParenthesisNeeded = checkParenthesis(command, 
varName);
    +                for (RDFNode item : items) {
    +                    if (isParenthesisNeeded) {
    +                        replacement.append("(");
    +                    }
    +                    String insert = FmtUtils.stringForNode(item.asNode(), 
(PrefixMapping) null);
    +                    replacement.append(insert);
    +                    if (isParenthesisNeeded) {
    +                        replacement.append(")");
    +                    }
    +                    replacement.append(" ");
    +                }
    +                replacement.deleteCharAt(replacement.length() - 1);
    +            } else {
    +                replacement.append("(");
    +                for (RDFNode item : items) {
    +                    String insert = FmtUtils.stringForNode(item.asNode(), 
(PrefixMapping) null);
    +                    replacement.append(insert);
    +                    replacement.append(" ");
    +                }
    +                replacement.deleteCharAt(replacement.length() - 1);
    +                replacement.append(")");
    +            }
    +
    +            return replacement;
    +        }
    +
    +        /**
    +         * Tidy up varName if doesn't start with a ? or $.
    +         *
    +         * @param varName
    +         * @return
    +         */
    +        private String createTarget() {
    +            String target;
    +
    +            if (varName.startsWith("?") || varName.startsWith("$")) {
    +                target = varName;
    +            } else {
    +                target = "?" + varName;
    +            }
    +            return target;
    +        }
    +
    +        protected void validateValuesSafeToInject(String command, String[] 
targetVars) {
    +
    +            for (int i = 0; i < targetVars.length; i++) {
    +                String targetVar = targetVars[i];
    +                if (isGrouped) {
    +                    //Iterate through each group according to the position 
of var and item.
    +                    for (List<? extends RDFNode> group : groupedItems) {
    +                        RDFNode item = group.get(i);
    +                        validateSafeToInject(command, targetVar, 
item.asNode());
    +                    }
    +                } else {
    +                    if (targetVars.length > 1) {
    +                        if (items instanceof List) {
    +                            //Multiple vars with items in an ordered list. 
Each var is checked against the item.
    +                            List<? extends RDFNode> listItems = (List<? 
extends RDFNode>) items;
    +                            RDFNode item = listItems.get(i);
    +                            validateSafeToInject(command, targetVar, 
item.asNode());
    +                        } else {
    +                            //Multiple vars with items not in an ordered 
list. This is parsing error.
    +                            throw new ARQException("Multiple VALUES 
variables (" + String.join(", ", targetVars) + ") being used without an ordered 
list of items: " + items.toString());
    --- End diff --
    
    This error isn't relevant anymore as the `List` is being enforced by the 
`setValues` method. A check is performed that the number of target vars equals 
the number of 'items' in a 'row' with exception thrown. The check is performed 
late as the 'command' is not final in the class and can be changed using 
`setCommandText`. Could be performed earlier but the existing approach is to 
wait until `toString` is called.


---

Reply via email to