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