Skip to content

Method: parseQuery(String)

1: /**
2: * Copyright (C) 2022 Czech Technical University in Prague
3: *
4: * This program is free software: you can redistribute it and/or modify it under
5: * the terms of the GNU General Public License as published by the Free Software
6: * Foundation, either version 3 of the License, or (at your option) any
7: * later version.
8: *
9: * This program is distributed in the hope that it will be useful, but WITHOUT
10: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11: * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12: * details. You should have received a copy of the GNU General Public License
13: * along with this program. If not, see <http://www.gnu.org/licenses/>.
14: */
15: package cz.cvut.kbss.jopa.query.sparql;
16:
17: import cz.cvut.kbss.jopa.exception.QueryParserException;
18: import cz.cvut.kbss.jopa.query.QueryParameter;
19: import cz.cvut.kbss.jopa.query.QueryParser;
20: import cz.cvut.kbss.jopa.query.parameter.ParameterValueFactory;
21:
22: import java.util.ArrayList;
23: import java.util.HashMap;
24: import java.util.List;
25: import java.util.Map;
26:
27: /**
28: * A simplified SPARQL query parser.
29: * <p>
30: * This implementation does not use any AST tree-based query string parsing as its only purpose is to split the query
31: * into chunks delimited by variable occurrences, so that the variables can be bound using parameters in the query API.
32: * <p>
33: * More diligent query parsing is left to the engine used to execute the resulting query.
34: */
35: public class SparqlQueryParser implements QueryParser {
36:
37: private static final String SELECT = "SELECT";
38: private static final String WHERE = "WHERE";
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: 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: case '|':
119:• if (inParam) {
120: parameterEnd(i);
121: }
122: wordEnd();
123: break;
124: default:
125: currentWord.append(c);
126: break;
127: }
128: }
129:• if (inParam) {
130: parameterEnd(i);
131: } else {
132: queryParts.add(query.substring(lastParamEndIndex));
133: }
134: return new SparqlQueryHolder(query, queryParts, parameters);
135: }
136:
137: private void parameterStart(int index, ParamType paramType) {
138: if (!inSQString && !inDQString) {
139: queryParts.add(query.substring(lastParamEndIndex, index));
140: paramStartIndex = index + 1;
141: inParam = true;
142: this.currentParamType = paramType;
143: }
144: }
145:
146: private void parameterEnd(int index) {
147: this.lastParamEndIndex = index;
148: this.inParam = false;
149: final String param = query.substring(paramStartIndex, index);
150: parameters.add(resolveParamIdentification(param));
151: }
152:
153: private QueryParameter<?> resolveParamIdentification(String identification) {
154: final QueryParameter<?> queryParameter;
155: if (identification.isEmpty()) {
156: if (currentParamType == ParamType.POSITIONAL) {
157: queryParameter = getQueryParameter(positionalCounter++);
158: } else {
159: throw new QueryParserException("Missing parameter name in query " + query);
160: }
161: } else {
162: if (currentParamType == ParamType.POSITIONAL) {
163: try {
164: Integer position = Integer.parseInt(identification);
165: positionalCounter++;
166: queryParameter = getQueryParameter(position);
167: } catch (NumberFormatException e) {
168: throw new QueryParserException(identification + " is not a valid parameter position.", e);
169: }
170: } else {
171: queryParameter = getQueryParameter(identification);
172: }
173: }
174: return queryParameter;
175: }
176:
177: private QueryParameter<?> getQueryParameter(String name) {
178: // We want to reuse the param instances, so that changes to them apply throughout the whole query
179: if (!uniqueParams.containsKey(name)) {
180: final QueryParameter<?> qp = new QueryParameter<>(name, parameterValueFactory);
181: qp.setProjected(inProjection);
182: uniqueParams.put(name, qp);
183: }
184: return uniqueParams.get(name);
185: }
186:
187: private QueryParameter<?> getQueryParameter(Integer position) {
188: if (uniqueParams.containsKey(position)) {
189: throw new QueryParserException("Parameter with position " + position + " already found in query " + query);
190: }
191: final QueryParameter<?> qp = new QueryParameter<>(position, parameterValueFactory);
192: uniqueParams.put(position, qp);
193: return qp;
194: }
195:
196: private void wordEnd() {
197: if (SELECT.equalsIgnoreCase(currentWord.toString())) {
198: this.inProjection = true;
199: } else if (inProjection && WHERE.equalsIgnoreCase(currentWord.toString())) {
200: this.inProjection = false;
201: }
202: currentWord = new StringBuilder();
203: }
204: }