View Javadoc

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 }