Skip to content

Package: SparqlQueryParser

SparqlQueryParser

nameinstructionbranchcomplexitylinemethod
SparqlQueryParser(ParameterValueFactory)
M: 0 C: 6
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 3
100%
M: 0 C: 1
100%
getQueryParameter(Integer)
M: 0 C: 28
100%
M: 0 C: 2
100%
M: 0 C: 2
100%
M: 0 C: 5
100%
M: 0 C: 1
100%
getQueryParameter(String)
M: 0 C: 28
100%
M: 0 C: 2
100%
M: 0 C: 2
100%
M: 0 C: 5
100%
M: 0 C: 1
100%
parameterEnd(int)
M: 0 C: 21
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 5
100%
M: 0 C: 1
100%
parameterStart(int, SparqlQueryParser.ParamType)
M: 0 C: 28
100%
M: 2 C: 2
50%
M: 2 C: 1
33%
M: 0 C: 6
100%
M: 0 C: 1
100%
parseQuery(String)
M: 8 C: 120
94%
M: 5 C: 24
83%
M: 4 C: 16
80%
M: 1 C: 33
97%
M: 0 C: 1
100%
queryVariableStart(int)
M: 0 C: 15
100%
M: 0 C: 4
100%
M: 0 C: 3
100%
M: 0 C: 5
100%
M: 0 C: 1
100%
resetParser()
M: 0 C: 49
100%
M: 0 C: 0
100%
M: 0 C: 1
100%
M: 0 C: 14
100%
M: 0 C: 1
100%
resolveParamIdentification(String)
M: 0 C: 70
100%
M: 0 C: 6
100%
M: 0 C: 4
100%
M: 0 C: 13
100%
M: 0 C: 1
100%
startComment(int, char)
M: 0 C: 22
100%
M: 1 C: 5
83%
M: 1 C: 3
75%
M: 0 C: 6
100%
M: 0 C: 1
100%
wordEnd()
M: 0 C: 28
100%
M: 0 C: 6
100%
M: 0 C: 4
100%
M: 0 C: 6
100%
M: 0 C: 1
100%

Coverage

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: private boolean inComment;
58: private boolean inUri;
59:
60: public SparqlQueryParser(ParameterValueFactory parameterValueFactory) {
61: this.parameterValueFactory = parameterValueFactory;
62: }
63:
64: private enum ParamType {
65: POSITIONAL, NAMED
66: }
67:
68:
69: @Override
70: public SparqlQueryHolder parseQuery(String query) {
71: this.query = query;
72: resetParser();
73: int i;
74:• for (i = 0; i < query.length(); i++) {
75: final char c = query.charAt(i);
76:• switch (c) {
77: case '#':
78: startComment(i, c);
79: break;
80: case '\'':
81:• this.inSQString = inComment == inSQString; // ~ inComment ? inSQString : !inSQString
82: break;
83: case '"':
84:• this.inDQString = inComment == inDQString; // ~ inComment ? inDQString : !inDQString
85: break;
86: case '$':
87:• if (!inComment) {
88: parameterStart(i, ParamType.POSITIONAL);
89: }
90: break;
91: case '?':
92: queryVariableStart(i);
93: break;
94: case '<':
95: this.inUri = true;
96:• if (inParam) {
97: parameterEnd(i);
98: }
99: wordEnd();
100: break;
101: case '>':
102: this.inUri = false;
103: wordEnd();
104: break;
105: case '\n':
106: this.inComment = false; // Intentional fall-through
107: case '{':
108: // ~ inComment ? inProjection : !inProjection
109:• this.inProjection = inComment && inProjection; // Intentional fall-through
110: case '\r':
111: case ',':
112: case ')':
113: case ' ':
114: case '.':
115: case ';':
116: case '}':
117: case '[':
118: case ']':
119: case '+':
120: case '*':
121: case '/':
122: case '|':
123:• if (inParam) {
124: parameterEnd(i);
125: }
126: wordEnd();
127: break;
128: default:
129: currentWord.append(c);
130: break;
131: }
132: }
133:• if (inParam) {
134: parameterEnd(i);
135: } else {
136: queryParts.add(query.substring(lastParamEndIndex));
137: }
138: return new SparqlQueryHolder(query, queryParts, parameters);
139: }
140:
141: private void resetParser() {
142: this.queryParts = new ArrayList<>();
143: this.uniqueParams = new HashMap<>();
144: this.positionalCounter = 1;
145: this.parameters = new ArrayList<>();
146: this.inSQString = false;
147: // In double-quoted string
148: this.inDQString = false;
149: this.inParam = false;
150: this.inUri = false;
151: this.inComment = false;
152: this.lastParamEndIndex = 0;
153: this.paramStartIndex = 0;
154: this.currentParamType = null;
155: this.currentWord = new StringBuilder();
156: }
157:
158: private void startComment(int index, char c) {
159:• if (!inUri) {
160:• if (inParam && !inComment) {
161: parameterEnd(index);
162: }
163: this.inComment = true;
164: } else {
165: currentWord.append(c);
166: }
167: }
168:
169: private void queryVariableStart(int i) {
170:• if (!inComment) {
171:• if (inParam) {
172: parameterEnd(i); // Property path zero or one
173: } else {
174: parameterStart(i, ParamType.NAMED);
175: }
176: }
177: }
178:
179: private void parameterStart(int index, ParamType paramType) {
180:• if (!inSQString && !inDQString) {
181: queryParts.add(query.substring(lastParamEndIndex, index));
182: this.paramStartIndex = index + 1;
183: this.inParam = true;
184: this.currentParamType = paramType;
185: }
186: }
187:
188: private void parameterEnd(int index) {
189: this.lastParamEndIndex = index;
190: this.inParam = false;
191: final String param = query.substring(paramStartIndex, index);
192: parameters.add(resolveParamIdentification(param));
193: }
194:
195: private QueryParameter<?> resolveParamIdentification(String identification) {
196: final QueryParameter<?> queryParameter;
197:• if (identification.isEmpty()) {
198:• if (currentParamType == ParamType.POSITIONAL) {
199: queryParameter = getQueryParameter(positionalCounter++);
200: } else {
201: throw new QueryParserException("Missing parameter name in query " + query);
202: }
203: } else {
204:• if (currentParamType == ParamType.POSITIONAL) {
205: try {
206: Integer position = Integer.parseInt(identification);
207: positionalCounter++;
208: queryParameter = getQueryParameter(position);
209: } catch (NumberFormatException e) {
210: throw new QueryParserException(identification + " is not a valid parameter position.", e);
211: }
212: } else {
213: queryParameter = getQueryParameter(identification);
214: }
215: }
216: return queryParameter;
217: }
218:
219: private QueryParameter<?> getQueryParameter(String name) {
220: // We want to reuse the param instances, so that changes to them apply throughout the whole query
221:• if (!uniqueParams.containsKey(name)) {
222: final QueryParameter<?> qp = new QueryParameter<>(name, parameterValueFactory);
223: qp.setProjected(inProjection);
224: uniqueParams.put(name, qp);
225: }
226: return uniqueParams.get(name);
227: }
228:
229: private QueryParameter<?> getQueryParameter(Integer position) {
230:• if (uniqueParams.containsKey(position)) {
231: throw new QueryParserException("Parameter with position " + position + " already found in query " + query);
232: }
233: final QueryParameter<?> qp = new QueryParameter<>(position, parameterValueFactory);
234: uniqueParams.put(position, qp);
235: return qp;
236: }
237:
238: private void wordEnd() {
239:• if (SparqlConstants.SELECT.equalsIgnoreCase(currentWord.toString())) {
240: this.inProjection = true;
241:• } else if (inProjection && SparqlConstants.WHERE.equalsIgnoreCase(currentWord.toString())) {
242: this.inProjection = false;
243: }
244: this.currentWord = new StringBuilder();
245: }
246: }