View Javadoc

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 }