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 }