Skip to contentMethod: wordEnd()
1: /*
2: * Copyright (C) 2023 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 final ParameterValueFactory parameterValueFactory;
38:
39: private String query;
40:
41: private Map<Object, QueryParameter<?>> uniqueParams;
42: private Integer positionalCounter;
43:
44: private List<String> queryParts;
45: private List<QueryParameter<?>> parameters;
46: private boolean inParam;
47: private boolean inSQString; // In apostrophe string (')
48: private boolean inDQString; // In double-quoted string (")
49: private int lastParamEndIndex;
50: private int paramStartIndex;
51: private ParamType currentParamType;
52: private StringBuilder currentWord;
53: private boolean inProjection;
54:
55: public SparqlQueryParser(ParameterValueFactory parameterValueFactory) {
56: this.parameterValueFactory = parameterValueFactory;
57: }
58:
59: private enum ParamType {
60: POSITIONAL, NAMED
61: }
62:
63:
64: @Override
65: public SparqlQueryHolder parseQuery(String query) {
66: this.query = query;
67: this.queryParts = new ArrayList<>();
68: this.uniqueParams = new HashMap<>();
69: this.positionalCounter = 1;
70: this.parameters = new ArrayList<>();
71: this.inSQString = false;
72: // In double-quoted string
73: this.inDQString = false;
74: this.inParam = false;
75: this.lastParamEndIndex = 0;
76: this.paramStartIndex = 0;
77: this.currentParamType = null;
78: this.currentWord = new StringBuilder();
79: int i;
80: for (i = 0; i < query.length(); i++) {
81: final char c = query.charAt(i);
82: switch (c) {
83: case '\'':
84: inSQString = !inSQString;
85: break;
86: case '"':
87: inDQString = !inDQString;
88: break;
89: case '$':
90: parameterStart(i, ParamType.POSITIONAL);
91: break;
92: case '?':
93: if (inParam) {
94: parameterEnd(i); // Property path zero or one
95: } else {
96: parameterStart(i, ParamType.NAMED);
97: }
98: break;
99: case '{':
100: this.inProjection = false; // Intentional fall-through
101: case '<':
102: case '>':
103: case ',':
104: case '\n':
105: case '\r':
106: case ')':
107: case ' ':
108: case '.':
109: case ';':
110: case '}':
111: case '[':
112: case ']':
113: case '+':
114: case '*':
115: case '/':
116: case '|':
117: if (inParam) {
118: parameterEnd(i);
119: }
120: wordEnd();
121: break;
122: default:
123: currentWord.append(c);
124: break;
125: }
126: }
127: if (inParam) {
128: parameterEnd(i);
129: } else {
130: queryParts.add(query.substring(lastParamEndIndex));
131: }
132: return new SparqlQueryHolder(query, queryParts, parameters);
133: }
134:
135: private void parameterStart(int index, ParamType paramType) {
136: if (!inSQString && !inDQString) {
137: queryParts.add(query.substring(lastParamEndIndex, index));
138: paramStartIndex = index + 1;
139: inParam = true;
140: this.currentParamType = paramType;
141: }
142: }
143:
144: private void parameterEnd(int index) {
145: this.lastParamEndIndex = index;
146: this.inParam = false;
147: final String param = query.substring(paramStartIndex, index);
148: parameters.add(resolveParamIdentification(param));
149: }
150:
151: private QueryParameter<?> resolveParamIdentification(String identification) {
152: final QueryParameter<?> queryParameter;
153: if (identification.isEmpty()) {
154: if (currentParamType == ParamType.POSITIONAL) {
155: queryParameter = getQueryParameter(positionalCounter++);
156: } else {
157: throw new QueryParserException("Missing parameter name in query " + query);
158: }
159: } else {
160: if (currentParamType == ParamType.POSITIONAL) {
161: try {
162: Integer position = Integer.parseInt(identification);
163: positionalCounter++;
164: queryParameter = getQueryParameter(position);
165: } catch (NumberFormatException e) {
166: throw new QueryParserException(identification + " is not a valid parameter position.", e);
167: }
168: } else {
169: queryParameter = getQueryParameter(identification);
170: }
171: }
172: return queryParameter;
173: }
174:
175: private QueryParameter<?> getQueryParameter(String name) {
176: // We want to reuse the param instances, so that changes to them apply throughout the whole query
177: if (!uniqueParams.containsKey(name)) {
178: final QueryParameter<?> qp = new QueryParameter<>(name, parameterValueFactory);
179: qp.setProjected(inProjection);
180: uniqueParams.put(name, qp);
181: }
182: return uniqueParams.get(name);
183: }
184:
185: private QueryParameter<?> getQueryParameter(Integer position) {
186: if (uniqueParams.containsKey(position)) {
187: throw new QueryParserException("Parameter with position " + position + " already found in query " + query);
188: }
189: final QueryParameter<?> qp = new QueryParameter<>(position, parameterValueFactory);
190: uniqueParams.put(position, qp);
191: return qp;
192: }
193:
194: private void wordEnd() {
195:• if (SparqlConstants.SELECT.equalsIgnoreCase(currentWord.toString())) {
196: this.inProjection = true;
197:• } else if (inProjection && SparqlConstants.WHERE.equalsIgnoreCase(currentWord.toString())) {
198: this.inProjection = false;
199: }
200: currentWord = new StringBuilder();
201: }
202: }