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/graph/GraphConstructor.java $:
28   */
29  package name.angoca.db2sa.core.syntax.graph;
30  
31  import java.io.IOException;
32  import java.util.HashMap;
33  import java.util.Iterator;
34  import java.util.Map;
35  
36  import javax.xml.parsers.DocumentBuilder;
37  import javax.xml.parsers.DocumentBuilderFactory;
38  import javax.xml.parsers.ParserConfigurationException;
39  
40  import name.angoca.db2sa.Configurator;
41  import name.angoca.db2sa.Constants;
42  import name.angoca.db2sa.core.syntax.graph.exception.DuplicateNodeException;
43  import name.angoca.db2sa.core.syntax.graph.exception.EndingNodeNotDefinedException;
44  import name.angoca.db2sa.core.syntax.graph.exception.GrammarFileException;
45  import name.angoca.db2sa.core.syntax.graph.exception.InvalidGraphException;
46  import name.angoca.db2sa.core.syntax.graph.exception.StartingNodeNotDefinedException;
47  import name.angoca.db2sa.messages.Messages;
48  
49  import org.slf4j.Logger;
50  import org.slf4j.LoggerFactory;
51  import org.w3c.dom.Document;
52  import org.w3c.dom.Element;
53  import org.w3c.dom.NodeList;
54  import org.xml.sax.InputSource;
55  import org.xml.sax.SAXException;
56  
57  /**
58   * This class builds the graph from the content of the files. <br/>
59   * Based on a tutorial that can be found in
60   * http://www.totheriver.com/learn/xml/xmltutorial.html <br/>
61   * TODO hacer algunos tests para comprobar la estructura del grafo creado. TODO
62   * Arreglar jerarquia de excepciones y agrupar. <b>Control Version</b><br />
63   * <ul>
64   * <li>0.0.1 Class creation.</li>
65   * <li>0.0.2 Recommendations from PMD.</li>
66   * <li>0.0.3 Organized.</li>
67   * <li>0.0.4 License token.</li>
68   * <li>0.0.5 License token reserved.</li>
69   * <li>0.0.6 Ending token after create table.</li>
70   * <li>0.1.0 Use of XML to create the graph.</li>
71   * <li>0.1.1 Help Token.</li>
72   * <li>0.1.2 Messages, exceptions and format.</li>
73   * <li>0.1.3 Starting and ending token as constants.</li>
74   * <li>0.1.4 Variable names and final.</li>
75   * <li>1.0.0 Moved to version 1.</li>
76   * </ul>
77   * 
78   * @author Andres Gomez Casanova <a
79   *         href="mailto:a n g o c a at y a h o o dot c o m">(AngocA)</a>
80   * @version 1.0.0 2009-07-19
81   */
82  public final class GraphConstructor {
83  
84      /**
85       * Logger.
86       */
87      private static final Logger LOGGER = LoggerFactory
88              .getLogger(GraphConstructor.class);
89  
90      /**
91       * Set of tokens.
92       */
93      private final Map<String, GraphToken> m_tokens;
94  
95      /**
96       * Default constructor.
97       */
98      private GraphConstructor() {
99          this.m_tokens = new HashMap<String, GraphToken>();
100     }
101 
102     /**
103      * Builds the graph from the content that was read from the grammar source
104      * (a file for example).
105      * 
106      * @param content
107      *            Data from the grammar file.
108      * @return Starting node of the graph.
109      * @throws InvalidGraphException
110      *             When the StartingNode or EndingNode are not well defined.
111      * 
112      */
113     public static StartingToken/* ! */build(final InputSource/* ! */content)
114             throws InvalidGraphException {
115         // Creates an instance.
116         final GraphConstructor graphCons = new GraphConstructor();
117 
118         // Parses the XML file and gets the DOM object.
119         final Document dom = graphCons.parseXmlFile(content);
120 
121         // Gets each token element without relations.
122         graphCons.firstPhaseDocument(dom);
123 
124         // Creates the relations between nodes.
125         graphCons.secondPhaseDocument(dom);
126 
127         // Adds the license token to the graph.
128         final StartingToken start = graphCons.addExtraTokens();
129 
130         if (GraphConstructor.LOGGER.isDebugEnabled()) {
131             graphCons.printData();
132         }
133 
134         return start;
135     }
136 
137     /**
138      * Loads the document builder.
139      * 
140      * @param content
141      *            XML content.
142      * @return The DOM read from the file.
143      * @throws GrammarFileException
144      *             If there is a problem reading the XML file of the grammar.
145      */
146     private Document parseXmlFile(final InputSource/* ! */content)
147             throws GrammarFileException {
148         // Gets the factory.
149         final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
150 
151         // Using factory, gets an instance of document builder.
152         DocumentBuilder docBuilder;
153         Document dom = null;
154 
155         try {
156             docBuilder = dbf.newDocumentBuilder();
157 
158             // Parse using builder to get DOM representation of the XML file.
159             dom = docBuilder.parse(content);
160 
161         } catch (ParserConfigurationException e) {
162             // I don't know how to test this part. It's outside my scope.
163             throw new GrammarFileException(e);
164         } catch (SAXException e) {
165             throw new GrammarFileException(e);
166         } catch (IOException e) {
167             // I don't know how to test this part. It's outside my scope.
168             throw new GrammarFileException(e);
169         }
170         return dom;
171     }
172 
173     /**
174      * Reads the document and creates all the nodes. NOTE: This phase does not
175      * create the relation between nodes.
176      * 
177      * @param dom
178      *            DOM from the XML file.
179      * @throws DuplicateNodeException
180      *             When there are several nodes in the grammar file with the
181      *             same id.
182      */
183     private void firstPhaseDocument(final Document dom)
184             throws DuplicateNodeException {
185         // Gets the root element.
186         final Element docEle = dom.getDocumentElement();
187 
188         // Gets the tokens
189         final NodeList nodelist = docEle.getElementsByTagName("token"); //$NON-NLS-1$
190 
191         if (nodelist != null) {
192             final int size = nodelist.getLength();
193             if (size > 0) {
194                 for (int i = 0; i < size; i += 1) {
195 
196                     // Gets the token element.
197                     final Element eleToken = (Element) nodelist.item(i);
198 
199                     // Gets the Token object.
200                     final GraphToken graphToken = this.getToken(eleToken);
201 
202                     // Adds the token to list
203                     final String tokenId = graphToken.getId();
204                     if (this.m_tokens.containsKey(tokenId)) {
205                         throw new DuplicateNodeException(tokenId);
206                     }
207                     this.m_tokens.put(tokenId, graphToken);
208                 }
209             }
210         }
211     }
212 
213     /**
214      * It takes a token element and reads the values in it, then creates a graph
215      * token object and returns it.
216      * 
217      * @param eleToken
218      *            element where the data of the token can be found.
219      * @return the graph token from the element.
220      */
221     private GraphToken /* ! */getToken(final Element /* ! */eleToken) {
222 
223         // For each node asks the id, name and if it is reserved.
224         final String tokenId = this.getTextValue(eleToken, "id"); //$NON-NLS-1$
225         final String name = this.getTextValue(eleToken, "name"); //$NON-NLS-1$
226         final NodeList nodelist = eleToken.getElementsByTagName("reserved"); //$NON-NLS-1$
227         boolean reserved = false;
228         if (nodelist != null && nodelist.getLength() > 0) {
229             reserved = true;
230         }
231 
232         // Creates a new token with its parameters
233         GraphToken token = null;
234 
235         if (tokenId == null) {
236             // The grammar does not define an id.
237             // TODO Cambiar la excepción lanzada
238             throw new RuntimeException("id not defined " + name); 
239         } else if (name == null) {
240             // The grammar does not define a name.
241             // TODO Cambiar la excepción lanzada
242             throw new RuntimeException("name not defined " + tokenId); 
243         } else if (tokenId.compareTo(Constants.STARTING_TOKEN) == 0) {
244             // It's the starting token
245             token = new StartingToken();
246         } else if (tokenId.compareTo(Constants.ENDING_TOKEN) == 0) {
247             // It's the ending token
248             token = new EndingToken();
249         } else {
250             // It's a normal token
251             token = new GraphToken(tokenId, name, reserved);
252         }
253 
254         return token;
255     }
256 
257     /**
258      * It takes an XML element and the tag name, then it looks for the tag and
259      * gets the text content.
260      * 
261      * @param element
262      *            XML element to read.
263      * @param tagName
264      *            Name of the element.
265      * @return Content of the element with the given name.
266      */
267     private String /* ! */getTextValue(final Element/* ! */element,
268             final String/* ! */tagName) {
269         String textValue = null;
270         final NodeList nodelist = element.getElementsByTagName(tagName);
271         if (nodelist != null && nodelist.getLength() > 0) {
272             final Element elem = (Element) nodelist.item(0);
273             textValue = elem.getFirstChild().getNodeValue();
274         }
275 
276         return textValue;
277     }
278 
279     /**
280      * Creates the relations between the nodes.
281      * 
282      * @param dom
283      *            XML document that has all the information about the graph.
284      */
285     private void secondPhaseDocument(final Document dom) {
286         // Gets the root element.
287         final Element docEle = dom.getDocumentElement();
288 
289         // Gets the tokens
290         final NodeList tokenlist = docEle.getElementsByTagName("token"); //$NON-NLS-1$
291 
292         if (tokenlist != null) {
293             final int size = tokenlist.getLength();
294             if (size > 0) {
295                 for (int i = 0; i < size; i += 1) {
296 
297                     // Gets the token element.
298                     final Element eleToken = (Element) tokenlist.item(i);
299 
300                     // Establishes the children of a Token object.
301                     this.getChildren(eleToken);
302                 }
303             }
304         }
305     }
306 
307     /**
308      * Retrieves the children of a token and put them in it.
309      * 
310      * @param eleToken
311      *            Description of a token.
312      */
313     private void getChildren(final Element/* ! */eleToken) {
314         // For each node asks the id.
315         final String tokenId = this.getTextValue(eleToken, "id"); //$NON-NLS-1$
316 
317         // Retrieves the token with that id.
318         final GraphToken token = this.m_tokens.get(tokenId);
319 
320         // Gets the children of that token.
321         final NodeList childList = eleToken.getElementsByTagName("idNode"); //$NON-NLS-1$
322 
323         if (childList != null) {
324             final int size = childList.getLength();
325             if (size > 0) {
326                 for (int i = 0; i < size; i += 1) {
327 
328                     // Gets the child token element.
329                     final Element childElement = (Element) childList.item(i);
330 
331                     // Establishes the children of a Token object.
332                     final String childId = childElement.getFirstChild()
333                             .getNodeValue();
334 
335                     // Retrieves the child and established the relation.
336                     final GraphToken childToken = this.m_tokens.get(childId);
337                     if (childToken == null) {
338                         // TODO lanzar excepción propia
339                         throw new RuntimeException("token not defined " 
340                                 + childId);
341                     }
342                     token.addWay(childToken);
343                 }
344             }
345         }
346         if (GraphConstructor.LOGGER.isDebugEnabled()) {
347             GraphConstructor.LOGGER.debug(token.toString());
348             for (int i = 0; i < token.getWays().size(); i += 1) {
349                 GraphConstructor.LOGGER.debug("\t{} ", token.toString()); //$NON-NLS-1$
350                 GraphConstructor.LOGGER.debug("\t\t\t -> {}", //$NON-NLS-1$
351                         token.getWays().get(i).toString());
352             }
353         }
354     }
355 
356     /**
357      * Adds the license token and the help token to the graph.
358      * 
359      * @return The StartingNode of the graph.
360      * @throws InvalidGraphException
361      *             When the StartingNode or the EndingNode are not well defined.
362      */
363     private StartingToken/* ! */addExtraTokens() throws InvalidGraphException {
364         StartingToken start = null;
365         try {
366             // Starting token
367             start = (StartingToken) this.m_tokens.get(Constants.STARTING_TOKEN);
368             if (start == null) {
369                 throw new StartingNodeNotDefinedException();
370             }
371 
372             // Ending token
373             final EndingToken end = (EndingToken) this.m_tokens
374                     .get(Constants.ENDING_TOKEN);
375             if (end == null) {
376                 throw new EndingNodeNotDefinedException();
377             }
378 
379             if (start.getWays().size() == 1
380                     && start.getWays().get(0).getId().compareTo(
381                             Constants.ENDING_TOKEN) == 0) {
382                 GraphConstructor.LOGGER.info("Invalid graph"); //$NON-NLS-1$
383                 // TODO cambiar la exception para que contenga un mensaje más
384                 // claro.
385                 throw new InvalidGraphException();
386             }
387 
388             // License token
389             final String aboutToken = Configurator.getInstance().getProperty(
390                     Constants.ABOUT_TOKEN);
391             if (aboutToken == null) {
392                 // TODO Throw another exception (from the appl)
393                 throw new RuntimeException(Messages 
394                         .getString("GraphConstructor" //$NON-NLS-1$
395                                 + ".NoAboutTokenDefined")); //$NON-NLS-1$
396             }
397             final GraphToken licenseCaller = new GraphToken(aboutToken, true);
398             final LicenseToken license = new LicenseToken();
399 
400             start.addWay(licenseCaller);
401             licenseCaller.addWay(license);
402             license.addWay(end);
403 
404             // Help token
405             final String helpToken = Configurator.getInstance().getProperty(
406                     Constants.HELP_TOKEN);
407             if (helpToken == null) {
408                 // TODO Throw another exception (from the appl)
409                 throw new RuntimeException(Messages
410                         .getString("GraphConstructor" //$NON-NLS-1$
411                                 + ".NoHelpTokenDefined")); //$NON-NLS-1$
412             }
413             final GraphToken helpCaller = new GraphToken(helpToken, true);
414             final HelpToken help = new HelpToken();
415 
416             start.addWay(helpCaller);
417             helpCaller.addWay(help);
418             help.addWay(end);
419 
420         } catch (ClassCastException e) {
421             // I don't know how to test this part.
422             throw new InvalidGraphException(e);
423         }
424         return start;
425     }
426 
427     /**
428      * Iterate through the list and print the content to console.
429      */
430     @SuppressWarnings("boxing")
431     private void printData() {
432 
433         GraphConstructor.LOGGER
434                 .debug("No of Tokens '{}'", this.m_tokens.size()); //$NON-NLS-1$
435 
436         final Iterator<GraphToken> iterator = this.m_tokens.values().iterator();
437         while (iterator.hasNext()) {
438             GraphConstructor.LOGGER.debug(iterator.next().toString());
439         }
440     }
441 }