1 /*
2 * JasperReports - Free Java Reporting Library.
3 * Copyright (C) 2001 - 2019 TIBCO Software Inc. All rights reserved.
4 * http://www.jaspersoft.com
5 *
6 * Unless you have purchased a commercial license agreement from Jaspersoft,
7 * the following license terms apply:
8 *
9 * This program is part of JasperReports.
10 *
11 * JasperReports is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * JasperReports is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU Lesser General Public License for more details.
20 *
21 * You should have received a copy of the GNU Lesser General Public License
22 * along with JasperReports. If not, see <http://www.gnu.org/licenses/>.
23 */
24 package net.sf.jasperreports.engine.util;
25
26 import java.util.ArrayList;
27 import java.util.List;
28 import java.util.StringTokenizer;
29
30 import net.sf.jasperreports.engine.DefaultJasperReportsContext;
31 import net.sf.jasperreports.engine.JRPropertiesUtil;
32 import net.sf.jasperreports.engine.JRQueryChunk;
33 import net.sf.jasperreports.engine.JRRuntimeException;
34
35
36 /**
37 * Report query parser.
38 *
39 * @author Lucian Chirita (lucianc@users.sourceforge.net)
40 */
41 public class JRQueryParser
42 {
43 public static final String EXCEPTION_MESSAGE_KEY_TOKEN_SEPARATORS_NOT_CONFIGURED = "util.query.token.separators.not.configured";
44
45 private static final JRQueryParser singleton = new JRQueryParser();
46
47 /**
48 * Returns a query parser instance.
49 *
50 * @return a query parser instance
51 */
52 public static JRQueryParser instance()
53 {
54 return singleton;
55 }
56
57
58 /**
59 * Parses a report query.
60 *
61 * @param text the query text
62 * @param chunkHandler a handler that will be asked to handle parsed query chunks
63 */
64 public void parse(String text, JRQueryChunkHandler chunkHandler)
65 {
66 if (text != null)
67 {
68 StringBuilder textChunk = new StringBuilder();
69
70 StringTokenizer tkzer = new StringTokenizer(text, "$", true);
71 boolean wasDelim = false;
72 while (tkzer.hasMoreTokens())
73 {
74 String token = tkzer.nextToken();
75
76 if (token.equals("$"))
77 {
78 if (wasDelim)
79 {
80 textChunk.append("$");
81 }
82
83 wasDelim = true;
84 }
85 else
86 {
87 if ( token.startsWith("P{") && wasDelim )
88 {
89 int end = token.indexOf('}');
90 if (end > 0)
91 {
92 if (textChunk.length() > 0)
93 {
94 chunkHandler.handleTextChunk(textChunk.toString());
95 }
96 String parameterChunk = token.substring(2, end);
97 chunkHandler.handleParameterChunk(parameterChunk);
98 textChunk = new StringBuilder(token.substring(end + 1));
99 }
100 else
101 {
102 if (wasDelim)
103 {
104 textChunk.append("$");
105 }
106 textChunk.append(token);
107 }
108 }
109 else if ( token.startsWith("P!{") && wasDelim )
110 {
111 int end = token.indexOf('}');
112 if (end > 0)
113 {
114 if (textChunk.length() > 0)
115 {
116 chunkHandler.handleTextChunk(textChunk.toString());
117 }
118 String parameterClauseChunk = token.substring(3, end);
119 chunkHandler.handleParameterClauseChunk(parameterClauseChunk);
120 textChunk = new StringBuilder(token.substring(end + 1));
121 }
122 else
123 {
124 if (wasDelim)
125 {
126 textChunk.append("$");
127 }
128 textChunk.append(token);
129 }
130 }
131 else if ( token.startsWith("X{") && wasDelim )
132 {
133 int end = token.indexOf('}');
134 if (end > 0)
135 {
136 if (textChunk.length() > 0)
137 {
138 chunkHandler.handleTextChunk(textChunk.toString());
139 }
140 String clauseChunk = token.substring(2, end);
141 parseClause(chunkHandler, clauseChunk);
142 textChunk = new StringBuilder(token.substring(end + 1));
143 }
144 else
145 {
146 if (wasDelim)
147 {
148 textChunk.append("$");
149 }
150 textChunk.append(token);
151 }
152 }
153 else
154 {
155 if (wasDelim)
156 {
157 textChunk.append("$");
158 }
159 textChunk.append(token);
160 }
161
162 wasDelim = false;
163 }
164 }
165 if (wasDelim)
166 {
167 textChunk.append("$");
168 }
169 if (textChunk.length() > 0)
170 {
171 chunkHandler.handleTextChunk(textChunk.toString());
172 }
173 }
174 }
175
176
177 protected void parseClause(JRQueryChunkHandler chunkHandler, String clauseChunk)
178 {
179 List<String> tokens = new ArrayList<String>();
180
181 boolean wasClauseToken = false;
182 char separator = determineClauseTokenSeparator(clauseChunk);
183 String separatorString = String.valueOf(separator);
184 StringTokenizer tokenizer = new StringTokenizer(clauseChunk, separatorString, true);
185 while (tokenizer.hasMoreTokens())
186 {
187 String token = tokenizer.nextToken();
188 if (token.equals(separatorString))
189 {
190 if (!wasClauseToken)
191 {
192 tokens.add("");
193 }
194 wasClauseToken = false;
195 }
196 else
197 {
198 tokens.add(token);
199 wasClauseToken = true;
200 }
201 }
202 if (!wasClauseToken)
203 {
204 tokens.add("");
205 }
206
207 String[] tokensArray = tokens.toArray(new String[tokens.size()]);
208 chunkHandler.handleClauseChunk(tokensArray, separator);
209 }
210
211 protected char determineClauseTokenSeparator(String clauseChunk)
212 {
213 String allSeparators = getTokenSeparators();
214 if (allSeparators == null || allSeparators.length() == 0)
215 {
216 throw
217 new JRRuntimeException(
218 EXCEPTION_MESSAGE_KEY_TOKEN_SEPARATORS_NOT_CONFIGURED,
219 (Object[])null);
220 }
221
222 int firstSepIdx = 0;//if none of the separators are found in the text, return the first separator
223 int clauseLenght = clauseChunk.length();
224 for (int idx = 0; idx < clauseLenght; ++idx)
225 {
226 int sepIdx = allSeparators.indexOf(clauseChunk.charAt(idx));
227 if (sepIdx >= 0)
228 {
229 firstSepIdx = sepIdx;
230 break;
231 }
232 }
233
234 return allSeparators.charAt(firstSepIdx);
235 }
236
237
238 protected String getTokenSeparators()
239 {
240 return JRPropertiesUtil.getInstance(DefaultJasperReportsContext.getInstance()).getProperty(JRQueryChunk.PROPERTY_CHUNK_TOKEN_SEPARATOR);
241 }
242
243 /**
244 * (Re)creates the query text from a list of chunks.
245 *
246 * @param chunks the chunks
247 * @return the recreated query text
248 */
249 public String asText(JRQueryChunk[] chunks)
250 {
251 String text = "";
252
253 if (chunks != null && chunks.length > 0)
254 {
255 StringBuilder sb = new StringBuilder();
256
257 for(int i = 0; i < chunks.length; i++)
258 {
259 JRQueryChunk queryChunk = chunks[i];
260 switch(queryChunk.getType())
261 {
262 case JRQueryChunk.TYPE_PARAMETER :
263 {
264 sb.append("$P{");
265 sb.append( queryChunk.getText() );
266 sb.append("}");
267 break;
268 }
269 case JRQueryChunk.TYPE_PARAMETER_CLAUSE :
270 {
271 sb.append("$P!{");
272 sb.append( queryChunk.getText() );
273 sb.append("}");
274 break;
275 }
276 case JRQueryChunk.TYPE_CLAUSE_TOKENS :
277 {
278 sb.append("$X{");
279 sb.append(queryChunk.getText());
280 sb.append("}");
281 break;
282 }
283 case JRQueryChunk.TYPE_TEXT :
284 default :
285 {
286 sb.append( queryChunk.getText() );
287 break;
288 }
289 }
290 }
291
292 text = sb.toString();
293 }
294
295 return text;
296 }
297
298 /**
299 * (Re)constructs a query clause chunk from the chunk tokens.
300 *
301 * @param tokens the chunk tokens
302 * @return the reconstructed query clause chunk
303 * @see JRQueryChunk#TYPE_CLAUSE_TOKENS
304 * @deprecated Replaced by {@link #asClauseText(String[], Character)}.
305 */
306 public String asClauseText(String[] tokens)
307 {
308 return asClauseText(tokens, null);
309 }
310
311 /**
312 * (Re)constructs a query clause chunk from the chunk tokens.
313 *
314 * @param tokens the chunk tokens
315 * @param separator the chunk tokens separator character
316 * @return the reconstructed query clause chunk
317 * @see JRQueryChunk#TYPE_CLAUSE_TOKENS
318 */
319 public String asClauseText(String[] tokens, Character separator)
320 {
321 if (separator == null)
322 {
323 separator = defaultTokenSeparator();
324 }
325
326 StringBuilder sb = new StringBuilder();
327 if (tokens != null && tokens.length > 0)
328 {
329 for (int i = 0; i < tokens.length; i++)
330 {
331 if (i > 0)
332 {
333 sb.append(separator);
334 }
335 String token = tokens[i];
336 if (token != null)
337 {
338 sb.append(token);
339 }
340 }
341 }
342 return sb.toString();
343 }
344
345 protected char defaultTokenSeparator()
346 {
347 String tokenSeparators = getTokenSeparators();
348 return tokenSeparators == null || tokenSeparators.isEmpty()
349 ? ',' // would not normally happen, determineClauseTokenSeparator throws exception
350 : tokenSeparators.charAt(0);
351 }
352
353 }
354