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