View Javadoc

1   /*
2    *   Magic-Project 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.magicproject.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.InputStream;
26  import java.io.OutputStream;
27  import java.util.ArrayList;
28  import java.util.HashMap;
29  import java.util.List;
30  import java.util.Map;
31  
32  import net.sf.magicproject.annotation.XmlTestElement;
33  import net.sf.magicproject.token.IdConst;
34  import net.sf.magicproject.tools.MToolKit;
35  import net.sf.magicproject.tools.PairStringInt;
36  import net.sf.magicproject.xml.XmlParser.Node;
37  
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.xml.sax.SAXException;
42  import org.xml.sax.SAXParseException;
43  
44  /***
45   * @author <a href="mailto:fabdouglas@users.sourceforge.net">Fabrice Daugan </a>
46   */
47  public class XmlConfiguration {
48  
49  	/***
50  	 * Is schema validation is activated.
51  	 */
52  	public static boolean validationOn = false;
53  
54  	/***
55  	 * Constructor.
56  	 * 
57  	 * @param configuration
58  	 *          An input stream containing a complete e.g. configuration file
59  	 * @exception SAXException
60  	 *              parsing error.
61  	 * @exception IOException
62  	 *              writing error.
63  	 */
64  	public XmlConfiguration(InputStream configuration) throws SAXException,
65  			IOException {
66  		initParser();
67  		synchronized (parser) {
68  			config = parser.parse(configuration);
69  		}
70  	}
71  
72  	/***
73  	 * 
74  	 */
75  	private static synchronized void initParser() {
76  		if (parser != null) {
77  			return;
78  		}
79  		parser = new XmlParser();
80  	}
81  
82  	/***
83  	 * Create a new object and configure it. A new object is created and
84  	 * configured.
85  	 * 
86  	 * @param xmlFile
87  	 *          the xml source file.
88  	 * @param workingDir
89  	 *          the working dir.
90  	 * @param output
91  	 *          the output strem of mdb file.
92  	 */
93  	public void newInstance(String xmlFile, String workingDir, OutputStream output) {
94  		try {
95  			if (XmlTools.aliasMap == null) {
96  				XmlTools.aliasMap = new HashMap<String, Integer>(newIncludeInstance());
97  			}
98  			String tagName = config.getTag();
99  			config.addAttribute(new XmlParser.Attribute("workingDir", workingDir));
100 			config.addAttribute(new XmlParser.Attribute("xmlFile", xmlFile.replace(
101 					'//', '/')));
102 			XmlTbs.getTbsComponent(tagName).buildMdb(config, output);
103 		} catch (Exception e) {
104 			e.printStackTrace();
105 		}
106 	}
107 
108 	/***
109 	 * Return the method name corresponding to the specified TAG.
110 	 * 
111 	 * @param tagName
112 	 * @return the method name corresponding to the specified TAG.
113 	 */
114 	static XmlToMDB getXmlClass(String tagName, Map<String, XmlToMDB> instances,
115 			Class<?> nameSpaceCall) {
116 		if (!nameSpaceCall.getSimpleName().startsWith("Xml"))
117 			throw new InternalError("Caller should be an Xml class : "
118 					+ nameSpaceCall);
119 
120 		XmlToMDB nodeClass = instances.get(tagName);
121 		if (nodeClass != null) {
122 			return nodeClass;
123 		}
124 
125 		String simpleClassName = StringUtils
126 				.capitalize(tagName.replaceAll("-", ""));
127 		String packageName = nameSpaceCall.getPackage().getName();
128 		String namespace = nameSpaceCall.getSimpleName().substring(3).toLowerCase();
129 		String className = packageName + "." + namespace + "." + simpleClassName;
130 		XmlToMDB result;
131 		try {
132 			result = (XmlToMDB) Class.forName(className).newInstance();
133 		} catch (Throwable e) {
134 			try {
135 				Class<?> mdbClass = null;
136 				simpleClassName = WordUtils.capitalize(tagName.replaceAll("-", " "))
137 						.replaceAll(" ", "");
138 				className = StringUtils.chomp(packageName, ".xml") + "." + namespace
139 						+ "." + simpleClassName;
140 				mdbClass = Class.forName(className);
141 				result = getAnnotedBuilder(mdbClass, tagName, packageName, namespace);
142 			} catch (Throwable ei2) {
143 				XmlConfiguration.error++;
144 				System.out.println("\t>> Unsupported " + namespace + " '" + tagName
145 						+ "'");
146 				result = DummyBuilder.instance();
147 			}
148 		}
149 		instances.put(tagName, result);
150 		return result;
151 	}
152 
153 	/***
154 	 * @param mdbClass
155 	 * @throws IllegalAccessException
156 	 * @throws InstantiationException
157 	 * @throws ClassNotFoundException
158 	 */
159 	private static XmlToMDB getAnnotedBuilder(Class<?> mdbClass, String tagName,
160 			String packageName, String namespace) throws InstantiationException,
161 			IllegalAccessException, ClassNotFoundException {
162 		if (!mdbClass.isAnnotationPresent(XmlTestElement.class)) {
163 			XmlConfiguration.error++;
164 			System.out.println("\t>> Unsupported annoted " + namespace + " '"
165 					+ tagName + "'");
166 			return DummyBuilder.instance();
167 		}
168 		XmlTestElement xmlElement = mdbClass.getAnnotation(XmlTestElement.class);
169 		XmlAnnoted annotedInstance = (XmlAnnoted) Class.forName(
170 				packageName + "." + namespace + ".XmlAnnoted").newInstance();
171 		annotedInstance.setXmlElement(xmlElement);
172 		annotedInstance.setAnnotedClass(mdbClass);
173 		return annotedInstance;
174 	}
175 
176 	/***
177 	 * Create a new object and configure it. A new object is created and
178 	 * configured.
179 	 * 
180 	 * @return The newly created configured object.
181 	 */
182 	private Map<String, Integer> newIncludeInstance() {
183 		// init exportation lists
184 		exportedProperties.clear();
185 		exportedTypes.clear();
186 		exportedPhases.clear();
187 		propertyPictures.clear();
188 
189 		Node aliases = XmlTools.getExternalizableNode(config, "aliases");
190 		Map<String, Integer> aliasMap = new HashMap<String, Integer>();
191 		// add the nocare value
192 		aliasMap.put("nocare", IdConst.NO_CARE);
193 		for (Node alias : aliases.getNodes("alias")) {
194 			aliasMap.put(alias.getAttribute("name"), Integer.parseInt(alias
195 					.getAttribute("value")));
196 			final String exportation = alias.getAttribute("export");
197 			if ("properties".equals(exportation)) {
198 				exportedProperties.add(new PairStringInt(alias.getAttribute("name"),
199 						Integer.parseInt(alias.getAttribute("value"))));
200 				// property-picture association
201 				final String picture = alias.getAttribute("picture");
202 				if (picture != null) {
203 					propertyPictures.put(Integer.parseInt(alias.getAttribute("value")),
204 							picture);
205 				}
206 			} else if ("damage-types".equals(exportation)) {
207 				exportedDamageTypes.add(new PairStringInt(alias.getAttribute("name"),
208 						Integer.parseInt(alias.getAttribute("value"))));
209 			} else if ("types".equals(exportation)) {
210 				exportedTypes.add(new PairStringInt(alias.getAttribute("name"), Integer
211 						.parseInt(alias.getAttribute("value"))));
212 			} else if ("phases".equals(exportation)) {
213 				exportedPhases.add(new PairStringInt(alias.getAttribute("name"),
214 						Integer.parseInt(alias.getAttribute("value"))));
215 			}
216 		}
217 		return aliasMap;
218 	}
219 
220 	/***
221 	 * <ul>
222 	 * 2 modes:
223 	 * <li>Update the a MDB for specified TBS against the XML files (main file,
224 	 * cards and fragments). Arguments are : TBS_NAME</li>
225 	 * <li>Rebuild completely the MDB for specified TBS. Arguments are : -full
226 	 * TBS_NAME</li>
227 	 * </ul>
228 	 * 
229 	 * @param args
230 	 *          main arguments.
231 	 */
232 	public static void main(String... args) {
233 		String res = "";
234 		warning = 0;
235 		uncompleted = 0;
236 		error = 0;
237 		for (String arg : args) {
238 			res += ", " + arg;
239 		}
240 		long start = System.currentTimeMillis();
241 		XmlTools.initHashMaps();
242 		try {
243 			if (args.length >= 2 && args[0].startsWith("-full")) {
244 				validationOn = !args[0].equalsIgnoreCase("-fullO");
245 				MToolKit.tbsName = args[args.length - 1];
246 				// / rebuild completely the current TBS <br>
247 				noPayMana = args.length > 2 && "-nopaymana".equals(args[1]);
248 				final File recycledDir = new File(IdConst.TBS_DIR + "/"
249 						+ MToolKit.tbsName + "/recycled");
250 				if (!recycledDir.exists() || !recycledDir.isDirectory()) {
251 					recycledDir.mkdir();
252 				}
253 
254 				String xmlFile = IdConst.TBS_DIR + "/" + MToolKit.tbsName + ".xml";
255 
256 				new XmlConfiguration(MToolKit.getResourceAsStream(xmlFile))
257 						.newInstance(xmlFile, IdConst.TBS_DIR + "/" + MToolKit.tbsName
258 								+ "/recycled", new FileOutputStream(MToolKit.getTbsFile(
259 								MToolKit.tbsName + ".mdb", false)));
260 			} else if (args.length == 3 && args[0].startsWith("-update")) {
261 				// Check the up-to-date state of MDB
262 				MToolKit.tbsName = args[1];
263 				String xmlFile = IdConst.TBS_DIR + "/" + MToolKit.tbsName + ".xml";
264 				long lastModifiedMdb = Long.parseLong(args[2]);
265 				boolean update = false;
266 				// Check the up-to-date state of MDB aginst the main XML file
267 				if (new File(xmlFile).lastModified() > lastModifiedMdb) {
268 					// The main XML file is newer than MDB
269 					System.out.println("MDB is out of date, " + xmlFile + " is newer");
270 					update = true;
271 				} else {
272 					final File fragmentDir = new File(IdConst.TBS_DIR + "/"
273 							+ MToolKit.tbsName);
274 					for (File frament : fragmentDir
275 							.listFiles((FilenameFilter) FileFilterUtils.andFileFilter(
276 									FileFilterUtils.suffixFileFilter("xml"), FileFilterUtils
277 											.prefixFileFilter("fragment-")))) {
278 						if (frament.lastModified() > lastModifiedMdb) {
279 							// One card is newer than MDB
280 							System.out
281 									.println("MDB is out of date, at least one fragment found : "
282 											+ frament.getName());
283 							update = true;
284 							break;
285 						}
286 					}
287 					if (!update) {
288 						// Check the up-to-date state of MDB against the cards
289 						final File recycledDir = new File(IdConst.TBS_DIR + "/"
290 								+ MToolKit.tbsName + "/recycled");
291 						if (!recycledDir.exists() || !recycledDir.isDirectory()) {
292 							recycledDir.mkdir();
293 						}
294 						if (recycledDir.lastModified() > lastModifiedMdb) {
295 							// The recycled XML file is newer than MDB
296 							System.out
297 									.println("MDB is out of date, the recycled directory is new");
298 							update = true;
299 						} else {
300 							for (File card : recycledDir
301 									.listFiles((FilenameFilter) FileFilterUtils.andFileFilter(
302 											FileFilterUtils.suffixFileFilter("xml"), FileFilterUtils
303 													.notFileFilter(FileFilterUtils
304 															.suffixFileFilter(IdConst.FILE_DATABASE_SAVED))))) {
305 								if (card.lastModified() > lastModifiedMdb) {
306 									// One card is newer than MDB
307 									System.out
308 											.println("MDB is out of date, at least one new card found : "
309 													+ card);
310 									update = true;
311 									break;
312 								}
313 							}
314 						}
315 					}
316 				}
317 				if (!update) {
318 					return;
319 				}
320 				// Need to update the whole MDB
321 				new XmlConfiguration(MToolKit.getResourceAsStream(xmlFile))
322 						.newInstance(xmlFile, IdConst.TBS_DIR + "/" + MToolKit.tbsName
323 								+ "/recycled", new FileOutputStream(IdConst.TBS_DIR + "/"
324 								+ MToolKit.tbsName + "/" + MToolKit.tbsName + ".mdb"));
325 			} else {
326 				// one card to update
327 				for (String arg : args) {
328 					new XmlConfiguration(MToolKit.getResourceAsStream(arg)).newInstance(
329 							arg, MToolKit.getFile(arg).getParentFile().getCanonicalPath(),
330 							new FileOutputStream(MToolKit.getFile(arg + ".mdb")));
331 				}
332 			}
333 		} catch (SAXParseException e) {
334 			// Ignore this error
335 		} catch (Exception e) {
336 			e.printStackTrace();
337 		}
338 		if (warning > 0) {
339 			System.out
340 					.println("\t" + warning + " warning" + (warning > 1 ? "s" : ""));
341 		}
342 		if (error > 0) {
343 			System.out.println("\t" + error + " error" + (error > 1 ? "s" : ""));
344 			System.out.println("Some cards have not been built correctly. Fix them.");
345 		} else {
346 			System.out.println("\tSuccessfull build");
347 		}
348 		System.out.println("\tTime : " + (System.currentTimeMillis() - start)
349 				/ 1000 + " s");
350 	}
351 
352 	/***
353 	 * 
354 	 */
355 	private static XmlParser parser;
356 
357 	/***
358 	 * 
359 	 */
360 	private XmlParser.Node config;
361 
362 	/***
363 	 * Exported properties
364 	 */
365 	public static List<PairStringInt> exportedProperties = new ArrayList<PairStringInt>();
366 
367 	/***
368 	 * Exported properties
369 	 */
370 	public static Map<Integer, String> propertyPictures = new HashMap<Integer, String>();
371 
372 	/***
373 	 * Exported types
374 	 */
375 	public static List<PairStringInt> exportedTypes = new ArrayList<PairStringInt>();
376 
377 	/***
378 	 * Exported phases
379 	 */
380 	public static List<PairStringInt> exportedPhases = new ArrayList<PairStringInt>();
381 
382 	/***
383 	 * Exported damage types
384 	 */
385 	public static List<PairStringInt> exportedDamageTypes = new ArrayList<PairStringInt>();
386 
387 	/***
388 	 * Indicates that the next 'paymana' actions would be played the parameter
389 	 * 'colorless="0"'
390 	 */
391 	public static boolean noPayMana;
392 
393 	/***
394 	 * Found errors
395 	 */
396 	public static int error;
397 
398 	/***
399 	 * Found warnings
400 	 */
401 	public static int warning;
402 
403 	/***
404 	 * Found uncompleted cards
405 	 */
406 	public static int uncompleted;
407 
408 	/***
409 	 * Indicates the debug data are saved in the mdb.
410 	 * 
411 	 * @return true if the debug data are saved in the mdb.
412 	 */
413 	public static boolean isDebugEnable() {
414 		return true; // TODO use configuration to enable/disable debug on mdb
415 	}
416 }