1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package net.sf.firemox.database;
20
21 import java.awt.Image;
22 import java.awt.image.BufferedImage;
23 import java.io.File;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.PrintWriter;
27 import java.lang.reflect.AccessibleObject;
28 import java.lang.reflect.Field;
29 import java.util.ArrayList;
30 import java.util.HashMap;
31 import java.util.List;
32 import java.util.Map;
33
34 import net.sf.firemox.clickable.target.card.CardModel;
35 import net.sf.firemox.database.data.StringData;
36 import net.sf.firemox.database.propertyconfig.Cache;
37 import net.sf.firemox.database.propertyconfig.PropertyConfig;
38 import net.sf.firemox.database.propertyconfig.PropertyConfigFactory;
39 import net.sf.firemox.token.IdConst;
40 import net.sf.firemox.tools.Configuration;
41 import net.sf.firemox.tools.FileFilterPlus;
42 import net.sf.firemox.tools.Log;
43 import net.sf.firemox.tools.MToolKit;
44 import net.sf.firemox.xml.XmlParser;
45 import net.sf.firemox.xml.XmlTools;
46 import net.sf.firemox.xml.XmlParser.Node;
47
48 import org.apache.commons.io.IOUtils;
49 import org.xml.sax.SAXException;
50
51 import sun.awt.image.FileImageSource;
52 import sun.awt.image.URLImageSource;
53
54 /***
55 * @author <a href="mailto:fabdouglas@users.sourceforge.net">Fabrice Daugan </a>
56 * @since 0.90
57 */
58 public final class DatabaseFactory {
59
60 private static final String CACHE_FILE_HEADER = "<?xml version='1.0' encoding='ISO-8859-1'?>";
61
62 /***
63 * Unique instance of a XML database.
64 */
65 private static XmlParser.Node config;
66
67 /***
68 * The loaded proxy configurations. The order determine priorities.
69 */
70 public static Proxy[] pictureProxies;
71
72 /***
73 * The loaded proxy configurations. The order determine priorities.
74 */
75 public static Proxy[] dataProxies;
76
77 /***
78 * A blank blacked-image.
79 */
80 public static Image blankImage;
81
82 /***
83 * Available properties of current TBS for databases.
84 */
85 public static Map<String, PropertyConfig> propertiesCacheConfig;
86
87 /***
88 * The common back picture of the cards of the selected turn based system. Is
89 * <code>null</code> while this factory has not been initialized.
90 *
91 * @see #init(InputStream)
92 */
93 public static Image backImage;
94
95 /***
96 * The common scaled back picture of the cards of the selected turn based
97 * system. Is <code>null</code> while this factory has not been initialized.
98 */
99 public static BufferedImage scaledBackImage;
100
101 /***
102 * The picture representing a damage for the selected turn based system. Is
103 * <code>null</code> while this factory has not been initialized.
104 *
105 * @see #init(InputStream)
106 */
107 public static Image damageImage;
108
109 /***
110 * The picture representing a damage for the selected turn based system. Is
111 * <code>null</code> while this factory has not been initialized.
112 *
113 * @see #init(InputStream)
114 */
115 public static Image damageScaledImage;
116
117 /***
118 * The source file field allowing to access the source file/URL of any
119 * picture.
120 */
121 static Field sourceFile;
122
123 static Field sourceUrl;
124
125 /***
126 * All created instances of DatabaseCard.
127 */
128 private static Map<Node, DatabaseCard> databaseCardCache = new HashMap<Node, DatabaseCard>();
129
130 /***
131 * Return the DatabaseCard object from the given card name. This object is
132 * build from the cache or the available proxies if cache is empty for this
133 * card. If there is no available proxy or if all all failed, the null value
134 * is returned. In case of success, the cache is updated and further proxy
135 * call would be done.
136 *
137 * @param pictureName
138 * the picture name associated to the database object. May be null to
139 * use the picture associated to the given card instead.
140 * @param cardModel
141 * the card name (no translated one)
142 * @param constraints
143 * constraints set {key,value}. May be null.
144 * @return the DatabaseCard object from the given card name.
145 */
146 public static DatabaseCard getDatabase(String pictureName,
147 CardModel cardModel, Map<String, String> constraints) {
148
149
150 DatabaseCard cacheData = null;
151 try {
152 cacheData = getDatabaseFromCache(cardModel, constraints);
153 } catch (Exception e1) {
154 e1.printStackTrace();
155 }
156
157 if (cacheData != null && cacheData.isConsistent()) {
158
159 if (pictureName != null && pictureName.length() > 0) {
160
161 cacheData = cacheData.clone(pictureName);
162 }
163 return cacheData;
164 }
165
166
167
168
169 for (Proxy dataProxy : dataProxies) {
170 if (dataProxy != null) {
171 try {
172
173 final DatabaseCard res = getDatabaseFromProxy(dataProxy, cardModel,
174 constraints);
175 if (res == null) {
176 return cacheData;
177 }
178 res.setPictureProxies(pictureProxies);
179
180
181 if (constraints != null) {
182 for (Map.Entry<String, String> key : constraints.entrySet()) {
183 res.add(new StringData(new Cache(key.getKey()), key.getValue()));
184 }
185 }
186 updateCache(res);
187 return res;
188 } catch (Exception e) {
189
190 e.printStackTrace();
191 Log.info("Proxy-data failure for " + cardModel + " : "
192 + dataProxy.getName() + ", error=" + e.getMessage());
193 }
194 }
195 }
196
197 return null;
198 }
199
200 /***
201 * Retrieve data from the cache. If this card is not stored in cache, null
202 * value is returned.
203 *
204 * @param cardName
205 * the card name (no translated one)
206 * @param constraints
207 * constraints set{key,value}. May be null.
208 * @return the DatabaseCard object from the given card name from the cache.
209 */
210 private static DatabaseCard getDatabaseFromCache(CardModel cardModel,
211 Map<String, String> constraints) throws IOException, SAXException {
212 if (config == null) {
213 final XmlParser parser = new XmlParser();
214 Configuration.loadTemplateTbsFile(IdConst.FILE_DATABASE_SAVED);
215 config = parser.parse(MToolKit.getTbsUrl(IdConst.FILE_DATABASE_SAVED)
216 .getPath());
217 }
218
219 final List<Node> cachedInstances = config.getNodes(cardModel.getKeyName());
220 if (cachedInstances != null && !cachedInstances.isEmpty()) {
221 final List<Node> validateCards = new ArrayList<Node>();
222
223
224 int bestScore = 0;
225 for (Node cachedCard : cachedInstances) {
226
227 int score = 0;
228 if (constraints != null && !constraints.isEmpty()) {
229 for (Map.Entry<String, String> constraintKey : constraints.entrySet()) {
230 final String constraintValue = constraintKey.getValue();
231 if (constraintValue == null || constraintValue.length() == 0) {
232
233 throw new InternalError("No value associated to property '"
234 + constraintKey.getKey() + "'");
235 }
236
237 final List<Node> properties = cachedCard.getNodes("property");
238 if (properties != null && !properties.isEmpty()) {
239 for (Node property : properties) {
240 if (property.getAttribute("name").equalsIgnoreCase(
241 constraintKey.getKey())
242 && property.getAttribute("value").equalsIgnoreCase(
243 constraintValue)) {
244
245 score++;
246 break;
247 }
248 }
249 }
250 }
251 } else {
252 validateCards.add(cachedCard);
253 break;
254 }
255 if (score > bestScore) {
256 bestScore = score;
257 validateCards.clear();
258 validateCards.add(cachedCard);
259 } else if (score == bestScore) {
260 validateCards.add(cachedCard);
261 }
262 }
263 if (!validateCards.isEmpty()) {
264
265 Node preferredData = null;
266 if (dataProxies == null) {
267 return getDatabaseObjectCard(cardModel, cachedInstances.get(0), true);
268 }
269 for (Proxy dataProxy : dataProxies) {
270 for (Node node : validateCards) {
271 if (dataProxy.getName().equals(node.getAttribute("proxy"))) {
272 preferredData = node;
273 break;
274 }
275 }
276 }
277
278 if (preferredData == null) {
279
280 preferredData = validateCards.get(0);
281 }
282 if (constraints == null) {
283 return getDatabaseObjectCard(cardModel, preferredData, true);
284 }
285 return getDatabaseObjectCard(cardModel, preferredData,
286 bestScore >= constraints.size());
287 }
288
289
290
291
292
293
294
295 }
296
297 return null;
298 }
299
300 /***
301 * Retrieve data from the given proxy. If this card is not available throw
302 * this proxy, null value is returned.
303 *
304 * @param proxy
305 * the proxy retrieving data
306 * @param cardName
307 * the card name (no translated one)
308 * @param constraints
309 * constraints set{key,value}. May be null.
310 * @throws IOException
311 * If some other I/O error occurs
312 * @return the built database object.
313 */
314 private static DatabaseCard getDatabaseFromProxy(Proxy proxy,
315 CardModel cardModel, Map<String, String> constraints) throws IOException {
316 return proxy.getDatabaseFromStream(cardModel, constraints);
317 }
318
319 private static DatabaseCard getDatabaseObjectCard(CardModel cardModel,
320 Node preferredData, boolean consistent) {
321 DatabaseCard databaseCard = databaseCardCache.get(preferredData);
322 if (databaseCard != null) {
323
324 databaseCard.updateCardModel(cardModel);
325 return databaseCard;
326 }
327 cardModel.setLocalName(preferredData.getAttribute("local-name"));
328 databaseCard = new DatabaseCard(cardModel, getProxy(preferredData
329 .getAttribute("proxy")), pictureProxies);
330 databaseCard.setConsistent(consistent);
331
332 if (preferredData.aList != null) {
333 for (Object node : preferredData.aList) {
334 if (node != null && node instanceof Node) {
335
336 databaseCard.add(propertiesCacheConfig.get(
337 ((Node) node).getAttribute("name")).parseProperty(
338 cardModel.getCardName(), (Node) node));
339 }
340 }
341 }
342
343 databaseCardCache.put(preferredData, databaseCard);
344 return databaseCard;
345 }
346
347 /***
348 * Return the proxy object giving it's name.<code>null</code> value is
349 * returned if the proxy has not been found or if the <code>proxyName</code>
350 * was <code>null</code>, empty, or no proxy were loaded (occurs in
351 * initComponent() method calls).
352 *
353 * @param proxyName
354 * the poxy's name
355 * @return the proxy object if found, <code>null</code> otherwise.
356 */
357 public static Proxy getProxy(String proxyName) {
358 if (proxyName != null && proxyName.length() > 0 && dataProxies != null) {
359 for (Proxy proxy : dataProxies) {
360 if (proxy != null && proxyName.equals(proxy.getName())) {
361 return proxy;
362 }
363 }
364 }
365 return null;
366 }
367
368 /***
369 * Initialize property configurations from the dbStream. Existing proxies are
370 * also initialized.
371 * <ul>
372 * Structure of Stream : Data[size]
373 * <li>properties[] [...]</li>
374 * </ul>
375 *
376 * @param dbStream
377 * the MDB file containing rules
378 * @throws IOException
379 * error during the database configuration.
380 */
381 public static void init(InputStream dbStream) throws IOException {
382 String[] dataProxyNameOrders = Configuration.getString(
383 "database.dataOrder", "").split("//|");
384 String[] pictureProxyNameOrders = Configuration.getString(
385 "database.pictureOrder", "").split("//|");
386 int count = dbStream.read();
387 propertiesCacheConfig = new HashMap<String, PropertyConfig>(count);
388 while (count-- > 0) {
389 PropertyConfig propertyConfig = PropertyConfigFactory
390 .getPropertyConfig(dbStream);
391 propertiesCacheConfig.put(propertyConfig.getName(), propertyConfig);
392 }
393
394
395 final File proxiesLocation = MToolKit.getTbsFile(IdConst.PROXIES_LOCATION);
396 final File[] lproxies;
397 if (proxiesLocation == null) {
398 Log.warn("The proxy directory '"
399 + MToolKit.getTbsFile(IdConst.PROXIES_LOCATION, false)
400 + "' does not exist");
401 lproxies = new File[0];
402 } else {
403 lproxies = proxiesLocation.listFiles(new FileFilterPlus("xml"));
404 }
405 dataProxies = new Proxy[lproxies.length];
406 pictureProxies = new Proxy[lproxies.length];
407
408 XmlTools.initHashMaps();
409
410
411 for (int i = lproxies.length; i-- > 0;) {
412 try {
413 dataProxies[i] = new Proxy(lproxies[i]);
414 pictureProxies[i] = dataProxies[i];
415 } catch (Exception e) {
416 e.printStackTrace();
417 }
418 }
419
420
421 int index = 0;
422 for (String dataProxyNameOrder : dataProxyNameOrders) {
423 for (int j = 0; j < dataProxies.length; j++) {
424 if (dataProxyNameOrder.equalsIgnoreCase(dataProxies[j].getXmlName())) {
425 final Proxy oldProxy = dataProxies[index];
426 dataProxies[index] = dataProxies[j];
427 dataProxies[j] = oldProxy;
428 index++;
429 break;
430 }
431 }
432 }
433 index = 0;
434 for (String pictureProxyNameOrder : pictureProxyNameOrders) {
435 for (int j = 0; j < pictureProxies.length; j++) {
436 if (pictureProxyNameOrder.equalsIgnoreCase(pictureProxies[j]
437 .getXmlName())) {
438 final Proxy oldProxy = pictureProxies[index];
439 pictureProxies[index] = pictureProxies[j];
440 pictureProxies[j] = oldProxy;
441 index++;
442 break;
443 }
444 }
445 }
446
447
448 try {
449 sourceFile = FileImageSource.class.getDeclaredField("imagefile");
450 sourceUrl = URLImageSource.class.getDeclaredField("url");
451 AccessibleObject.setAccessible(new AccessibleObject[] { sourceFile },
452 true);
453 AccessibleObject
454 .setAccessible(new AccessibleObject[] { sourceUrl }, true);
455 } catch (Exception e) {
456 e.printStackTrace();
457 }
458
459 XmlTools.clean();
460 }
461
462 /***
463 * Save the cache into the database XML file.
464 */
465 public static void saveCache() {
466 if (config != null) {
467 try {
468 PrintWriter printer = new PrintWriter(Configuration
469 .loadTemplateTbsFile(IdConst.FILE_DATABASE_SAVED));
470 printer.println(CACHE_FILE_HEADER);
471 config.print(printer);
472 IOUtils.closeQuietly(printer);
473 } catch (IOException e) {
474
475 e.printStackTrace();
476 }
477 }
478 }
479
480 /***
481 * Update the cache with the given data.
482 *
483 * @param databaseCard
484 * the data of a card to cache in our XML data base.
485 */
486 private static void updateCache(DatabaseCard databaseCard) {
487 if (config == null) {
488 throw new IllegalStateException("Cache should be initialized");
489 }
490 databaseCard.updateCache(config);
491 }
492
493 /***
494 * Prevent this class to be instantiated.
495 */
496 private DatabaseFactory() {
497
498 }
499 }