1 /*
2 * db2sa: DB2 Syntax Assistant
3 * Copyright (C) Andres Gomez Casanova
4 *
5 * This file is part of db2sa.
6 *
7 * db2sa 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 * db2sa 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: 2009-07-08 19:56:43 +0200 (Wed, 08 Jul 2009) $:
26 * Revision: $LastChangedRevision: 357 $:
27 * URL: $HeadURL: https://db2sa.svn.sourceforge.net/svnroot/db2sa/branches/db2sa_beta/source-code/src/main/java/name/angoca/db2sa/core/syntax/ImplSyntaxAnalyzer.java $:
28 */
29 package name.angoca.db2sa.core.syntax;
30
31 import java.util.ArrayList;
32 import java.util.List;
33
34 import name.angoca.db2sa.Constants;
35 import name.angoca.db2sa.core.lexical.Token;
36 import name.angoca.db2sa.core.lexical.exceptions.InvalidTokenException;
37 import name.angoca.db2sa.core.syntax.graph.EndingToken;
38 import name.angoca.db2sa.core.syntax.graph.GraphConstructor;
39 import name.angoca.db2sa.core.syntax.graph.GraphToken;
40 import name.angoca.db2sa.core.syntax.graph.StartingToken;
41 import name.angoca.db2sa.core.syntax.graph.exception.InvalidGraphException;
42 import name.angoca.db2sa.messages.Messages;
43 import name.angoca.db2sa.readers.GrammarReader;
44
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47 import org.xml.sax.InputSource;
48
49 /**
50 * This is the implementation of the syntax analyzer. This is almost the most
51 * important part of the application.<br/>
52 * <b>Control Version</b><br />
53 * <ul>
54 * <li>0.0.1 Class creation with first algorithm</li>
55 * <li>0.0.2 Change the algorithm.</li>
56 * <li>0.1.0 Throw exception when the graph is invalid.</li>
57 * <li>0.2.0 Old algorithm deleted.</li>
58 * <li>0.3.0 Recommendations from PMD.</li>
59 * <li>0.3.1 Organized.</li>
60 * <li>0.3.2 Instantiated object.</li>
61 * <li>0.3.3 EndingToken not like an option.</li>
62 * <li>0.3.4 InputSource for the XML file.</li>
63 * <li>0.4.0 Invalid graph exception.</li>
64 * <li>0.5.0 Destroy instance.</li>
65 * <li>0.5.1 Starting and ending token as constants.</li>
66 * <li>0.5.2 Logger messages.</li>
67 * <li>0.5.3 Free resources when destroys the instance.</li>
68 * <li>0.5.4 variable name and final.</li>
69 * <li>1.0.0 Moved to version 1.</li>
70 * </ul>
71 *
72 * @author Andres Gomez Casanova <a
73 * href="mailto:a n g o c a at y a h o o dot c o m">(AngocA)</a>
74 * @version 1.0.0 2009-07-19
75 */
76 public final class ImplSyntaxAnalyzer extends AbstractSyntacticalAnalyzer {
77
78 /**
79 * Logger.
80 */
81 private static final Logger LOGGER = LoggerFactory
82 .getLogger(ImplSyntaxAnalyzer.class);
83
84 /**
85 * The only instance of this object.
86 */
87 private static ImplSyntaxAnalyzer s_instance;
88
89 /**
90 * This is the first token of the grammar, and all the commands are analyzed
91 * from this token.
92 */
93 private StartingToken m_startingToken;
94
95 /**
96 * Constructor that sets a token that permits to scan the graph.
97 *
98 * @throws InvalidGraphException
99 * When there is a problem when creating the graph.
100 */
101 public ImplSyntaxAnalyzer() throws InvalidGraphException {
102 super();
103 final InputSource content = GrammarReader.read();
104 this.m_startingToken = GraphConstructor.build(content);
105 }
106
107 /**
108 * Implementation of solitaire pattern, that returns the only instance of an
109 * object.
110 *
111 * @return The only instance of this object.
112 * @throws InvalidGraphException
113 * When there is a problem at graph creation.
114 */
115 public static ImplSyntaxAnalyzer/* ! */getInstance()
116 throws InvalidGraphException {
117 synchronized (ImplSyntaxAnalyzer.class) {
118 if (ImplSyntaxAnalyzer.s_instance == null) {
119 ImplSyntaxAnalyzer.LOGGER
120 .debug("Creating ImplSyntaxAnalyzer instance."); //$NON-NLS-1$
121 ImplSyntaxAnalyzer.s_instance = new ImplSyntaxAnalyzer();
122 }
123 }
124 return ImplSyntaxAnalyzer.s_instance;
125 }
126
127 /**
128 * Destroys the instance. Useful for testing purposes.
129 */
130 public static void destroyInstance() {
131 ImplSyntaxAnalyzer.LOGGER
132 .debug("Destroying ImplSyntaxAnalyzer instance."); //$NON-NLS-1$
133
134 if (ImplSyntaxAnalyzer.s_instance != null) {
135 ImplSyntaxAnalyzer.s_instance.m_startingToken = null;
136 ImplSyntaxAnalyzer.s_instance.m_startingToken = null;
137 }
138 ImplSyntaxAnalyzer.s_instance = null;
139 }
140
141 /**
142 * This method analyzes a set of options returned by the graph, and check
143 * them if they start with the name of the given token but they don't
144 * represent the same token in the graph (they are different strings).<br/>
145 * This is the case of 'table' and 'tablespace', they are different but they
146 * start with the same pattern.
147 *
148 * @param token
149 * Pattern token to search.
150 * @param options
151 * Possible options to analyze.
152 * @return Set of options that starts with the pattern token and it is
153 * different to the token.
154 */
155 private List<Token>/* <!>! */analyzeOptions(final Token/* ! */token,
156 final List<Token>/* <!>! */options) {
157 final List<Token> ret = new ArrayList<Token>();
158 final String pattern = token.getToken();
159 final int size = options.size();
160 for (int i = 0; i < size; i += 1) {
161 final Token option = options.get(i);
162
163 // Start with the same pattern, but it is not the same token option.
164 if (option.getToken().startsWith(pattern)
165 && option.getToken().compareTo(pattern) != 0) {
166 // FIXME validar cuando hay endingNode
167 if (option.getToken().compareTo(Constants.ENDING_TOKEN) == 0) {
168 ImplSyntaxAnalyzer.LOGGER.debug("Ending token!!"); //$NON-NLS-1$
169 }
170 ret.add(option);
171 }
172 }
173 return ret;
174 }
175
176 /*
177 * (non-Javadoc)
178 *
179 * @see
180 * name.angoca.db2sa.core.syntax.AbstractSyntacticalAnalyzer#getOptions(
181 * List)
182 */
183 @Override
184 public GraphAnswer/* <!>! */getOptions(final List<Token>/* <!>! */phrase)
185 throws InvalidGraphException {
186 // TODO reducir el cyclomatic complexity
187 if (ImplSyntaxAnalyzer.LOGGER.isDebugEnabled()) {
188 ImplSyntaxAnalyzer.LOGGER.debug(
189 "Getting option: {}", phrase.toString()); //$NON-NLS-1$
190 }
191
192 List<Token> lastValidOptions = new ArrayList<Token>(0);
193 List<Token> nextTokenOptions = new ArrayList<Token>(0);
194
195 final int size = phrase.size();
196 int index = -1;
197
198 // The search of the last node starts from the firsts node.
199 GraphToken currentNode = this.m_startingToken;
200
201 // The search is still valid since there is a way in the graph. This
202 // means that the phrase is not recognized by the graph.
203 boolean valid = true;
204 try {
205 // This is invariant of this cycle. (i <= size-1)
206 while (index <= size - 1 && valid) {
207 // Evaluates if the currentNode is valid to find ways.
208 if (currentNode == null) {
209 if (ImplSyntaxAnalyzer.LOGGER.isDebugEnabled()) {
210 if (phrase.get(index) == null) {
211 // TODO Revisar si este caso existe
212 ImplSyntaxAnalyzer.LOGGER
213 .warn("\t--Nothing found."); //$NON-NLS-1$
214 } else {
215 ImplSyntaxAnalyzer.LOGGER.warn(
216 "\t--Nothing found {} ", //$NON-NLS-1$
217 phrase.get(index).getToken());
218 }
219 }
220 valid = false;
221 } else {
222 // This is the case base that shows the possible options
223 // after
224 // the
225 // last token.
226 if (index == size - 1) {
227 index += 1;
228 nextTokenOptions = this.getWays(currentNode);
229
230 } else
231 // This is the other base case that shows the possibles
232 // options before the last token, and the options starts
233 // with the same name of the last token.
234 if (index == size - 2) {
235 index += 1;
236 final List<Token> lastTokenOptions = this
237 .getWays(currentNode);
238 lastValidOptions = this.analyzeOptions(phrase
239 .get(index), lastTokenOptions);
240 currentNode = this.nextToken(currentNode, phrase
241 .get(index));
242
243 } else
244 // This is the case when scanning the graph in order to
245 // search the last token.
246 if (index < size - 2) {
247 index += 1;
248 currentNode = this.nextToken(currentNode, phrase
249 .get(index));
250 }
251 }
252 }
253 } catch (InvalidTokenException e) {
254 // I don't know how to tests that.
255 throw new InvalidGraphException(e);
256 } catch (NullPointerException nullExcep) {
257 // TODO Lanzar otra excepción (Mas bien revisar en qué caso se da y
258 // evitarlo)
259 throw new RuntimeException(Messages
260 .getString("ImplSyntaxAnalyzer.InvalidGraph") //$NON-NLS-1$
261 , nullExcep);
262 }
263 return new GraphAnswer(lastValidOptions, nextTokenOptions);
264 }
265
266 /**
267 * Returns all the possible ways from the current node. If EndingNode is
268 * returned, that means that the current phrase is a valid command.
269 *
270 * @param currentNode
271 * Current node to analyze.
272 * @return The possible ways from the current node.
273 * @throws InvalidTokenException
274 * If the graph token has an invalid token.
275 */
276 private List<Token>/* <!>! */getWays(final GraphToken/* ! */currentNode)
277 throws InvalidTokenException {
278 final List<GraphToken> ways = currentNode.getWays();
279 final int size = ways.size();
280
281 final List<Token> ret = new ArrayList<Token>(size);
282
283 for (int i = 0; i < size; i += 1) {
284 final GraphToken graphToken = ways.get(i);
285 if (!(graphToken instanceof EndingToken)) {
286 final String identifier = graphToken.getName();
287 ret.add(this.createToken(identifier));
288 }
289 }
290 return ret;
291 }
292
293 /**
294 * Creates a token given the identifier.
295 *
296 * @param identifier
297 * The token identifier.
298 * @return The token instantiated.
299 * @throws InvalidTokenException
300 * If there is a problem with the token.
301 */
302 private Token/* ! */createToken(final String/* ! */identifier)
303 throws InvalidTokenException {
304 return new Token(identifier);
305 }
306
307 /**
308 * This method scan the graph from the current currentNode, and searches the
309 * token as one of its possible ways. If there is not a valid way in the
310 * graph with the given token, the method return nulls.
311 *
312 * @param currentNode
313 * Node in the graph where the scan begins.
314 * @param token
315 * Token to search as a possible way from the currentNode.
316 * @return null if there is no a valid way from the current node, or a new
317 * node that represents the new position in the graph.
318 */
319 private GraphToken/* ? */nextToken(final GraphToken/* ! */currentNode,
320 final Token/* ! */token) {
321
322 GraphToken ret = null;
323 final List<GraphToken> ways = currentNode.getWays();
324
325 // This variable helps to stop the cycle when founding the valid token.
326 boolean found = false;
327
328 final int size = ways.size();
329 int count = 0;
330 while (count < size && !found) {
331 final GraphToken temp = ways.get(count);
332 if (temp.represent(token.getToken())) {
333 ret = temp;
334 found = true;
335 }
336 // FireBugs says that is bogus, but I don't know why.
337 count += 1;
338 }
339
340 if (ImplSyntaxAnalyzer.LOGGER.isDebugEnabled()) {
341 if (ret == null) {
342 ImplSyntaxAnalyzer.LOGGER
343 .debug("Next: {}:\t{}->\tNULL", //$NON-NLS-1$
344 new String[] { token.toString(),
345 currentNode.toString() });
346 } else {
347 ImplSyntaxAnalyzer.LOGGER.debug(
348 "Next: {}:\t{}->\t{}", new String[] { //$NON-NLS-1$
349 token.toString(), currentNode.toString(),
350 ret.toString() });
351 }
352 }
353
354 return ret;
355 }
356 }