Skip to content

Method: parseQuery(String)

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