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 23:10:20 -0500 (dom, 06 mar 2011) $:
26 * Revision: $LastChangedRevision: 1917 $:
27 * URL: $HeadURL: https://zemucan.svn.sourceforge.net/svnroot/zemucan/branches/zemucan_v1/source-code/grammarReaderApi/src/main/java/name/angoca/zemucan/grammarReader/api/GrammarReaderController.java $:
28 */
29 package name.angoca.zemucan.grammarReader.api;
30
31 import java.lang.reflect.Constructor;
32 import java.lang.reflect.InvocationTargetException;
33 import java.util.ArrayList;
34 import java.util.List;
35 import java.util.StringTokenizer;
36
37 import name.angoca.zemucan.AbstractZemucanException;
38 import name.angoca.zemucan.core.graph.model.Graph;
39 import name.angoca.zemucan.core.graph.model.StartingNode;
40 import name.angoca.zemucan.tools.Constants;
41 import name.angoca.zemucan.tools.configurator.Configurator;
42
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
45
46 /**
47 * This class controls the read of the grammar, and the graph construction. The
48 * class loads the corresponding GrammarReading by reflection, using the
49 * property that indicates the name of the class to use as GrammarReader.
50 * <p>
51 * <b>Control Version</b>
52 * <p>
53 * <ul>
54 * <li>1.0.0 Class creation.</li>
55 * <li>1.0.1 Grammar filename separator.</li>
56 * <li>1.1.0 Retrieves the graph.</li>
57 * <li>1.2.0 Synchronization.</li>
58 * <li>1.3.0 Interface.</li>
59 * <li>1.3.1 Empty grammar name.</li>
60 * <li>1.3.2 Files names are just tokens.</li>
61 * <li>1.4.0 New structure.</li>
62 * <li>1.4.1 Replaces with the returned graph.</li>
63 * <li>1.4.2 Javadoc, extra nodes from configuration.</li>
64 * </ul>
65 *
66 * @author Andres Gomez Casanova <a
67 * href="mailto:a n g o c a at y a h o o dot c o m" >(AngocA)</a>
68 * @version 1.4.2 2010-08-08
69 * @since 1.0
70 */
71 public final class GrammarReaderController implements
72 InterfaceGrammarReaderController {
73 /**
74 * Unique instance of the grammar controller.
75 */
76 private static GrammarReaderController instance;
77 /**
78 * Logger.
79 */
80 private static final Logger LOGGER = LoggerFactory
81 .getLogger(GrammarReaderController.class);
82
83 /**
84 * Destroys the instance. Useful for testing purposes. It could be used when
85 * creating several graphs that are not connected.
86 */
87 public static void destroyInstance() {
88 GrammarReaderController.LOGGER
89 .debug("Destroying GrammarReaderController instance."); //$NON-NLS-1$
90
91 if (GrammarReaderController.instance != null) {
92 GrammarReaderController.instance = null;
93 }
94
95 assert GrammarReaderController.instance == null;
96 }
97
98 /**
99 * Retrieves the unique instance of the grammar controller. If it does not
100 * exist, it creates the corresponding instance. At that time the grammar is
101 * read.
102 * <p>
103 * This method is the implementation of the singleton pattern.
104 * <p>
105 * This method has a part where it is synchronized, however it is not thread
106 * safe because of the problem with the Single Pattern in Java
107 * (http://www.ibm.com/developerworks/java/library/j-dcl.html)
108 *
109 * @return The unique instance of grammar controller.
110 * @throws AbstractZemucanException
111 * If the read graph is invalid. If a parameter is null when
112 * calling a method. If the grammar is invalid. If there is a
113 * problem calling the GrammarReader.
114 */
115 public static GrammarReaderController/* ! */getInstance()
116 throws AbstractZemucanException {
117 if (GrammarReaderController.instance == null) {
118 GrammarReaderController.LOGGER
119 .debug("Creating GrammarReaderController instance"); //$NON-NLS-1$
120 synchronized (GrammarReaderController.class) {
121 GrammarReaderController.instance = new GrammarReaderController();
122 }
123 }
124
125 assert GrammarReaderController.instance != null;
126 return GrammarReaderController.instance;
127 }
128
129 /**
130 * Merge the read graphs in just one and establish the startingNode.
131 *
132 * @param graphs
133 * set of graphs.
134 * @return The merge graph.
135 * @throws AbstractZemucanException
136 * If there is a problem merging the graphs. If there is a
137 * problem merging the graphs.
138 */
139 public static Graph/* ! */mergeGraphs(final Graph[]/* [!]! */graphs)
140 throws AbstractZemucanException {
141 Graph masterGraph = null;
142 if (graphs.length > 1) {
143 masterGraph = graphs[0];
144 for (int i = 1; i < graphs.length; i += 1) {
145 final Graph graph = graphs[i];
146 // Merge two graphs.
147 graph.merge(masterGraph);
148 }
149 // Simplifies two ways that represent the same token.
150 masterGraph.simplifyGraph();
151 } else if (graphs.length == 1) {
152 masterGraph = graphs[0];
153 } else {
154 assert false;
155 }
156
157 return masterGraph;
158 }
159
160 /**
161 * Retrieves all the globalDelimiters of all files that compose the grammar.
162 * The set has not repeat elements. TODO v1.1 revisar que los delimitadores
163 * de todos los archivos son los mismos, si no lo son, los grafos no se
164 * pueden navegar. TODO v2.0 en un futuro permitir gramaticas con diferentes
165 * delimitadores, pero solo se pueden interpretar grafos parciales, no toda
166 * la gramatica.
167 *
168 * @param delimiters
169 * Set of globalDelimiters.
170 * @return Delimiters unified.
171 */
172 public static String/* ! */processDelimiters(
173 final List<String>/* [!]! */delimiters) {
174 String globalDelimiters = ""; //$NON-NLS-1$
175 for (final String delims : delimiters) {
176 for (int j = 0; j < delims.length(); j += 1) {
177 final char character = delims.charAt(j);
178 if (globalDelimiters.indexOf(character) == -1) {
179 globalDelimiters += delims.charAt(j);
180 }
181 }
182 }
183 GrammarReaderController.LOGGER
184 .debug("Delimiters merged: " + globalDelimiters); //$NON-NLS-1$
185 return globalDelimiters;
186 }
187
188 /**
189 * Set of globalDelimiters of the grammar.
190 */
191 private final String globalDelimiters;
192
193 /**
194 * Entry point of the graph.
195 */
196 private final StartingNode startingNode;
197
198 /**
199 * Unified graph.
200 */
201 private final Graph unifiedGgraph;
202
203 /**
204 * Creates the instance of the GrammarReader. It calls the corresponding
205 * GrammarReader class by its name doing reflection.
206 *
207 * @throws AbstractZemucanException
208 * If the graph is invalid. If a parameter is null. If the
209 * grammar is invalid. If there is a problem calling the
210 * GrammarReader.
211 */
212 private GrammarReaderController() throws AbstractZemucanException {
213
214 final Constructor<?> constructor = this.getConstructor();
215
216 // Retrieves the files that are part of the grammar
217 final String grammarFileDescriptors = Configurator.getInstance()
218 .getProperty(Constants.GRAMMAR_FILE_DESCRIPTORS_PROPERTY);
219 final StringTokenizer tokenFileDescriptors = new StringTokenizer(
220 grammarFileDescriptors, ";"); //$NON-NLS-1$
221
222 final int filesQty = tokenFileDescriptors.countTokens();
223 final int position = 0;
224 // Set of graph that represents each file of the grammar.
225 final List<Graph> graphs = new ArrayList<Graph>(filesQty);
226 final List<String> grammarDelimiters = new ArrayList<String>(filesQty);
227 // Iterates over each file, creating a graph in each
228 // iteration.
229 while (tokenFileDescriptors.hasMoreTokens()) {
230 final String fileDescriptor = tokenFileDescriptors.nextToken();
231 this.readGraph(constructor, position, graphs, grammarDelimiters,
232 fileDescriptor);
233 }
234 this.globalDelimiters = GrammarReaderController
235 .processDelimiters(grammarDelimiters);
236 this.unifiedGgraph = GrammarReaderController.mergeGraphs(graphs
237 .toArray(new Graph[0]));
238 this.startingNode = this.unifiedGgraph.getStartingNode();
239 this.unifiedGgraph.checkDuplicates();
240 }
241
242 /**
243 * Retrieves a constructor of a grammar reader by reflexivity of a class.
244 * The name of the class is obtained from the configurator.
245 *
246 * @return Constructor of the grammar reader.
247 * @throws GrammarReaderNotDefinedException
248 * If there is not grammar reader defined in the configuration
249 * file.
250 * @throws GeneralGrammarReaderProblemException
251 * If there is a problem doing the reflexivity.
252 */
253 private Constructor<?>/* ! */getConstructor()
254 throws AbstractGrammarReaderException {
255 // Retrieves the name of the grammar reader class.
256 final String grammarReaderName = Configurator.getInstance()
257 .getProperty(Constants.GRAMMAR_READER_NAME_PROPERTY);
258 if ((grammarReaderName == null) || grammarReaderName.equals("")) {
259 throw new GrammarReaderNotDefinedException();
260 }
261
262 // Obtain the class by reflexivity.
263 Class<?> clazz = null;
264 try {
265 clazz = Class.forName(grammarReaderName);
266 } catch (final ClassNotFoundException e) {
267 throw new GeneralGrammarReaderProblemException(e);
268 }
269 // Get the constructor of the class (Graph + String).
270 Constructor<?> constructor = null;
271 try {
272 constructor = clazz.getConstructor(Graph.class, String.class);
273 } catch (final NoSuchMethodException e) {
274 throw new GeneralGrammarReaderProblemException(e);
275 }
276 return constructor;
277 }
278
279 /*
280 * (non-Javadoc)
281 *
282 * @see
283 * name.angoca.zemucan.grammarReader.api.InterfaceGrammarReaderController
284 * #getDelimiters()
285 */
286 @Override
287 public String/* ! */getDelimiters() throws AbstractInvalidGrammarException {
288 return this.globalDelimiters;
289 }
290
291 /**
292 * Return the graph already unified.
293 *
294 * @return Unified graph.
295 */
296 Graph/* ! */getGraph() {
297 return this.unifiedGgraph;
298 }
299
300 /*
301 * (non-Javadoc)
302 *
303 * @see
304 * name.angoca.zemucan.grammarReader.api.InterfaceGrammarReaderController
305 * #getStartingNode()
306 */
307 @Override
308 public StartingNode/* ! */getStartingNode()
309 throws AbstractZemucanException {
310 return this.startingNode;
311 }
312
313 /**
314 * Reads a grammar file, and builds a graph, and then, it is stored in the
315 * given position.
316 *
317 * @param constructor
318 * Grammar reader constructor.
319 * @param position
320 * Position
321 * @param graphs
322 * List of graphs to fill.
323 * @param grammarDelimiters
324 * Grammar delimiter.
325 * @param fileDescriptor
326 * Name of the file that contains the grammar.
327 * @return
328 * @throws AbstractZemucanException
329 */
330 private int readGraph(final Constructor<?> constructor, int position,
331 final List<Graph> graphs, final List<String> grammarDelimiters,
332 final String fileDescriptor) throws AbstractZemucanException {
333 final boolean extraNodes = !Boolean.parseBoolean(System
334 .getProperty(Constants.WITHOUT_EXTRA_NODES));
335 graphs.add(position, new Graph(fileDescriptor, extraNodes));
336 try {
337 //
338 final Object grammar = constructor.newInstance(
339 graphs.get(position), fileDescriptor);
340 // Creates the graph
341 final Graph newGraph = ((AbstractGrammarReader) grammar)
342 .generateGraph();
343 if (newGraph != graphs.get(position)) {
344 graphs.remove(position);
345 graphs.add(position, newGraph);
346 }
347 // Reads the graph.
348 ((AbstractGrammarReader) grammar).getStartingNode();
349 // Reads the globalDelimiters of the graph in the
350 // given file.
351 grammarDelimiters.add(position,
352 ((AbstractGrammarReader) grammar).getDelimiters());
353
354 position += 1;
355 } catch (final IllegalArgumentException e1) {
356 // I don't know how to tests this part. It's out of my
357 // scope.
358 throw new GeneralGrammarReaderProblemException(e1);
359 } catch (final InstantiationException e1) {
360 throw new GeneralGrammarReaderProblemException(e1);
361 } catch (final IllegalAccessException e1) {
362 // I don't know how to tests this part. It's out of my
363 // scope.
364 throw new GeneralGrammarReaderProblemException(e1);
365 } catch (final InvocationTargetException e1) {
366 throw new GeneralGrammarReaderProblemException(e1);
367 }
368 return position;
369 }
370 }