1 /*
2 * Zemucan: A Syntax Assistant for DB2
3 * Copyright (C) 2009, 2010 Andres Gomez Casanova
4 *
5 * This file is part of Zemucan.
6 *
7 * Zemucan is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Lesser General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * Zemucan is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with this library; if not, see <http://www.gnu.org/licenses/>.
19 *
20 * Contact:
21 * a n g o c a at y a h o o dot c o m
22 * Cra. 45 No 61 - 31, Bogota, Colombia.
23 *
24 * Author: $LastChangedBy: angoca $:
25 * Date: $LastChangedDate: 2011-03-06 22:24:44 -0500 (dom, 06 mar 2011) $:
26 * Revision: $LastChangedRevision: 1915 $:
27 * URL: $HeadURL: https://zemucan.svn.sourceforge.net/svnroot/zemucan/branches/zemucan_v1/source-code/analyzers/src/main/java/name/angoca/zemucan/core/syntactic/impl/ImplementationSyntacticAnalyzer.java $:
28 */
29 package name.angoca.zemucan.core.syntactic.impl;
30
31 import java.util.ArrayList;
32 import java.util.Iterator;
33 import java.util.List;
34
35 import name.angoca.zemucan.AbstractZemucanException;
36 import name.angoca.zemucan.core.graph.model.AbstractGraphNode;
37 import name.angoca.zemucan.core.graph.model.GraphNode;
38 import name.angoca.zemucan.core.graph.model.NonReservedGraphNode;
39 import name.angoca.zemucan.core.graph.model.StartingNode;
40 import name.angoca.zemucan.core.graph.model.TextualGraphNode;
41 import name.angoca.zemucan.core.lexical.impl.InvalidTokenException;
42 import name.angoca.zemucan.core.lexical.model.Token;
43 import name.angoca.zemucan.core.syntactic.api.AbstractSyntacticAnalyzer;
44 import name.angoca.zemucan.core.syntactic.model.GraphAnswer;
45 import name.angoca.zemucan.grammarReader.api.GrammarReaderController;
46 import name.angoca.zemucan.tools.Constants;
47 import name.angoca.zemucan.tools.messages.Messages;
48
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51
52 /**
53 * This is the implementation of the syntax analyzer. This is almost the most
54 * important part of the application. This class is not thread safe because it
55 * does not have a synchronized part in the singleton.
56 * <p>
57 * Each node of the graph is unique, I mean there are not two nodes that
58 * represents the same word. The grammar reader has to assure that there are not
59 * this case in the produced graph.
60 * <p>
61 * <b>Control Version</b>
62 * <p>
63 * <ul>
64 * <li>0.0.1 Class creation with first algorithm</li>
65 * <li>0.0.2 Change the algorithm.</li>
66 * <li>0.1.0 Throw exception when the graph is invalid.</li>
67 * <li>0.2.0 Old algorithm deleted.</li>
68 * <li>0.3.0 Recommendations from PMD.</li>
69 * <li>0.3.1 Organized.</li>
70 * <li>0.3.2 Instantiated object.</li>
71 * <li>0.3.3 EndingToken not like an option.</li>
72 * <li>0.3.4 InputSource for the XML file.</li>
73 * <li>0.4.0 Invalid graph exception.</li>
74 * <li>0.5.0 Destroy instance.</li>
75 * <li>0.5.1 Starting and ending token as constants.</li>
76 * <li>0.5.2 Logger messages.</li>
77 * <li>0.5.3 Free resources when destroys the instance.</li>
78 * <li>0.5.4 variable name and final.</li>
79 * <li>1.0.0 Moved to version 1.</li>
80 * <li>1.1.0 Exception hierarchy changed.</li>
81 * <li>1.1.1 No null pointer exception.</li>
82 * <li>1.2.0 Constructor visibility.</li>
83 * <li>1.2.1 Not synchronized, not thread safe.</li>
84 * <li>1.2.2 Destroy instance.</li>
85 * <li>1.3.0 throws and asserts.</li>
86 * <li>1.3.1 compareTo -> equals and TO DOs.</li>
87 * <li>1.4.0 Space after phrase</li>
88 * <li>1.4.1 GraphToken renamed by GraphNode</li>
89 * <li>1.4.2 GrammarReader separated from graph.</li>
90 * <li>1.4.3 Node renamed.</li>
91 * <li>1.5.0 No delimiters and grammarController.</li>
92 * <li>1.5.1 Synchronization.</li>
93 * <li>1.6.0 Method renamed.</li>
94 * <li>1.7.0 Without create token.</li>
95 * <li>1.7.1 Iterator to analyze the ways.</li>
96 * <li>1.7.2 Destroy grammar reader instance.</li>
97 * <li>1.8.0 GraphNode hierarchy.</li>
98 * </ul>
99 *
100 * @author Andres Gomez Casanova <a
101 * href="mailto:a n g o c a at y a h o o dot c o m" >(AngocA)</a>
102 * @version 1.8.0 2010-08-29
103 */
104 public final class ImplementationSyntacticAnalyzer extends
105 AbstractSyntacticAnalyzer {
106
107 /**
108 * The only instance of this object.
109 */
110 private static ImplementationSyntacticAnalyzer instance;
111
112 /**
113 * Logger.
114 */
115 private static final Logger LOGGER = LoggerFactory
116 .getLogger(ImplementationSyntacticAnalyzer.class);
117
118 /**
119 * Destroys the instance. Useful for testing purposes.
120 */
121 public static void destroyInstance() {
122 ImplementationSyntacticAnalyzer.LOGGER
123 .debug("Destroying ImplementationSyntacticAnalyzer instance."); //$NON-NLS-1$
124
125 if (ImplementationSyntacticAnalyzer.instance != null) {
126 ImplementationSyntacticAnalyzer.instance.startingNode = null;
127 ImplementationSyntacticAnalyzer.instance = null;
128 }
129
130 assert ImplementationSyntacticAnalyzer.instance == null;
131 }
132
133 /**
134 * Implementation of solitaire pattern, that returns the only instance of an
135 * object.
136 * <p>
137 * This method has a part where it is synchronized, however it is not thread
138 * safe because of the problem with the Single Pattern in Java
139 * (http://www.ibm.com/developerworks/java/library/j-dcl.html)
140 *
141 * @return The only instance of this object.
142 * @throws AbstractZemucanException
143 * When there is a problem at graph creation or a null
144 * parameter.
145 */
146 public static ImplementationSyntacticAnalyzer/* ! */getInstance()
147 throws AbstractZemucanException {
148 if (ImplementationSyntacticAnalyzer.instance == null) {
149 ImplementationSyntacticAnalyzer.LOGGER
150 .debug("Creating ImplementationSyntaticAnalyzer instance."); //$NON-NLS-1$
151 synchronized (ImplementationSyntacticAnalyzer.class) {
152 ImplementationSyntacticAnalyzer.instance = new ImplementationSyntacticAnalyzer();
153 }
154 }
155
156 assert ImplementationSyntacticAnalyzer.instance != null;
157 return ImplementationSyntacticAnalyzer.instance;
158 }
159
160 /**
161 * For assertions.
162 */
163 private boolean assertsEnabled;
164
165 /**
166 * This is the first token of the grammar, and all the commands are analyzed
167 * from this token.
168 */
169 private StartingNode startingNode;
170
171 /**
172 * Constructor that sets a token that permits to scan the graph.
173 *
174 * @throws AbstractZemucanException
175 * When there is a problem when creating the graph. If there is
176 * a null parameter. When there is a problem reading the
177 * grammar. If there is a problem calling the GrammarReader.
178 */
179 private ImplementationSyntacticAnalyzer() throws AbstractZemucanException {
180 super();
181 this.assertsEnabled = false;
182 // Intentional side-effect!
183 assert this.assertsEnabled = true;
184
185 this.startingNode = GrammarReaderController.getInstance()
186 .getStartingNode();
187 // Destroys the grammar reader controller instances because it is not
188 // longer necessary.
189 GrammarReaderController.destroyInstance();
190 }
191
192 /**
193 * This method analyzes a set of options returned by the graph, and check
194 * them if they start with the name of the given token but they don't
195 * represent the same token in the graph (they are different strings).
196 * <p>
197 * This is the case of 'table' and 'tablespace', they are different but they
198 * start with the same pattern.
199 *
200 * @param token
201 * Pattern token to search.
202 * @param options
203 * Possible options to analyze.
204 * @return Set of options that starts with the pattern token and it is
205 * different to the token.
206 */
207 private List<Token>/* <!>! */analyzeOptions(final Token/* ! */token,
208 final List<Token>/* <!>! */options) {
209 assert token != null;
210 assert options != null;
211 if (this.assertsEnabled) {
212 for (final Token token2 : options) {
213 assert token2 != null;
214 }
215 }
216
217 final List<Token> ret = new ArrayList<Token>();
218 final String pattern = token.getToken().toLowerCase();
219 final int size = options.size();
220 for (int i = 0; i < size; i += 1) {
221 final Token option = options.get(i);
222
223 // Start with the same pattern, but it is not the same
224 // token option.
225 if (option.getToken().startsWith(pattern)
226 && !option.getToken().equals(pattern)) {
227 if (option.getToken().equals(Constants.ENDING_NODE)) {
228 ImplementationSyntacticAnalyzer.LOGGER
229 .error(Messages
230 .getString("ImplementationSyntacticAnalyzer." //$NON-NLS-1$
231 + "FatalError"), pattern); //$NON-NLS-1$
232 assert true;
233 }
234 ret.add(option);
235 }
236 }
237
238 assert ret != null;
239 if (this.assertsEnabled) {
240 for (final Token token3 : ret) {
241 assert token3 != null;
242 }
243 }
244 return ret;
245 }
246
247 /*
248 * (non-Javadoc)
249 *
250 * @see name.angoca.zemucan.core.syntactic.api.AbstractSyntacticAnalyzer#
251 * analyzeTokens(java.util.List, boolean)
252 */
253 @Override
254 public GraphAnswer/* ! */analyzeTokens(final List<Token>/* <!>! */phrase,
255 final boolean endsWithSpace) throws AbstractZemucanException {
256 assert phrase != null;
257 if (this.assertsEnabled) {
258 for (final Token token : phrase) {
259 assert token != null;
260 }
261 }
262
263 // TODO v1.1 reducir el cyclomatic complexity
264 if (ImplementationSyntacticAnalyzer.LOGGER.isDebugEnabled()) {
265 ImplementationSyntacticAnalyzer.LOGGER.debug(
266 "Getting option: {}", phrase.toString()); //$NON-NLS-1$
267 }
268
269 // The search of the last node starts from the firsts node.
270 AbstractGraphNode currentNode = this.startingNode;
271
272 List<Token> lastValidOptions = new ArrayList<Token>(0);
273 List<Token> nextTokenOptions = new ArrayList<Token>(0);
274
275 final int size = phrase.size();
276 int index = -1;
277
278 // The search is still valid since there is a way in the
279 // graph. This means that the phrase is not recognized by the
280 // graph.
281 boolean valid = true;
282 // This is invariant of this cycle. (index <= size-1)
283 while ((index <= size - 1) && valid) {
284 // Evaluates if the currentNode is valid to find ways.
285 if (currentNode == null) {
286 this.optionNotFound(phrase, index);
287 valid = false;
288 } else {
289 // This is the case base that shows the possible
290 // options after the last token.
291 if (index == size - 1) {
292 index += 1;
293 nextTokenOptions = this.getWays(currentNode);
294
295 } else
296 // This is the other base case that shows the
297 // possibles options before the last token, and the
298 // options starts with the same name of the last
299 // token.
300 if (index == size - 2) {
301 index += 1;
302 final List<Token> lastTokenOptions = this
303 .getWays(currentNode);
304 final Token token = phrase.get(index);
305 if (!endsWithSpace) {
306 lastValidOptions = this.analyzeOptions(token,
307 lastTokenOptions);
308 }
309 currentNode = this.nextToken(currentNode, token);
310
311 } else
312 // This is the case when scanning the graph in order
313 // to search the last token.
314 if (index < size - 2) {
315 index += 1;
316 currentNode = this
317 .nextToken(currentNode, phrase.get(index));
318 }
319 }
320 }
321
322 final GraphAnswer graphAnswer = new GraphAnswer(lastValidOptions,
323 nextTokenOptions);
324 assert graphAnswer != null;
325 return graphAnswer;
326 }
327
328 /**
329 * Returns all the possible ways from the current node. If EndingNode is
330 * returned, that means that the current phrase is a valid command.
331 * <p>
332 * This creates a copy of the elements of the ways list.
333 *
334 * @param currentNode
335 * Current node to analyze.
336 * @return The possible ways from the current node.
337 * @throws InvalidTokenException
338 * If the graph token has an invalid token.
339 */
340 private List<Token>/* <!>! */getWays(
341 final AbstractGraphNode/* ! */currentNode)
342 throws InvalidTokenException {
343 assert currentNode != null;
344
345 final Iterator<AbstractGraphNode> ways = currentNode.getWays();
346
347 final List<Token> ret = new ArrayList<Token>();
348
349 while (ways.hasNext()) {
350 final AbstractGraphNode graphNode = ways.next();
351 if (graphNode instanceof TextualGraphNode) {
352 if (graphNode instanceof NonReservedGraphNode) {
353 final NonReservedGraphNode nonReserved = (NonReservedGraphNode) graphNode;
354 ret.add(new Token(nonReserved.getName(), false));
355 } else if (graphNode instanceof GraphNode) {
356 final GraphNode reservedWordNode = (GraphNode) graphNode;
357 ret.add(new Token(reservedWordNode.getName(), true));
358 }
359 }
360 }
361
362 assert ret != null;
363 if (this.assertsEnabled) {
364 for (final Token token : ret) {
365 assert token != null;
366 }
367 }
368 return ret;
369 }
370
371 /**
372 * This method scans the graph from the current currentNode, and searches
373 * the token as one of its possible ways. If there is not a valid way in the
374 * graph with the given token, the method return null.
375 *
376 * @param currentNode
377 * Node in the graph where the scan begins.
378 * @param token
379 * Token to search as a possible way from the currentNode.
380 * @return null if there is not a valid way from the current node, or a new
381 * node that represents the new position in the graph.
382 */
383 private AbstractGraphNode/* ? */nextToken(
384 final AbstractGraphNode/* ! */currentNode,
385 final Token/* ! */token) {
386 assert currentNode != null;
387 assert token != null;
388
389 AbstractGraphNode ret = null;
390 final Iterator<AbstractGraphNode> ways = currentNode.getWays();
391
392 // This variable helps to stop the cycle when founding the
393 // valid token.
394 boolean found = false;
395
396 int count = 0;
397 while (ways.hasNext() && !found) {
398 final AbstractGraphNode temp = ways.next();
399 if (temp.represent(token.getToken())) {
400 ret = temp;
401 found = true;
402 }
403 // FireBugs says that is bogus, but I don't know why.
404 count += 1;
405 }
406
407 if (ImplementationSyntacticAnalyzer.LOGGER.isDebugEnabled()) {
408 if (ret == null) {
409 ImplementationSyntacticAnalyzer.LOGGER
410 .debug("Next: {}:\t{}->\tNULL", //$NON-NLS-1$
411 new String[] { token.toString(),
412 currentNode.toString() });
413 } else {
414 ImplementationSyntacticAnalyzer.LOGGER.debug(
415 "Next: {}:\t{}->\t{}", new String[] { //$NON-NLS-1$
416 token.toString(), currentNode.toString(),
417 ret.toString() });
418 }
419 }
420
421 return ret;
422 }
423
424 private void optionNotFound(final List<Token> phrase, final int index) {
425 if (ImplementationSyntacticAnalyzer.LOGGER.isDebugEnabled()) {
426 ImplementationSyntacticAnalyzer.LOGGER.debug(
427 "The '{}' option was not found in the grammar.", //$NON-NLS-1$
428 phrase.get(index).getToken());
429 }
430 }
431 }