View Javadoc

1   /*
2    *   Firemox is a turn based strategy simulator
3    *   Copyright (C) 2003-2007 Fabrice Daugan
4    *
5    *   This program is free software; you can redistribute it and/or modify it 
6    * under the terms of the GNU General Public License as published by the Free 
7    * Software Foundation; either version 2 of the License, or (at your option) any
8    * later version.
9    *
10   *   This program is distributed in the hope that it will be useful, but WITHOUT 
11   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12   * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more 
13   * details.
14   *
15   *   You should have received a copy of the GNU General Public License along  
16   * with this program; if not, write to the Free Software Foundation, Inc., 
17   * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18   */
19  package net.sf.firemox.xml;
20  
21  import java.io.File;
22  import java.io.FileOutputStream;
23  import java.io.FilenameFilter;
24  import java.io.IOException;
25  import java.io.OutputStream;
26  import java.util.ArrayList;
27  import java.util.HashMap;
28  import java.util.List;
29  import java.util.Map;
30  
31  import net.sf.firemox.annotation.XmlTestElement;
32  import net.sf.firemox.token.IdConst;
33  import net.sf.firemox.tools.MToolKit;
34  import net.sf.firemox.tools.PairStringInt;
35  import net.sf.firemox.xml.XmlParser.Node;
36  
37  import org.apache.commons.io.FilenameUtils;
38  import org.apache.commons.io.filefilter.FileFilterUtils;
39  import org.apache.commons.lang.StringUtils;
40  import org.apache.commons.lang.WordUtils;
41  import org.kohsuke.args4j.CmdLineException;
42  import org.kohsuke.args4j.CmdLineParser;
43  import org.xml.sax.SAXException;
44  import org.xml.sax.SAXParseException;
45  
46  /***
47   * @author <a href="mailto:fabdouglas@users.sourceforge.net">Fabrice Daugan </a>
48   */
49  public class XmlConfiguration {
50  
51  	/***
52  	 * Constructor.
53  	 * 
54  	 * @param xmlFile
55  	 *          the XML source file.
56  	 * @param workingDir
57  	 *          the working directory.
58  	 * @param output
59  	 *          the output stream of MDB file.
60  	 * @exception SAXException
61  	 *              parsing error.
62  	 * @exception IOException
63  	 *              writing error.
64  	 */
65  	private XmlConfiguration(String xmlFile, String workingDir,
66  			OutputStream output) throws SAXException, IOException {
67  		initParser();
68  		synchronized (parser) {
69  			config = parser.parse(MToolKit.getResourceAsStream(xmlFile));
70  		}
71  		try {
72  			if (XmlTools.aliasMap == null) {
73  				XmlTools.aliasMap = new HashMap<String, Integer>(newIncludeInstance());
74  			}
75  			String tagName = config.getTag();
76  			config.addAttribute(new XmlParser.Attribute("workingDir", workingDir));
77  			config.addAttribute(new XmlParser.Attribute("xmlFile", xmlFile.replace(
78  					'//', '/')));
79  			XmlTbs.getTbsComponent(tagName).buildMdb(config, output);
80  		} catch (Exception e) {
81  			error("Component error with '" + config.getTag() + "'\n" + config);
82  			e.printStackTrace();
83  		}
84  	}
85  
86  	/***
87  	 * Build and return a parsed instance of given rules file.
88  	 * 
89  	 * @param rulesFile
90  	 *          the XML card file.
91  	 * @param workingDir
92  	 *          the working directory.
93  	 * @param output
94  	 *          the output stream of MD file.
95  	 * @return a parsed instance of given rules file.
96  	 * @exception SAXException
97  	 *              parsing error.
98  	 * @exception IOException
99  	 *              writing error.
100 	 */
101 	public static XmlConfiguration parseRules(String rulesFile,
102 			String workingDir, OutputStream output) throws SAXException, IOException {
103 		currentCard = null;
104 		return new XmlConfiguration(rulesFile, workingDir, output);
105 	}
106 
107 	/***
108 	 * Build and return a parsed instance of given card file.
109 	 * 
110 	 * @param cardFile
111 	 *          the XML card file.
112 	 * @param workingDir
113 	 *          the working directory.
114 	 * @param output
115 	 *          the output stream of MDB file.
116 	 * @return a parsed instance of given card file.
117 	 * @exception SAXException
118 	 *              parsing error.
119 	 * @exception IOException
120 	 *              writing error.
121 	 */
122 	public static XmlConfiguration parseCard(String cardFile, String workingDir,
123 			OutputStream output) throws SAXException, IOException {
124 		currentCard = FilenameUtils.getName(cardFile);
125 		return new XmlConfiguration(cardFile, workingDir, output);
126 	}
127 
128 	/***
129 	 * @throws SAXException
130 	 */
131 	private static synchronized void initParser() throws SAXException {
132 		if (parser != null) {
133 			return;
134 		}
135 		parser = new XmlParser(XmlConfiguration.getOptions().isXsdValidation());
136 	}
137 
138 	/***
139 	 * Add an error.
140 	 * 
141 	 * @param message
142 	 *          error message.
143 	 */
144 	public static void error(String message) {
145 		error(message, false);
146 	}
147 
148 	/***
149 	 * Add an error.
150 	 * 
151 	 * @param message
152 	 *          error message.
153 	 * @param dump
154 	 *          if <code>true</code>, the stack trace is printed.
155 	 */
156 	public static void error(String message, boolean dump) {
157 		if (currentCard != null) {
158 			System.out.println(currentCard);
159 			currentCard = null;
160 		}
161 		XmlConfiguration.error++;
162 		System.out.print("\tERROR ");
163 		System.out.println(message);
164 		if (dump) {
165 			new RuntimeException().printStackTrace(System.out);
166 		}
167 	}
168 
169 	/***
170 	 * Add an error.
171 	 * 
172 	 * @param message
173 	 *          error message.
174 	 */
175 	public static void info(String message) {
176 		System.out.println(message);
177 	}
178 
179 	/***
180 	 * Add an error.
181 	 * 
182 	 * @param message
183 	 *          error message.
184 	 */
185 	public static void warning(String message) {
186 		if (currentCard != null) {
187 			System.out.println(currentCard);
188 			currentCard = null;
189 		}
190 		XmlConfiguration.warning++;
191 		System.out.print("\tWARNING ");
192 		System.out.println(message);
193 	}
194 
195 	/***
196 	 * Add an uncompleted counter.
197 	 */
198 	public static void uncompleted() {
199 		if (currentCard != null) {
200 			System.out.println(currentCard);
201 			currentCard = null;
202 		}
203 		XmlConfiguration.uncompleted++;
204 		System.out.println("\t... uncompleted card");
205 	}
206 
207 	/***
208 	 * Add a fatal error message.
209 	 * 
210 	 * @param message
211 	 *          error message.
212 	 */
213 	public static void fatal(String message) {
214 		XmlConfiguration.error++;
215 		throw new RuntimeException(message);
216 	}
217 
218 	/***
219 	 * The current parsed card.
220 	 */
221 	private static String currentCard;
222 
223 	private static Options options;
224 
225 	/***
226 	 * Return the method name corresponding to the specified TAG.
227 	 * 
228 	 * @param tagName
229 	 * @return the method name corresponding to the specified TAG.
230 	 */
231 	static XmlToMDB getXmlClass(String tagName, Map<String, XmlToMDB> instances,
232 			Class<?> nameSpaceCall) {
233 		if (!nameSpaceCall.getSimpleName().startsWith("Xml"))
234 			throw new InternalError("Caller should be an Xml class : "
235 					+ nameSpaceCall);
236 
237 		XmlToMDB nodeClass = instances.get(tagName);
238 		if (nodeClass != null) {
239 			return nodeClass;
240 		}
241 
242 		String simpleClassName = StringUtils
243 				.capitalize(tagName.replaceAll("-", ""));
244 		String packageName = nameSpaceCall.getPackage().getName();
245 		String namespace = nameSpaceCall.getSimpleName().substring(3).toLowerCase();
246 		String className = packageName + "." + namespace + "." + simpleClassName;
247 		XmlToMDB result;
248 		try {
249 			result = (XmlToMDB) Class.forName(className).newInstance();
250 		} catch (Throwable e) {
251 			Class<?> mdbClass = null;
252 			simpleClassName = WordUtils.capitalize(tagName.replaceAll("-", " "))
253 					.replaceAll(" ", "");
254 			try {
255 				result = (XmlToMDB) Class.forName(
256 						packageName + "." + namespace + "." + simpleClassName)
257 						.newInstance();
258 			} catch (Throwable e1) {
259 				try {
260 					className = StringUtils.chomp(packageName, ".xml") + "." + namespace
261 							+ "." + simpleClassName;
262 					mdbClass = Class.forName(className);
263 					if (!mdbClass.isAnnotationPresent(XmlTestElement.class)) {
264 						result = (XmlToMDB) mdbClass.newInstance();
265 					} else {
266 						result = getAnnotedBuilder(mdbClass, tagName, packageName,
267 								namespace);
268 					}
269 				} catch (Throwable ei2) {
270 					error("Unsupported " + namespace + " '" + tagName + "'");
271 					result = DummyBuilder.instance();
272 				}
273 			}
274 		}
275 		instances.put(tagName, result);
276 		return result;
277 	}
278 
279 	/***
280 	 * @param mdbClass
281 	 * @throws IllegalAccessException
282 	 * @throws InstantiationException
283 	 * @throws ClassNotFoundException
284 	 */
285 	private static XmlToMDB getAnnotedBuilder(Class<?> mdbClass, String tagName,
286 			String packageName, String namespace) throws InstantiationException,
287 			IllegalAccessException, ClassNotFoundException {
288 		if (!mdbClass.isAnnotationPresent(XmlTestElement.class)) {
289 			error("Unsupported annoted " + namespace + " '" + tagName + "'");
290 			return DummyBuilder.instance();
291 		}
292 		XmlTestElement xmlElement = mdbClass.getAnnotation(XmlTestElement.class);
293 		XmlAnnoted annotedInstance = (XmlAnnoted) Class.forName(
294 				packageName + "." + namespace + ".XmlAnnoted").newInstance();
295 		annotedInstance.setXmlElement(xmlElement);
296 		annotedInstance.setAnnotedClass(mdbClass);
297 		return annotedInstance;
298 	}
299 
300 	/***
301 	 * Create a new object and configure it. A new object is created and
302 	 * configured.
303 	 * 
304 	 * @return The newly created configured object.
305 	 */
306 	private Map<String, Integer> newIncludeInstance() {
307 		// initialize export lists
308 		EXPORTED_PROPERTIES.clear();
309 		EXPORTED_TYPES.clear();
310 		EXPORTED_PHASES.clear();
311 		PROPERTY_PICTURES.clear();
312 
313 		Node aliases = XmlTools.getExternalizableNode(config, "aliases");
314 		Map<String, Integer> aliasMap = new HashMap<String, Integer>();
315 		// add the no-care value
316 		aliasMap.put("nocare", IdConst.NO_CARE);
317 		for (Node alias : aliases.getNodes("alias")) {
318 			aliasMap.put(alias.getAttribute("name"), Integer.parseInt(alias
319 					.getAttribute("value")));
320 			final String exportation = alias.getAttribute("export");
321 			if ("properties".equals(exportation)) {
322 				EXPORTED_PROPERTIES.add(new PairStringInt(alias.getAttribute("name"),
323 						Integer.parseInt(alias.getAttribute("value"))));
324 				// property-picture association
325 				final String picture = alias.getAttribute("picture");
326 				if (picture != null) {
327 					PROPERTY_PICTURES.put(Integer.parseInt(alias.getAttribute("value")),
328 							picture);
329 				}
330 			} else if ("damage-types".equals(exportation)) {
331 				EXPORTED_DAMAGE_TYPES.add(new PairStringInt(alias.getAttribute("name"),
332 						Integer.parseInt(alias.getAttribute("value"))));
333 			} else if ("types".equals(exportation)) {
334 				EXPORTED_TYPES.add(new PairStringInt(alias.getAttribute("name"),
335 						Integer.parseInt(alias.getAttribute("value"))));
336 			} else if ("phases".equals(exportation)) {
337 				EXPORTED_PHASES.add(new PairStringInt(alias.getAttribute("name"),
338 						Integer.parseInt(alias.getAttribute("value"))));
339 			}
340 		}
341 		return aliasMap;
342 	}
343 
344 	/***
345 	 * <ul>
346 	 * 2 modes:
347 	 * <li>Update the a MDB for specified TBS against the XML files (main file,
348 	 * cards and fragments). Arguments are : TBS_NAME</li>
349 	 * <li>Rebuild completely the MDB for specified TBS. Arguments are : -full
350 	 * TBS_NAME</li>
351 	 * </ul>
352 	 * 
353 	 * @param args
354 	 *          main arguments.
355 	 */
356 	public static void main(String... args) {
357 		options = new Options();
358 		final CmdLineParser parser = new CmdLineParser(options);
359 		try {
360 			parser.parseArgument(args);
361 		} catch (CmdLineException e) {
362 			// Display help
363 			info(e.getMessage());
364 			parser.setUsageWidth(80);
365 			parser.printUsage(System.out);
366 			System.exit(-1);
367 			return;
368 		}
369 
370 		if (options.isVersion()) {
371 			// Display version
372 			info("Version is " + IdConst.VERSION);
373 			System.exit(-1);
374 			return;
375 		}
376 
377 		if (options.isHelp()) {
378 			// Display help
379 			parser.setUsageWidth(80);
380 			parser.printUsage(System.out);
381 			System.exit(-1);
382 			return;
383 		}
384 
385 		warning = 0;
386 		uncompleted = 0;
387 		error = 0;
388 		long start = System.currentTimeMillis();
389 		XmlTools.initHashMaps();
390 		MToolKit.tbsName = options.getMdb();
391 		String xmlFile = MToolKit.getFile(
392 				IdConst.TBS_DIR + "/" + MToolKit.tbsName + ".xml", false)
393 				.getAbsolutePath();
394 		try {
395 			if (options.isForce()) {
396 				final File recycledDir = MToolKit.getTbsFile("recycled");
397 				if (!recycledDir.exists() || !recycledDir.isDirectory()) {
398 					recycledDir.mkdir();
399 				}
400 
401 				parseRules(xmlFile, MToolKit.getTbsFile("recycled").getAbsolutePath(),
402 						new FileOutputStream(MToolKit.getTbsFile(MToolKit.tbsName + ".mdb",
403 								false)));
404 			} else {
405 				// Check the up-to-date state of MDB
406 				final File file = MToolKit.getFile(IdConst.TBS_DIR + "/"
407 						+ MToolKit.tbsName + "/" + MToolKit.tbsName + ".mdb");
408 				final long lastModifiedMdb;
409 				if (file == null) {
410 					lastModifiedMdb = 0;
411 				} else {
412 					lastModifiedMdb = file.lastModified();
413 				}
414 				boolean update = false;
415 				// Check the up-to-date state of MDB against the main XML file
416 				if (MToolKit.getFile(xmlFile).lastModified() > lastModifiedMdb) {
417 					// The main XML file is newer than MDB
418 					System.out.println("MDB is out of date, " + xmlFile + " is newer");
419 					update = true;
420 				} else {
421 					final File fragmentDir = MToolKit.getTbsFile("");
422 					for (File frament : fragmentDir
423 							.listFiles((FilenameFilter) FileFilterUtils.andFileFilter(
424 									FileFilterUtils.suffixFileFilter("xml"), FileFilterUtils
425 											.prefixFileFilter("fragment-")))) {
426 						if (frament.lastModified() > lastModifiedMdb) {
427 							// One card is newer than MDB
428 							System.out
429 									.println("MDB is out of date, at least one fragment found : "
430 											+ frament.getName());
431 							update = true;
432 							break;
433 						}
434 					}
435 					if (!update) {
436 						// Check the up-to-date state of MDB against the cards
437 						final File recycledDir = MToolKit.getTbsFile("recycled");
438 						if (!recycledDir.exists() || !recycledDir.isDirectory()) {
439 							recycledDir.mkdir();
440 						}
441 						if (recycledDir.lastModified() > lastModifiedMdb) {
442 							// The recycled XML file is newer than MDB
443 							System.out
444 									.println("MDB is out of date, the recycled directory is new");
445 							update = true;
446 						} else {
447 							for (File card : recycledDir
448 									.listFiles((FilenameFilter) FileFilterUtils.andFileFilter(
449 											FileFilterUtils.suffixFileFilter("xml"), FileFilterUtils
450 													.notFileFilter(FileFilterUtils
451 															.suffixFileFilter(IdConst.FILE_DATABASE_SAVED))))) {
452 								if (card.lastModified() > lastModifiedMdb) {
453 									// One card is newer than MDB
454 									System.out
455 											.println("MDB is out of date, at least one new card found : "
456 													+ card);
457 									update = true;
458 									break;
459 								}
460 							}
461 						}
462 					}
463 				}
464 				if (!update) {
465 					return;
466 				}
467 				// Need to update the whole MDB
468 				parseRules(xmlFile, MToolKit.getTbsFile("recycled").getAbsolutePath(),
469 						new FileOutputStream(MToolKit.getTbsFile(MToolKit.tbsName + ".mdb",
470 								false)));
471 			}
472 		} catch (SAXParseException e) {
473 			// Ignore this error
474 		} catch (Exception e) {
475 			e.printStackTrace();
476 		}
477 		if (warning > 0) {
478 			System.out
479 					.println("\t" + warning + " warning" + (warning > 1 ? "s" : ""));
480 		}
481 		if (error > 0) {
482 			System.out.println("\t" + error + " error" + (error > 1 ? "s" : ""));
483 			System.out.println("Some cards have not been built correctly. Fix them.");
484 		} else {
485 			System.out.println("\tSuccessfull build");
486 		}
487 		System.out.println("\tTime : " + (System.currentTimeMillis() - start)
488 				/ 1000 + " s");
489 	}
490 
491 	/***
492 	 * 
493 	 */
494 	private static XmlParser parser;
495 
496 	/***
497 	 * 
498 	 */
499 	private XmlParser.Node config;
500 
501 	/***
502 	 * Exported properties
503 	 */
504 	public static final List<PairStringInt> EXPORTED_PROPERTIES = new ArrayList<PairStringInt>();
505 
506 	/***
507 	 * Exported properties
508 	 */
509 	public static final Map<Integer, String> PROPERTY_PICTURES = new HashMap<Integer, String>();
510 
511 	/***
512 	 * Exported types
513 	 */
514 	public static final List<PairStringInt> EXPORTED_TYPES = new ArrayList<PairStringInt>();
515 
516 	/***
517 	 * Exported phases
518 	 */
519 	public static final List<PairStringInt> EXPORTED_PHASES = new ArrayList<PairStringInt>();
520 
521 	/***
522 	 * Exported damage types
523 	 */
524 	public static final List<PairStringInt> EXPORTED_DAMAGE_TYPES = new ArrayList<PairStringInt>();
525 
526 	/***
527 	 * Found errors
528 	 */
529 	private static int error;
530 
531 	/***
532 	 * Found warnings
533 	 */
534 	private static int warning;
535 
536 	/***
537 	 * Found uncompleted cards
538 	 */
539 	static int uncompleted;
540 
541 	/***
542 	 * Indicates the debug data are saved in the MDB.
543 	 * 
544 	 * @return true if the debug data are saved in the MDB.
545 	 */
546 	public static boolean isDebugEnable() {
547 		return true; // TODO use configuration to enable/disable debug on MDB
548 	}
549 
550 	/***
551 	 * Return <code>true</code> if there is one or more errors.
552 	 * 
553 	 * @return <code>true</code> if there is one or more errors.
554 	 */
555 	public static boolean hasError() {
556 		return error != 0;
557 	}
558 
559 	/***
560 	 * Return options of builder.
561 	 * 
562 	 * @return options.
563 	 */
564 	public static Options getOptions() {
565 		return options;
566 	}
567 
568 	/***
569 	 * Return the no-Pay-Mana option.
570 	 * 
571 	 * @return the no-Pay-Mana option.
572 	 */
573 	public static boolean isNoPayMana() {
574 		return options.isNoPayMana();
575 	}
576 }