Skip to content

Method: resolveParamIdentification(String)

1: /*
2: * JOPA
3: * Copyright (C) 2024 Czech Technical University in Prague
4: *
5: * This library is free software; you can redistribute it and/or
6: * modify it under the terms of the GNU Lesser General Public
7: * License as published by the Free Software Foundation; either
8: * version 3.0 of the License, or (at your option) any later version.
9: *
10: * This library is distributed in the hope that it will be useful,
11: * but WITHOUT ANY WARRANTY; without even the implied warranty of
12: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13: * Lesser General Public License for more details.
14: *
15: * You should have received a copy of the GNU Lesser General Public
16: * License along with this library.
17: */
18: package cz.cvut.kbss.jopa.query.sparql;
19:
20: import cz.cvut.kbss.jopa.exception.QueryParserException;
21: import cz.cvut.kbss.jopa.query.QueryParameter;
22: import cz.cvut.kbss.jopa.query.QueryParser;
23: import cz.cvut.kbss.jopa.query.parameter.ParameterValueFactory;
24:
25: import java.util.ArrayList;
26: import java.util.HashMap;
27: import java.util.List;
28: import java.util.Map;
29:
30: /**
31: * A simplified SPARQL query parser.
32: * <p>
33: * This implementation does not use any AST tree-based query string parsing as its only purpose is to split the query
34: * into chunks delimited by variable occurrences, so that the variables can be bound using parameters in the query API.
35: * <p>
36: * More diligent query parsing is left to the engine used to execute the resulting query.
37: */
38: public class SparqlQueryParser implements QueryParser {
39:
40: private final ParameterValueFactory parameterValueFactory;
41:
42: private String query;
43:
44: private Map<Object, QueryParameter<?>> uniqueParams;
45: private Integer positionalCounter;
46:
47: private List<String> queryParts;
48: private List<QueryParameter<?>> parameters;
49: private boolean inParam;
50: private boolean inSQString; // In apostrophe string (')
51: private boolean inDQString; // In double-quoted string (")
52: private int lastParamEndIndex;
53: private int paramStartIndex;
54: private ParamType currentParamType;
55: private StringBuilder currentWord;
56: private boolean inProjection;
57:
58: public SparqlQueryParser(ParameterValueFactory parameterValueFactory) {
59: this.parameterValueFactory = parameterValueFactory;
60: }
61:
62: private enum ParamType {
63: POSITIONAL, NAMED
64: }
65:
66:
67: @Override
68: public SparqlQueryHolder parseQuery(String query) {
69: this.query = query;
70: this.queryParts = new ArrayList<>();
71: this.uniqueParams = new HashMap<>();
72: this.positionalCounter = 1;
73: this.parameters = new ArrayList<>();
74: this.inSQString = false;
75: // In double-quoted string
76: this.inDQString = false;
77: this.inParam = false;
78: this.lastParamEndIndex = 0;
79: this.paramStartIndex = 0;
80: this.currentParamType = null;
81: this.currentWord = new StringBuilder();
82: int i;
83: for (i = 0; i < query.length(); i++) {
84: final char c = query.charAt(i);
85: switch (c) {
86: case '\'':
87: inSQString = !inSQString;
88: break;
89: case '"':
90: inDQString = !inDQString;
91: break;
92: case '$':
93: parameterStart(i, ParamType.POSITIONAL);
94: break;
95: case '?':
96: if (inParam) {
97: parameterEnd(i); // Property path zero or one
98: } else {
99: parameterStart(i, ParamType.NAMED);
100: }
101: break;
102: case '{':
103: this.inProjection = false; // Intentional fall-through
104: case '<':
105: case '>':
106: case ',':
107: case '\n':
108: case '\r':
109: case ')':
110: case ' ':
111: case '.':
112: case ';':
113: case '}':
114: case '[':
115: case ']':
116: case '+':
117: case '*':
118: case '/':
119: case '|':
120: if (inParam) {
121: parameterEnd(i);
122: }
123: wordEnd();
124: break;
125: default:
126: currentWord.append(c);
127: break;
128: }
129: }
130: if (inParam) {
131: parameterEnd(i);
132: } else {
133: queryParts.add(query.substring(lastParamEndIndex));
134: }
135: return new SparqlQueryHolder(query, queryParts, parameters);
136: }
137:
138: private void parameterStart(int index, ParamType paramType) {
139: if (!inSQString && !inDQString) {
140: queryParts.add(query.substring(lastParamEndIndex, index));
141: paramStartIndex = index + 1;
142: inParam = true;
143: this.currentParamType = paramType;
144: }
145: }
146:
147: private void parameterEnd(int index) {
148: this.lastParamEndIndex = index;
149: this.inParam = false;
150: final String param = query.substring(paramStartIndex, index);
151: parameters.add(resolveParamIdentification(param));
152: }
153:
154: private QueryParameter<?> resolveParamIdentification(String identification) {
155: final QueryParameter<?> queryParameter;
156:• if (identification.isEmpty()) {
157:• if (currentParamType == ParamType.POSITIONAL) {
158: queryParameter = getQueryParameter(positionalCounter++);
159: } else {
160: throw new QueryParserException("Missing parameter name in query " + query);
161: }
162: } else {
163:• if (currentParamType == ParamType.POSITIONAL) {
164: try {
165: Integer position = Integer.parseInt(identification);
166: positionalCounter++;
167: queryParameter = getQueryParameter(position);
168: } catch (NumberFormatException e) {
169: throw new QueryParserException(identification + " is not a valid parameter position.", e);
170: }
171: } else {
172: queryParameter = getQueryParameter(identification);
173: }
174: }
175: return queryParameter;
176: }
177:
178: private QueryParameter<?> getQueryParameter(String name) {
179: // We want to reuse the param instances, so that changes to them apply throughout the whole query
180: if (!uniqueParams.containsKey(name)) {
181: final QueryParameter<?> qp = new QueryParameter<>(name, parameterValueFactory);
182: qp.setProjected(inProjection);
183: uniqueParams.put(name, qp);
184: }
185: return uniqueParams.get(name);
186: }
187:
188: private QueryParameter<?> getQueryParameter(Integer position) {
189: if (uniqueParams.containsKey(position)) {
190: throw new QueryParserException("Parameter with position " + position + " already found in query " + query);
191: }
192: final QueryParameter<?> qp = new QueryParameter<>(position, parameterValueFactory);
193: uniqueParams.put(position, qp);
194: return qp;
195: }
196:
197: private void wordEnd() {
198: if (SparqlConstants.SELECT.equalsIgnoreCase(currentWord.toString())) {
199: this.inProjection = true;
200: } else if (inProjection && SparqlConstants.WHERE.equalsIgnoreCase(currentWord.toString())) {
201: this.inProjection = false;
202: }
203: currentWord = new StringBuilder();
204: }
205: }