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.database;
20  
21  import java.awt.Graphics;
22  import java.awt.Image;
23  import java.awt.image.ImageProducer;
24  import java.net.MalformedURLException;
25  import java.net.URL;
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.clickable.target.card.AbstractCard;
32  import net.sf.firemox.clickable.target.card.CardModel;
33  import net.sf.firemox.clickable.target.card.CardModelLazy;
34  import net.sf.firemox.clickable.target.card.MCard;
35  import net.sf.firemox.database.data.TranslatableData;
36  import net.sf.firemox.deckbuilder.MdbLoader;
37  import net.sf.firemox.management.MonitorListener;
38  import net.sf.firemox.management.MonitoredCheckContent;
39  import net.sf.firemox.tools.Log;
40  import net.sf.firemox.tools.MToolKit;
41  import net.sf.firemox.tools.Picture;
42  import net.sf.firemox.xml.XmlParser;
43  import net.sf.firemox.xml.XmlParser.Node;
44  import sun.awt.image.FileImageSource;
45  import sun.awt.image.URLImageSource;
46  
47  /***
48   * @author <a href="mailto:fabdouglas@users.sourceforge.net">Fabrice Daugan </a>
49   * @since 0.90
50   */
51  public class DatabaseCard {
52  
53  	private Map<String, TranslatableData> data = new HashMap<String, TranslatableData>(
54  			5);
55  
56  	/***
57  	 * The proxy used to build this object.
58  	 */
59  	private final Proxy dataProxy;
60  
61  	/***
62  	 * The proxy used to display picture of this object. If is null,
63  	 * <code>dataProxy</code> will be used.
64  	 */
65  	private Proxy[] pictureProxies;
66  
67  	/***
68  	 * The currently associated image of this card. Is <code>null</code> while
69  	 * not loaded.
70  	 */
71  	private MonitoredCheckContent image;
72  
73  	/***
74  	 * The currently associated scaled image of this card. Is <code>null</code>
75  	 * while not loaded.
76  	 */
77  	private Image scaledImage;
78  
79  	/***
80  	 * The card model of this card.
81  	 */
82  	private CardModel cardModel;
83  
84  	/***
85  	 * The consistency of this database.
86  	 */
87  	private boolean consistent;
88  
89  	/***
90  	 * Create new instance of DatabaseCard.
91  	 * 
92  	 * @param cardModel
93  	 *          the card model containing card name used as id
94  	 * @param dataProxy
95  	 *          the proxy where this data came from. If is <code>null</code>,
96  	 *          the picture would be get using the default art URL.
97  	 * @param pictureProxies
98  	 *          the proxies used to get image. If is <code>null</code>,
99  	 *          <code>dataProxy</code> would be used.
100 	 */
101 	public DatabaseCard(CardModel cardModel, Proxy dataProxy,
102 			Proxy... pictureProxies) {
103 		this.cardModel = cardModel;
104 		this.dataProxy = dataProxy;
105 		setPictureProxies(pictureProxies);
106 	}
107 
108 	/***
109 	 * Return the card model of this card.
110 	 * 
111 	 * @return the card model of this card.
112 	 */
113 	public CardModel getCardModel() {
114 		return cardModel;
115 	}
116 
117 	/***
118 	 * Return the proxy used to build this object.
119 	 * 
120 	 * @return the proxy used to build this object.
121 	 */
122 	public Proxy getDataProxy() {
123 		return dataProxy;
124 	}
125 
126 	/***
127 	 * Return the proxy used to display picture of this object. If is null,
128 	 * <code>dataProxy</code> will be used.
129 	 * 
130 	 * @return the proxy used to display picture of this object. If is null,
131 	 *         <code>dataProxy</code> will be used.
132 	 */
133 	public Proxy getPictureProxy() {
134 		return pictureProxies[0];
135 	}
136 
137 	/***
138 	 * Return the proxies used to display picture of this object. If is null,
139 	 * <code>dataProxy</code> will be used.
140 	 * 
141 	 * @return the proxies used to display picture of this object. If is null,
142 	 *         <code>dataProxy</code> will be used.
143 	 */
144 	public Proxy[] getPictureProxies() {
145 		return pictureProxies;
146 	}
147 
148 	/***
149 	 * The card name. Is considered as id. Return the English name, not the
150 	 * localized one.
151 	 * 
152 	 * @return the card name. Is considered as id.
153 	 * @see net.sf.firemox.clickable.target.card.CardModel#getCardName()
154 	 */
155 	public String getCardName() {
156 		return cardModel.getCardName();
157 	}
158 
159 	/***
160 	 * Return the localized card's name.
161 	 * 
162 	 * @return the localized card's name.
163 	 * @see net.sf.firemox.clickable.target.card.CardModel#getLocalName()
164 	 */
165 	public String getLocalName() {
166 		return cardModel.getLocalName();
167 	}
168 
169 	/***
170 	 * Return XML rule designer of the card.
171 	 * 
172 	 * @return XML rule designer of the card.
173 	 * @see net.sf.firemox.clickable.target.card.CardModel#getRulesCredit()
174 	 */
175 	public String getRulesCredit() {
176 		return cardModel.getRulesCredit();
177 	}
178 
179 	/***
180 	 * Add a translatable data to this database.
181 	 * 
182 	 * @param data
183 	 *          translatable data to add.
184 	 */
185 	public void add(TranslatableData data) {
186 		if ("local-name".equalsIgnoreCase(data.getValue())) {
187 			cardModel.setLocalName(data.getValue());
188 		}
189 		this.data.put(data.getPropertyName(), data);
190 	}
191 
192 	/***
193 	 * Return the card's picture as it would be displayed in the board.
194 	 * 
195 	 * @param card
196 	 *          the card requesting it's picture.
197 	 * @return the card's picture as it would be displayed in the board.
198 	 */
199 	public Image getImage(AbstractCard card) {
200 		if (!card.isVisibleForYou()) {
201 			return DatabaseFactory.backImage;
202 		}
203 		return getImage((MonitorListener) card);
204 	}
205 
206 	/***
207 	 * Return the scaled card's picture as it would be displayed in the board.
208 	 * 
209 	 * @param card
210 	 *          the card requesting it's picture.
211 	 * @return the scaled card's picture as it would be displayed in the board.
212 	 */
213 	public Image getScaledImage(AbstractCard card) {
214 		if (!card.isVisibleForYou()) {
215 			return DatabaseFactory.scaledBackImage;
216 		}
217 		return getScaledImage((MonitorListener) card);
218 	}
219 
220 	/***
221 	 * Return the card's picture as it would be displayed in the board.
222 	 * 
223 	 * @param listener
224 	 *          the listener to notify when the picture will be completely loaded.
225 	 * @return the card's picture as it would be displayed in the board.
226 	 */
227 	public Image getImage(MonitorListener listener) {
228 		if (image == null) {
229 			loadDatabasePicture(null, listener);
230 		} else if (!image.isFinished()) {
231 			image.addListener(listener);
232 		}
233 		return image.getContent();
234 	}
235 
236 	/***
237 	 * Return the scaled card's picture as it would be displayed in the board.
238 	 * 
239 	 * @param listener
240 	 *          the listener to notify when the picture will be completely loaded.
241 	 * @return the scaled card's picture as it would be displayed in the board.
242 	 */
243 	public Image getScaledImage(MonitorListener listener) {
244 		if (scaledImage == null) {
245 			if (image == null) {
246 				getImage(listener);
247 			}
248 			if (!image.isFinished()) {
249 				return image.getContent();
250 			}
251 			scaledImage = Picture.getScaledImage(image.getContent());
252 		}
253 		return scaledImage;
254 	}
255 
256 	/***
257 	 * Invalidate the card picture
258 	 */
259 	public synchronized void invalidateImage() {
260 		image = null;
261 		scaledImage = null;
262 	}
263 
264 	/***
265 	 * Return the stream description used to build the picture by this database
266 	 * object.
267 	 * 
268 	 * @return the stream description used to build the picture by this database
269 	 *         object.
270 	 */
271 	public String getPictureStream() {
272 		if (image != null && DatabaseFactory.sourceFile != null
273 				&& image.getContent() != null) {
274 			final ImageProducer imageProducer = image.getContent().getSource();
275 			if (imageProducer instanceof FileImageSource) {
276 				try {
277 					return DatabaseFactory.sourceFile.get(imageProducer).toString();
278 				} catch (Exception e) {
279 					return "unavailable stream";
280 				}
281 			} else if (imageProducer instanceof URLImageSource) {
282 				try {
283 					return DatabaseFactory.sourceUrl.get(imageProducer).toString();
284 				} catch (Exception e) {
285 					return "unavailable stream";
286 				}
287 			}
288 		}
289 		// TODO internationalized "unavailable stream"
290 		return "unavailable stream";
291 	}
292 
293 	/***
294 	 * Return the default local picture of this card model. The proxies are not
295 	 * used to get this image.
296 	 * 
297 	 * @return <code>null</code> if there is no local image.A non
298 	 *         <code>null</code> object otherwise.
299 	 * @throws MalformedURLException
300 	 */
301 	private MonitoredCheckContent getDefaultImage() throws MalformedURLException {
302 		return Picture.loadImage(MToolKit.getTbsPicture(cardModel.getKeyName()
303 				+ ".jpg", false), null);
304 
305 	}
306 
307 	/***
308 	 * Load the image considering proxy, properties and the given picture name. If
309 	 * no proxy is set for this database, the picture is loaded from the
310 	 * 'tbs/${tbs.name}/images' directory. The picture filename is either the
311 	 * specified <param>pictureName</param> if not null, either the built from
312 	 * the card name. The looked for picture type is '.jpg'.<br>
313 	 * If a proxy is set, then the proxy will build the URL where the picture can
314 	 * been found first locally, then remotely from the proxy web-site.
315 	 * 
316 	 * @param pictureName
317 	 *          the alternative picture name.
318 	 * @param listener
319 	 *          the card requesting this picture.
320 	 */
321 	private synchronized void loadDatabasePicture(String pictureName,
322 			MonitorListener listener) {
323 		if (image != null) {
324 			// since the call to this method, the picture has been loaded.
325 			image.addListener(listener);
326 			return;
327 		}
328 		try {
329 			if (pictureProxies == null) {
330 				/*
331 				 * Since the proxy has not been provided, the picture will be saved in
332 				 * the share 'tbs/xxx/images' place
333 				 */
334 				if (pictureName != null) {
335 					// a special picture has been specified. It must be local.
336 					image = Picture.loadImage(MToolKit
337 							.getTbsPicture(pictureName + ".jpg"), null);
338 				} else {
339 					// The default art URL will be used if the picture is not yet local.
340 					image = Picture.loadImage(MToolKit.getTbsPicture(cardModel
341 							.getKeyName()
342 							+ ".jpg", false), new URL(MdbLoader.getArtURL() + "/"
343 							+ cardModel.getKeyName() + ".jpg"));
344 				}
345 			} else {
346 
347 				/***
348 				 * The proxy has not been provided, we try to match one. <br>
349 				 * First with the local one, then with the remote ones.<br>
350 				 * The picture will be saved in the proxy private location
351 				 * 'tbs/XXX/images/proxies/${proxy.name}/${picture.URL less
352 				 * proxy.baseURL}/' place
353 				 */
354 				image = getDefaultImage();
355 				if (image != null) {
356 					return;
357 				}
358 
359 				List<String> localPaths = new ArrayList<String>();
360 				for (Proxy pictureProxy : pictureProxies) {
361 					for (String path : pictureProxy.getLocalPictures(cardModel, data)) {
362 						if (path != null) {
363 							image = Picture.loadImage(path, null);
364 							if (image != null) {
365 								return;
366 							}
367 						}
368 						localPaths.add(path);
369 					}
370 				}
371 
372 				List<String> remotePaths = new ArrayList<String>();
373 				for (Proxy pictureProxy : pictureProxies) {
374 					remotePaths.addAll(pictureProxy.getRemotePictures(cardModel, data));
375 				}
376 
377 				// even the card.id is not provided or another problem occurred
378 				if (pictureName != null) {
379 					// a special picture has been specified. It must be local.
380 					image = Picture.loadImage(MToolKit
381 							.getTbsPicture(pictureName + ".jpg"), null);
382 				} else {
383 					// The default art URL will be used if the picture is not yet local.
384 					localPaths.add(MToolKit.getTbsPicture(
385 							cardModel.getKeyName() + ".jpg", false));
386 					remotePaths.add(MdbLoader.getArtURL() + "/" + cardModel.getKeyName()
387 							+ ".jpg");
388 				}
389 
390 				if (image == null) {
391 					image = new MonitoredCheckContent(localPaths, remotePaths, listener);
392 					image.start();
393 				}
394 			}
395 		} catch (MalformedURLException e) {
396 			try {
397 				image = Picture.loadImage(MToolKit.getTbsPicture(cardModel.getKeyName()
398 						+ ".jpg"), null);
399 			} catch (MalformedURLException ex) {
400 				Log.debug("Error during picture load of " + cardModel.getKeyName(), ex);
401 			}
402 		}
403 
404 		if (image == null) {
405 			// picture load failed, return the default picture.
406 			image = new MonitoredCheckContent(DatabaseFactory.backImage);
407 		}
408 
409 	}
410 
411 	/***
412 	 * Create a node representing this data inside the given node.
413 	 * 
414 	 * @param inNode
415 	 *          the node where data would be added to.
416 	 */
417 	void updateCache(Node inNode) {
418 
419 		// build attributes
420 		XmlParser.Attribute[] attributes = new XmlParser.Attribute[2];
421 		attributes[0] = new XmlParser.Attribute("local-name", getLocalName());
422 		attributes[1] = new XmlParser.Attribute("proxy", dataProxy.getName());
423 
424 		// build node
425 		Node node = new XmlParser.Node(inNode, cardModel.getKeyName(), null);
426 		node.aAttrs = attributes;
427 
428 		// add properties
429 		for (TranslatableData dataIt : data.values()) {
430 			XmlParser.Attribute[] values = new XmlParser.Attribute[2];
431 			values[0] = new XmlParser.Attribute("name", dataIt.getPropertyName());
432 			values[1] = new XmlParser.Attribute("value", dataIt.getValue());
433 			Node property = new XmlParser.Node(node, "property", null);
434 			property.aAttrs = values;
435 			node.add(0, property);
436 		}
437 		inNode.aList.add(0, node);
438 	}
439 
440 	/***
441 	 * Create a new instance of DatabaseCard with a picture different form the
442 	 * standard one (relative to card name). No proxy would be used for this new
443 	 * instance.<br>
444 	 * The given picture is immediately loaded.
445 	 * 
446 	 * @param pictureName
447 	 *          the picture to use with this DatabaseCard
448 	 * @return a clone of this instance, without proxy and with the specified
449 	 *         picture loaded immediately.
450 	 */
451 	public DatabaseCard clone(String pictureName) {
452 		final DatabaseCard clone = new DatabaseCard(cardModel, null);
453 		clone.loadDatabasePicture(pictureName, null);
454 		return clone;
455 	}
456 
457 	@Override
458 	public DatabaseCard clone() {
459 		return clone(cardModel);
460 	}
461 
462 	/***
463 	 * Return a clone of this object from the given card model.
464 	 * 
465 	 * @param cardModel
466 	 * @return a clone of this object from the given card model.
467 	 */
468 	public DatabaseCard clone(CardModel cardModel) {
469 		final DatabaseCard clone = new DatabaseCard(cardModel, dataProxy,
470 				pictureProxies);
471 		return clone;
472 	}
473 
474 	/***
475 	 * Return the translated data associated to the named property.
476 	 * 
477 	 * @param property
478 	 *          the property name.
479 	 * @return the translated data associated to the named property.
480 	 */
481 	public String getProperty(String property) {
482 		final TranslatableData data = this.data.get(property);
483 		if (data != null) {
484 			return data.getTranslatedValue(dataProxy);
485 		}
486 		return "-";
487 	}
488 
489 	/***
490 	 * Set the proxy used to display picture of this object. If is null,
491 	 * <code>dataProxy</code> will be used.
492 	 * 
493 	 * @param pictureProxies
494 	 *          the new proxies used for picture. Order corresponds to preference.
495 	 */
496 	public void setPictureProxies(Proxy... pictureProxies) {
497 		if (this.pictureProxies != pictureProxies) {
498 			if (pictureProxies == null) {
499 				this.pictureProxies = new Proxy[] { dataProxy };
500 			} else {
501 				this.pictureProxies = pictureProxies;
502 			}
503 			image = null;
504 		}
505 	}
506 
507 	@Override
508 	public String toString() {
509 		return cardModel.toString();
510 	}
511 
512 	/***
513 	 * Set the consistency of this database.
514 	 * 
515 	 * @param consistent
516 	 *          the new consistency of this database.
517 	 */
518 	public void setConsistent(boolean consistent) {
519 		this.consistent = consistent;
520 	}
521 
522 	/***
523 	 * Return the new score of this database.
524 	 * 
525 	 * @return true if this database is consistent.
526 	 */
527 	public boolean isConsistent() {
528 		return consistent;
529 	}
530 
531 	/***
532 	 * This method update and paint on the given card, the progress bar of task of
533 	 * the image attached to this model.
534 	 * 
535 	 * @param card
536 	 *          the listener to manage.
537 	 * @param g
538 	 *          the graphics used to paint the progress bar.
539 	 */
540 	public void updatePaintNotification(MCard card, Graphics g) {
541 		if (image == null || !image.isFinished()) {
542 			// The displayed picture is not completely load, we display a progress bar
543 			if (image != null && card.isVisibleForYou()) {
544 				image.paintNotification(g);
545 			}
546 		} else if (image.isFinished()) {
547 			image.acknowledgeFinished(card);
548 		}
549 	}
550 
551 	/***
552 	 * This method update and paint on the given listener, the progress bar of
553 	 * task of the image attached to this model.
554 	 * 
555 	 * @param listener
556 	 *          the listener to manage.
557 	 * @param g
558 	 *          the graphics used to paint the progress bar.
559 	 */
560 	public void updatePaintNotification(MonitorListener listener, Graphics g) {
561 		if (image == null || !image.isFinished()) {
562 			// The displayed picture is not completely load, we display a progress bar
563 			if (image != null) {
564 				image.paintNotification(g);
565 			}
566 		} else if (image.isFinished()) {
567 			image.acknowledgeFinished(listener);
568 		}
569 	}
570 
571 	/***
572 	 * Reset the buffered data.
573 	 */
574 	public void updateMUI() {
575 		scaledImage = null;
576 	}
577 
578 	/***
579 	 * Update the card model if needed.
580 	 * 
581 	 * @param cardModel
582 	 *          the new card model.
583 	 */
584 	public void updateCardModel(CardModel cardModel) {
585 		if (cardModel != null && this.cardModel instanceof CardModelLazy) {
586 			this.cardModel = cardModel;
587 		}
588 	}
589 
590 }