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.tools;
20  
21  import java.awt.Font;
22  import java.awt.Image;
23  import java.awt.MediaTracker;
24  import java.awt.Point;
25  import java.awt.Toolkit;
26  import java.io.File;
27  import java.io.FileInputStream;
28  import java.io.IOException;
29  import java.io.InputStream;
30  import java.io.OutputStream;
31  import java.net.HttpURLConnection;
32  import java.net.URISyntaxException;
33  import java.net.URL;
34  import java.net.URLConnection;
35  import java.nio.charset.Charset;
36  import java.util.Properties;
37  import java.util.Random;
38  
39  import javax.sound.sampled.AudioFormat;
40  import javax.sound.sampled.AudioInputStream;
41  import javax.sound.sampled.AudioSystem;
42  import javax.sound.sampled.Clip;
43  import javax.sound.sampled.DataLine;
44  import javax.sound.sampled.LineUnavailableException;
45  import javax.sound.sampled.SourceDataLine;
46  import javax.sound.sampled.UnsupportedAudioFileException;
47  import javax.swing.ImageIcon;
48  import javax.swing.JComponent;
49  import javax.swing.JFileChooser;
50  import javax.swing.JFrame;
51  import javax.swing.JOptionPane;
52  import javax.swing.LookAndFeel;
53  import javax.swing.SwingUtilities;
54  
55  import net.sf.firemox.clickable.target.card.MCard;
56  import net.sf.firemox.clickable.target.card.SystemCard;
57  import net.sf.firemox.deckbuilder.MdbLoader;
58  import net.sf.firemox.token.IdCardColors;
59  import net.sf.firemox.token.IdCommonToken;
60  import net.sf.firemox.token.IdConst;
61  import net.sf.firemox.ui.MagicUIComponents;
62  import net.sf.firemox.xml.XmlDeckTranslator;
63  
64  import org.apache.commons.io.IOUtils;
65  import org.apache.commons.lang.ArrayUtils;
66  import org.apache.commons.lang.StringUtils;
67  import org.jvnet.substance.SubstanceLookAndFeel;
68  import org.jvnet.substance.grip.DragBumpsGripPainter;
69  import org.jvnet.substance.painter.AlphaControlBackgroundComposite;
70  import org.mortbay.util.Password;
71  
72  import sun.misc.BASE64Encoder;
73  
74  /***
75   * Utility class.
76   * 
77   * @author Fabrice Daugan
78   * @author brius
79   * @since 0.53
80   * @since 0.54 added "save..." methods
81   */
82  public final class MToolKit {
83  
84  	/***
85  	 * Return path used to determine the root directory for the development mode.
86  	 */
87  	private final static String RESOURCE_DEV = "magic.sh";
88  
89  	/***
90  	 * Return path used to determine the root directory for the test mode.
91  	 */
92  	private final static String RESOURCE_TEST = "foo.test";
93  
94  	/***
95  	 * The root directory
96  	 */
97  	private static String rootDir = null;
98  
99  	/***
100 	 * Create a new instance of this class.
101 	 */
102 	private MToolKit() {
103 		super();
104 	}
105 
106 	/***
107 	 * Return the root directory.
108 	 * 
109 	 * @return the root directory.
110 	 */
111 	public static String getRootDir() {
112 		if (rootDir == null) {
113 			String requiredFile = RESOURCE_DEV;
114 			URL url = Thread.currentThread().getContextClassLoader().getResource(
115 					requiredFile);
116 			if (url == null) {
117 				requiredFile = RESOURCE_TEST;
118 				url = Thread.currentThread().getContextClassLoader().getResource(
119 						requiredFile);
120 			}
121 			if (url == null) {
122 				rootDir = new File("").getAbsolutePath();
123 			} else {
124 				try {
125 					return StringUtils.removeEnd(new File(url.toURI()).getAbsolutePath(),
126 							requiredFile);
127 				} catch (URISyntaxException e) {
128 					rootDir = StringUtils.removeEnd(url.getPath(), requiredFile);
129 				}
130 			}
131 			if (rootDir.startsWith("/"))
132 				rootDir = rootDir.substring(1);
133 		}
134 		return rootDir;
135 	}
136 
137 	/***
138 	 * Replace occurrences into a string.
139 	 * 
140 	 * @param data
141 	 *          the string to replace occurrences into
142 	 * @param from
143 	 *          the occurrence to replace.
144 	 * @param to
145 	 *          the occurrence to be used as a replacement.
146 	 * @return the new string with replaced occurrences.
147 	 */
148 	public static String replaceAll(String data, String from, String to) {
149 		if (from.length() > 0 && from.length() > 0) {
150 			final StringBuilder buf = new StringBuilder(data.length());
151 			int pos = -1;
152 			int i = 0;
153 			while ((pos = data.indexOf(from, i)) != -1) {
154 				buf.append(data.substring(i, pos)).append(to);
155 				i = pos + from.length();
156 			}
157 			buf.append(data.substring(i));
158 			return buf.toString();
159 		}
160 		return data;
161 	}
162 
163 	/***
164 	 * @param idToken
165 	 *          the value to translate.
166 	 * @return the real value of the specified value. If the value is a constant
167 	 *         the returned value can be positive or negative.
168 	 */
169 	public static int getConstant(int idToken) {
170 		if (idToken != IdConst.ALL
171 				&& (idToken & 0xFF80) == IdConst.NEGATIVE_NUMBER_MASK) {
172 			// a negative number
173 			return -(idToken & 0x7F);
174 		}
175 		return idToken;
176 	}
177 
178 	/***
179 	 * The default charset used by this application.
180 	 */
181 	public static final String CHARSET = Charset.forName("ISO-8859-1").name();
182 
183 	/***
184 	 * read a string from inputStream ending with \0. The maximum size of this
185 	 * string is 200 chars.
186 	 * 
187 	 * @param inputStream
188 	 *          is the input stream
189 	 * @return the read string
190 	 * @throws IOException
191 	 *           If some other I/O error occurs
192 	 */
193 	public synchronized static String readString(InputStream inputStream)
194 			throws IOException {
195 		int count = 0;
196 		try {
197 			while ((mBuffer[count] = (byte) inputStream.read()) != 0) {
198 				count++;
199 			}
200 		} catch (ArrayIndexOutOfBoundsException e) {
201 			throw new IOException("The given data exceed the expected size '"
202 					+ mBuffer.length + "' reading string '"
203 					+ new String(mBuffer, 0, count, CHARSET) + "'\n\tdump : '"
204 					+ ArrayUtils.toString(mBuffer));
205 		} catch (Exception e) {
206 			throw new IOException("Exception '" + e + "' reading string '"
207 					+ new String(mBuffer, 0, count, CHARSET) + "'\n\tdump : '"
208 					+ ArrayUtils.toString(mBuffer));
209 		}
210 
211 		// Return the text from the stream against a specific charset.
212 		return new String(mBuffer, 0, count, CHARSET);
213 	}
214 
215 	/***
216 	 * write a string to output stream ending with \0.
217 	 * 
218 	 * @param out
219 	 *          is the input stream
220 	 * @param string
221 	 *          the string to write
222 	 */
223 	public static void writeString(OutputStream out, String string) {
224 		try {
225 			if (string != null) {
226 				out.write(string.getBytes(CHARSET));
227 			}
228 			out.write(0);
229 		} catch (Exception e) {
230 			throw new InternalError("writing string in file," + e);
231 		}
232 	}
233 
234 	/***
235 	 * read a string from input The strings read end with \0 with no limit of size
236 	 * 
237 	 * @param inputStream
238 	 *          is the input stream containing content
239 	 * @return the read string from the specified input stream
240 	 */
241 	public static String readText(InputStream inputStream) {
242 		StringBuffer string = new StringBuffer(100);
243 		try {
244 			int intRead;
245 			while ((intRead = inputStream.read()) != 0) {
246 				string.append((char) intRead);
247 			}
248 		} catch (Exception e) {
249 			throw new InternalError("reading text in file," + e);
250 		}
251 		return string.toString();
252 	}
253 
254 	/***
255 	 * Read a byte array 256*256*256 bytes maximum sized from the given stream.
256 	 * 
257 	 * @param inputStream
258 	 *          is the input stream containing content.
259 	 * @return the byte array.
260 	 * @throws IOException
261 	 *           If some other I/O error occurs
262 	 */
263 	public static byte[] readByteArray(InputStream inputStream)
264 			throws IOException {
265 		final int size = MToolKit.readInt24(inputStream);
266 		final byte[] array = new byte[size];
267 		int readCounter = 0;
268 		while (readCounter != size) {
269 			readCounter += inputStream.read(array, readCounter, size - readCounter);
270 		}
271 		return array;
272 	}
273 
274 	/***
275 	 * Read an icon from the given stream.
276 	 * 
277 	 * @param inputStream
278 	 *          is the input stream containing content.
279 	 * @return the read icon.
280 	 * @throws IOException
281 	 *           If some other I/O error occurs
282 	 */
283 	public static ImageIcon readImageIcon(InputStream inputStream)
284 			throws IOException {
285 		return new ImageIcon(readByteArray(inputStream));
286 	}
287 
288 	/***
289 	 * Read an image from the given stream.
290 	 * 
291 	 * @param inputStream
292 	 *          is the input stream containing content.
293 	 * @return the read image.
294 	 * @throws IOException
295 	 *           If some other I/O error occurs
296 	 */
297 	public static Image readImage(InputStream inputStream) throws IOException {
298 		return Toolkit.getDefaultToolkit().createImage(readByteArray(inputStream));
299 	}
300 
301 	/***
302 	 * Return 0 or 1 at random way
303 	 * 
304 	 * @return 0 or 1 at random way
305 	 */
306 	public static int getRandom() {
307 		return getRandom(2);
308 	}
309 
310 	/***
311 	 * Return a number [0,length[ at random way
312 	 * 
313 	 * @param length
314 	 *          maximum+1 value to return.
315 	 * @return a number [0,length[ at random way
316 	 */
317 	public static int getRandom(int length) {
318 		lastRandom = random.nextInt(length);
319 		return lastRandom;
320 	}
321 
322 	/***
323 	 * Copy the source file throw the output stream.
324 	 * 
325 	 * @param src
326 	 *          the source file.
327 	 * @param out
328 	 *          the output stream.
329 	 * @throws IOException
330 	 */
331 	public static void writeFile(File src, OutputStream out) throws IOException {
332 		InputStream in = new FileInputStream(src);
333 		MToolKit.writeInt24(out, (int) src.length());
334 
335 		// Transfer bytes from in to out
336 		final byte[] buf = new byte[1024];
337 		int len;
338 		while ((len = in.read(buf)) >= 0) {
339 			out.write(buf, 0, len);
340 		}
341 		IOUtils.closeQuietly(in);
342 	}
343 
344 	/***
345 	 * file filter used for MDB files
346 	 */
347 	private static FileFilterPlus mdbFilter = new FileFilterPlus("mdb",
348 			"MDB File For Firemox");
349 
350 	/***
351 	 * file filter used for deck files
352 	 */
353 	private static FileFilterPlus txtFilter = new FileFilterPlus("txt",
354 			"TXT format: CardName;qty");
355 
356 	/***
357 	 * file filter used for JPG pictures
358 	 */
359 	private static FileFilterPlus pictureFilter = new FileFilterPlus(
360 			new String[] { "jpg", "gif", "png" }, "Images");
361 
362 	/***
363 	 * Return the deck file chosen with an "open" dialog.
364 	 * 
365 	 * @return the file chosen, or the previous one if canceled.
366 	 */
367 	public static String getDeckFile() {
368 		return getDeckFile(JFileChooser.OPEN_DIALOG);
369 	}
370 
371 	/***
372 	 * Return the deck file chosen
373 	 * 
374 	 * @param type
375 	 *          open or save option
376 	 * @return the file chosen, or the previous one if canceled.
377 	 */
378 	public static String getDeckFile(int type) {
379 		return getDeckFile(MagicUIComponents.magicForm, type);
380 	}
381 
382 	/***
383 	 * Return the deck file chosen
384 	 * 
385 	 * @param parent
386 	 *          is parent of this dialog
387 	 * @param type
388 	 *          open or save option
389 	 * @return the file chosen, or the previous one if canceled.
390 	 */
391 	public static String getDeckFile(JFrame parent, int type) {
392 		String deckFile = Configuration.getString("decks.deck(0)", "");
393 		try {
394 			File file = MToolKit.getFile(deckFile);
395 			if (file == null) {
396 				file = showDialogFile("choose your deck file", 'o', null, txtFilter,
397 						parent, type);
398 			} else {
399 				file = showDialogFile("choose your deck file", 'o', file
400 						.getAbsoluteFile(), txtFilter, parent, type);
401 			}
402 
403 			if (file != null) {
404 				deckFile = getShortDeckFile(file.getCanonicalPath());
405 			} else
406 				return null;
407 			return deckFile;
408 
409 		} catch (IOException e) {
410 			JOptionPane.showMessageDialog(parent,
411 					"Error occurred reading the specified file", "File problem",
412 					JOptionPane.ERROR_MESSAGE);
413 			return Configuration.getString("decks.deck(0)", "");
414 		} catch (Exception e) {
415 			// cancel of user;
416 			return null;
417 		}
418 	}
419 
420 	/***
421 	 * Open a DialogFile dialog and return the chosen MDB file.
422 	 * 
423 	 * @param oldFile
424 	 *          the old MDB file, will be returned if user cancel operation
425 	 * @param parent
426 	 *          is parent of this dialog
427 	 * @return the file chosen, or oldFile if canceled
428 	 */
429 	public static String getMdbFile(String oldFile, JFrame parent) {
430 		try {
431 			return showDialogFile("choose your mdb file", 'o',
432 					oldFile == null ? null : MToolKit.getFile(oldFile), mdbFilter,
433 					parent, JFileChooser.OPEN_DIALOG).getCanonicalPath();
434 		} catch (java.io.IOException e) {
435 			javax.swing.JOptionPane.showMessageDialog(parent,
436 					"Error occurred reading the specified file", "File problem",
437 					JOptionPane.ERROR_MESSAGE);
438 			return oldFile;
439 		} catch (Exception e) {
440 			// cancel of user;
441 			return null;
442 		}
443 	}
444 
445 	/***
446 	 * return the picture file chosen
447 	 * 
448 	 * @param oldFile
449 	 *          the old picture, will be returned if user cancel operation
450 	 * @param parent
451 	 *          is parent of this dialog
452 	 * @return the picture file chosen, or oldFile if canceled
453 	 */
454 	public static String getPictureFile(String oldFile, JFrame parent) {
455 		try {
456 			return showDialogFile("choose your a picture", 'o', null, pictureFilter,
457 					parent, JFileChooser.OPEN_DIALOG).getCanonicalPath();
458 		} catch (java.io.IOException e) {
459 			throw new InternalError("choosing picture file");
460 		} catch (Exception e) {
461 			// cancel of user;
462 			return null;
463 		}
464 	}
465 
466 	/***
467 	 * Display a file dialog with many options
468 	 * 
469 	 * @param titreBoite
470 	 *          title of this dialog box
471 	 * @param etiquetteBouton
472 	 *          button "yes"
473 	 * @param infoBulle
474 	 *          tooltip
475 	 * @param raccourciBouton
476 	 *          shortcut for "yes" button
477 	 * @param fichier
478 	 *          old file
479 	 * @param fileFilter
480 	 *          file filter to apply
481 	 * @param parent
482 	 *          is parent of this dialog
483 	 * @param type
484 	 *          the dialog type
485 	 * @return the File object corresponding to the user's choice
486 	 * @see JFileChooser#setDialogType(int)
487 	 */
488 	private static File showDialogFile(String titreBoite, char raccourciBouton,
489 			File fichier, FileFilterPlus fileFilter, JFrame parent, int type) {
490 		return showDialogFile(titreBoite, raccourciBouton, fichier, fileFilter,
491 				parent, type, JFileChooser.FILES_AND_DIRECTORIES);
492 	}
493 
494 	/***
495 	 * Display a file dialog with many options
496 	 * 
497 	 * @param titreBoite
498 	 *          title of this dialog box
499 	 * @param raccourciBouton
500 	 *          shortcut for "yes" button
501 	 * @param fichier
502 	 *          old file
503 	 * @param fileFilter
504 	 *          file filter to apply
505 	 * @param parent
506 	 *          is parent of this dialog
507 	 * @param type
508 	 *          the dialog type
509 	 * @param mode
510 	 * @return the File object corresponding to the user's choice
511 	 * @see JFileChooser#setDialogType(int)
512 	 */
513 	public static File showDialogFile(String titreBoite, char raccourciBouton,
514 			File fichier, FileFilterPlus fileFilter, JFrame parent, int type, int mode) {
515 		if (fileChooser == null) {
516 			fileChooser = new JFileChooser();
517 		}
518 		fileChooser.setDialogType(type);
519 		fileChooser.setDialogTitle(titreBoite);
520 		fileChooser.setApproveButtonMnemonic(raccourciBouton);
521 		if (fileFilter != null) {
522 			fileFilter.addExtension(".");
523 			fileChooser.setFileFilter(fileFilter);
524 		}
525 		fileChooser.setFileSelectionMode(mode);
526 		fileChooser.rescanCurrentDirectory();
527 		fileChooser.setSelectedFile(fichier);
528 		/*
529 		 * if (filePreview != null) { filePreview.setFileChooser(fileChooser);
530 		 * fileChooser.setAccessory(filePreview); }
531 		 */
532 
533 		int resultat = fileChooser.showDialog(parent, null);
534 
535 		return resultat == JFileChooser.APPROVE_OPTION ? fileChooser
536 				.getSelectedFile() : null;
537 	}
538 
539 	/***
540 	 * An utility function that layers on top of the LookAndFeel's
541 	 * isSupportedLookAndFeel() method. Returns true if the LookAndFeel is
542 	 * supported. Returns false if the LookAndFeel is not supported and/or if
543 	 * there is any kind of error checking if the LookAndFeel is supported. The
544 	 * L&F menu will use this method to detemine whether the various L&F options
545 	 * should be active or inactive.
546 	 * 
547 	 * @param laf
548 	 *          L1F name
549 	 * @return true if successfully loaded
550 	 */
551 	public static boolean isAvailableLookAndFeel(String laf) {
552 		try {
553 			Class.forName(laf);
554 			// final Class lnfClass = Class.forName(laf);
555 			// final LookAndFeel newLAF = (LookAndFeel) (lnfClass.newInstance());
556 			// return newLAF.isSupportedLookAndFeel();
557 			return true;
558 		} catch (Exception e) { // If ANYTHING weird happens, return false
559 			// try {
560 			// final Class lnfClass2 = JarClassLoader.getInstance().loadClass(laf);
561 			// final LookAndFeel newLAF = (LookAndFeel) (lnfClass2.newInstance());
562 			// return newLAF.isSupportedLookAndFeel();
563 			// } catch (Exception e2) { // If ANYTHING weird happens, return false
564 			return false;
565 			// }
566 		}
567 	}
568 
569 	/***
570 	 * Return the L&F instance from the givent name.
571 	 * 
572 	 * @param laf
573 	 *          the L&F class name
574 	 * @return the L&F instance
575 	 * @throws InstantiationException
576 	 * @throws IllegalAccessException
577 	 * @throws ClassNotFoundException
578 	 */
579 	public static LookAndFeel geLookAndFeel(String laf)
580 			throws InstantiationException, IllegalAccessException,
581 			ClassNotFoundException {
582 		final Class<?> lnfClass = Class.forName(laf);
583 		return (LookAndFeel) (lnfClass.newInstance());
584 	}
585 
586 	/***
587 	 * Read the next 2 bytes from the specified input stream and return the
588 	 * integer value coded with 32bits
589 	 * 
590 	 * @param input
591 	 *          is the file containing the short (2bytes)to read
592 	 * @return the read integer built with the 2 next bytes read from the
593 	 *         specified input stream
594 	 * @throws IOException
595 	 *           if error occurred during the reading process from the specified
596 	 *           input stream
597 	 */
598 	public static int readInt16(InputStream input) throws IOException {
599 		int res = input.read() << 8;
600 		return res + input.read();
601 	}
602 
603 	/***
604 	 * Read the next 2 bytes from the specified input stream and return the
605 	 * integer value coded with 32bits
606 	 * 
607 	 * @param high
608 	 *          the high byte.
609 	 * @param low
610 	 *          the low byte.
611 	 * @return the read integer built with the 2 next bytes read from the
612 	 *         specified input stream
613 	 */
614 	public static int readInt16(byte high, byte low) {
615 		int res = high << 8;
616 		return res + low;
617 	}
618 
619 	/***
620 	 * Read the next 3 bytes from the specified input stream and return the
621 	 * integer value coded with 48bits
622 	 * 
623 	 * @param input
624 	 *          is the file containing the short (3bytes)to read
625 	 * @return the read integer built with the 3 next bytes read from the
626 	 *         specified input stream
627 	 * @throws IOException
628 	 *           if error occurred during the reading process from the specified
629 	 *           input stream
630 	 */
631 	public static int readInt24(InputStream input) throws IOException {
632 		int res = input.read() << 16;
633 		res += input.read() << 8;
634 		return res + input.read();
635 	}
636 
637 	/***
638 	 * Write the specified positive short coded on 2 bytes to the specified input
639 	 * stream
640 	 * 
641 	 * @param out
642 	 *          is the output stream where the specified integer would be written
643 	 * @param int16
644 	 *          the 2 bytes integer to write
645 	 */
646 	public static void writeInt16(OutputStream out, int int16) {
647 		try {
648 			out.write(int16 / 256);
649 			out.write(int16 % 256);
650 		} catch (Exception e) {
651 			throw new InternalError("writing int16 in file," + e);
652 		}
653 	}
654 
655 	/***
656 	 * Write the specified positive integer coded on 3 bytes to the specified
657 	 * input stream
658 	 * 
659 	 * @param out
660 	 *          is the output stream where the specified integer would be written
661 	 * @param int24
662 	 *          the 3 bytes integer to write
663 	 */
664 	public static void writeInt24(OutputStream out, int int24) {
665 		try {
666 			out.write(int24 / 65536);
667 			out.write(int24 % 65536 / 256);
668 			out.write(int24 % 256);
669 		} catch (Exception e) {
670 			throw new InternalError("writing int24 in file," + e);
671 		}
672 	}
673 
674 	/***
675 	 * Create, and return the connection established with a http server. May use a
676 	 * http proxy if the settings have been set
677 	 * 
678 	 * @return the SMTP properties.
679 	 */
680 	public static Properties getSmtpProperties() {
681 		if (Configuration.getBoolean("useProxy", false)) {
682 			// we use the proxy configuration
683 			System.setProperty("socksProxySet", "true");
684 			System.setProperty("socksProxyHost", Configuration.getString("proxyHost",
685 					"192.168.0.252"));
686 			System.setProperty("socksProxyPort", "25");
687 			final String clearLoginPwd = Password.deobfuscate(Configuration
688 					.getString("proxyObfuscatedLoginPwd", "anonymous:"));
689 			System.setProperty("socksProxyUserName", clearLoginPwd.substring(0,
690 					clearLoginPwd.indexOf(':')));
691 			System.setProperty("socksProxyPassword", clearLoginPwd
692 					.substring(clearLoginPwd.indexOf(':') + 1));
693 
694 			System.setProperty("socks.useProxy", "true");
695 			System.setProperty("socks.proxyHost", System
696 					.getProperty("socksProxyHost"));
697 			System.setProperty("socks.proxyPort", System
698 					.getProperty("socksProxyPort"));
699 			System.setProperty("socks.proxyUserName", System
700 					.getProperty("socksProxyUserName"));
701 			System.setProperty("socks.proxyPassword", System
702 					.getProperty("socksProxyPassword"));
703 		}
704 		return System.getProperties();
705 	}
706 
707 	/***
708 	 * Create, and return the connection established with a http server. May use a
709 	 * http proxy if the settings have been set
710 	 * 
711 	 * @param url
712 	 *          the requested url.
713 	 * @return Http connection.
714 	 */
715 	public static URLConnection getHttpConnection(URL url) {
716 		try {
717 			if (Configuration.getBoolean("useProxy", false)) {
718 				// we use the proxy configuration
719 				Properties systPrp = System.getProperties();
720 				systPrp.put("proxySet", "true");
721 				systPrp.put("http.proxyHost", Configuration.getString("proxyHost",
722 						"192.168.0.252"));
723 				systPrp.put("http.proxyPort", Configuration.getString("proxyPort",
724 						"1299"));
725 				System.setProperties(systPrp);
726 
727 				HttpURLConnection uc = (HttpURLConnection) url.openConnection();
728 				BASE64Encoder encoder = new sun.misc.BASE64Encoder();
729 				String encodedUserPwd = encoder.encode(Password.deobfuscate(
730 						Configuration.getString("proxyObfuscatedLoginPwd", "")).getBytes());
731 				if (encodedUserPwd.length() > 0) {
732 					uc.setRequestProperty("Proxy-Authorization", "Basic "
733 							+ encodedUserPwd);
734 				}
735 				uc.connect();
736 				return uc;
737 			}
738 			return url.openConnection();
739 		} catch (IOException e) {
740 			return null;
741 		}
742 	}
743 
744 	/***
745 	 * Return the loaded picture from a local place.
746 	 * 
747 	 * @param localFile
748 	 *          the local file name.
749 	 * @return the local picture.
750 	 * @throws InterruptedException
751 	 */
752 	public static Image getLocalPicture(String localFile)
753 			throws InterruptedException {
754 		final Image result = Toolkit.getDefaultToolkit().getImage(
755 				getFile(localFile, true).getAbsolutePath());
756 		if (result == null) {
757 			throw new InterruptedException("Picture " + localFile
758 					+ " has not been found");
759 		}
760 		final MediaTracker tracker = new MediaTracker(MagicUIComponents.magicForm);
761 		tracker.addImage(result, 0);
762 		tracker.waitForAll();
763 		if (tracker.isErrorAny()) {
764 			tracker.removeImage(result, 0);
765 			tracker.waitForAll();
766 			result.flush();
767 			throw new InterruptedException("Malformed picture " + localFile);
768 		}
769 		return result;
770 	}
771 
772 	/***
773 	 * Return the picture specific to the current TBS
774 	 * 
775 	 * @param pictureFile
776 	 *          the path of picture to load. This file must not begin with '/'
777 	 * @return the picture specific to the current TBS
778 	 */
779 	public static String getTbsHtmlPicture(String pictureFile) {
780 		return MToolKit.getTbsPicture(pictureFile, false).replace('//', '/');
781 	}
782 
783 	/***
784 	 * Return the picture specific to the current TBS
785 	 * 
786 	 * @param pictureFile
787 	 *          the path of picture to load. This file must not begin with '/'
788 	 * @return the picture specific to the current TBS
789 	 */
790 	public static String getTbsPicture(String pictureFile) {
791 		return getTbsPicture(pictureFile, true);
792 	}
793 
794 	/***
795 	 * Return the picture specific to the current TBS
796 	 * 
797 	 * @param pictureFile
798 	 *          the path of picture to load. This file must not begin with '/'
799 	 * @param mustExist
800 	 *          <code>true</code> if the resource to search must exists,
801 	 *          <code>false</code> either
802 	 * @return the picture specific to the current TBS
803 	 */
804 	public static String getTbsPicture(String pictureFile, boolean mustExist) {
805 		final File file = getTbsFile(IdConst.IMAGES_DIR + pictureFile, mustExist);
806 		if (file == null) {
807 			if (mustExist) {
808 				return null;
809 			}
810 			return pictureFile;
811 		}
812 		return file.getAbsolutePath();
813 	}
814 
815 	/***
816 	 * Return the mana picture specific to the current TBS
817 	 * 
818 	 * @param idColor
819 	 *          the color of mana
820 	 * @return the mana picture specific to the current TBS
821 	 */
822 	public static ImageIcon getTbsBigManaPicture(int idColor) {
823 		if (idColor == 0)
824 			return new ImageIcon(getTbsPicture("mana/colorless/big/"
825 					+ MdbLoader.colorlessBigURL));
826 		return new ImageIcon(getTbsPicture("mana/colored/big/"
827 				+ MdbLoader.coloredBigManas[idColor]));
828 	}
829 
830 	/***
831 	 * Return the mana picture specific to the current TBS
832 	 * 
833 	 * @param idColor
834 	 *          the color of mana
835 	 * @param amount
836 	 *          requested amount.
837 	 * @return the mana picture specific to the current TBS
838 	 */
839 	public static String getHtmlMana(int idColor, int amount) {
840 		if (idColor == IdCommonToken.COLORLESS_MANA) {
841 			if (amount >= MdbLoader.colorlessSmlManas.length) {
842 				return MdbLoader.colorlessSmlManasHtml[MdbLoader.colorlessSmlManas.length - 1]
843 						+ getHtmlMana(idColor, amount - MdbLoader.colorlessSmlManas.length
844 								+ 1);
845 			} else if (amount == -1) {
846 				return MdbLoader.unknownSmlManaHtml;
847 			}
848 			return MdbLoader.colorlessSmlManasHtml[amount];
849 
850 		}
851 		if (amount == -1) {
852 			return MdbLoader.unknownSmlManaHtml;
853 		}
854 
855 		String res = "";
856 		for (int i = amount; i-- > 0;) {
857 			res += MdbLoader.coloredSmlManasHtml[idColor];
858 		}
859 		return res;
860 	}
861 
862 	/***
863 	 * Return the file specific to the current TBS
864 	 * 
865 	 * @param file
866 	 *          the path of file to load. This file must not begin with '/'
867 	 * @return the picture specific to the current TBS
868 	 */
869 	public static File getTbsFile(String file) {
870 		return getTbsFile(file, true);
871 	}
872 
873 	/***
874 	 * Return the URL specific to the current TBS
875 	 * 
876 	 * @param file
877 	 *          the path of file to load. This file must not begin with '/'
878 	 * @return the URL specific to the current TBS
879 	 */
880 	public static URL getTbsUrl(String file) {
881 		return Thread.currentThread().getContextClassLoader().getResource(
882 				new StringBuilder(IdConst.TBS_DIR).append("/").append(tbsName).append(
883 						"/").append(file).toString());
884 	}
885 
886 	/***
887 	 * Return the URL corresponding file name.
888 	 * 
889 	 * @param file
890 	 *          the path of file to load. This file must not begin with '/'
891 	 * @return the URL corresponding file name.
892 	 */
893 	public static URL getUrl(String file) {
894 		return Thread.currentThread().getContextClassLoader().getResource(file);
895 	}
896 
897 	/***
898 	 * Return the file specific to the current TBS
899 	 * 
900 	 * @param file
901 	 *          the path of file to load. This file must not begin with '/'
902 	 * @param mustExist
903 	 *          <code>true</code> if the resource to search must exists,
904 	 *          <code>false</code> either
905 	 * @return the file specific to the current TBS
906 	 */
907 	public static File getTbsFile(String file, boolean mustExist) {
908 		return getFile(new StringBuilder(IdConst.TBS_DIR).append("/").append(
909 				tbsName).append("/").append(file).toString(), mustExist);
910 	}
911 
912 	/***
913 	 * Return the sound specific to the current TBS
914 	 * 
915 	 * @param soundFile
916 	 *          the path of picture to load. This file must not begin with '/'
917 	 * @return the sound specific to the current TBS
918 	 */
919 	public static String getSoundFile(String soundFile) {
920 		return MToolKit.getTbsFile(IdConst.SOUNDS_DIR + soundFile)
921 				.getAbsolutePath();
922 	}
923 
924 	/***
925 	 * loadClip loads the sound-file into a clip.
926 	 * 
927 	 * @param soundFile
928 	 *          file to be loaded and played.
929 	 */
930 	public static void loadClip(String soundFile) {
931 		AudioFormat audioFormat = null;
932 		AudioInputStream actionIS = null;
933 		try {
934 			// actionIS = AudioSystem.getAudioInputStream(input); // Does not work !
935 			actionIS = AudioSystem.getAudioInputStream(MToolKit.getFile(MToolKit
936 					.getSoundFile(soundFile)));
937 			AudioFormat.Encoding targetEncoding = AudioFormat.Encoding.PCM_SIGNED;
938 			actionIS = AudioSystem.getAudioInputStream(targetEncoding, actionIS);
939 			audioFormat = actionIS.getFormat();
940 
941 		} catch (UnsupportedAudioFileException afex) {
942 			Log.error(afex);
943 		} catch (IOException ioe) {
944 
945 			if (ioe.getMessage().equalsIgnoreCase("mark/reset not supported")) { // Ignore
946 				Log.error("IOException ignored.");
947 			}
948 			Log.error(ioe.getStackTrace());
949 		}
950 
951 		// define the required attributes for our line,
952 		// and make sure a compatible line is supported.
953 
954 		// get the source data line for play back.
955 		DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat);
956 		if (!AudioSystem.isLineSupported(info)) {
957 			Log.error("LineCtrl matching " + info + " not supported.");
958 			return;
959 		}
960 
961 		// Open the source data line for play back.
962 		try {
963 			Clip clip = null;
964 			try {
965 				Clip.Info info2 = new Clip.Info(Clip.class, audioFormat);
966 				clip = (Clip) AudioSystem.getLine(info2);
967 				clip.open(actionIS);
968 				clip.start();
969 			} catch (IOException ioe) {
970 				Log.error(ioe);
971 			}
972 		} catch (LineUnavailableException ex) {
973 			Log.error("Unable to open the line: " + ex);
974 			return;
975 		}
976 	}
977 
978 	/***
979 	 * Represents the default font
980 	 */
981 	public static Font defaultFont;
982 
983 	/***
984 	 * Represents the MDB file corresponding to the current TBS name. Is null if
985 	 * no TBS is currently defined.
986 	 */
987 	public static String mdbFile;
988 
989 	/***
990 	 * Represents the default MDB name. This is not the full name of selected TBS.
991 	 */
992 	public static String tbsName;
993 
994 	/***
995 	 * represents the file chooser
996 	 */
997 	public static JFileChooser fileChooser;
998 
999 	private static byte[] mBuffer = new byte[200];
1000 
1001 	static int lastRandom = 0; // Last integer generated
1002 
1003 	/***
1004 	 * The current random sequence. May be initialized at the beginning of each
1005 	 * play.
1006 	 */
1007 	public static Random random = new Random(); // Random generator
1008 
1009 	/***
1010 	 * Return absolute location of given component.
1011 	 * 
1012 	 * @param component
1013 	 *          the component to locate
1014 	 * @return the absolute POINT location of given component.
1015 	 */
1016 	public static Point getAbsoluteLocation(JComponent component) {
1017 		return SwingUtilities.convertPoint(component, 0, 0,
1018 				MagicUIComponents.magicForm.getContentPane());
1019 	}
1020 
1021 	/***
1022 	 * Parses the string argument as a signed integer in the radix specified by
1023 	 * the second argument. The characters in the string must all be digits of 10
1024 	 * radix (as determined by whether
1025 	 * {@link java.lang.Character#digit(char, int)} returns a nonnegative value),
1026 	 * except that the first character may be an ASCII minus sign <code>'-'</code> (<code>'&#92;u002D'</code>)
1027 	 * to indicate a negative value. The resulting integer value is returned.
1028 	 * <p>
1029 	 * The <code>Integer#MIN_VALUE</code> value is returned if any of the
1030 	 * following situations occurs:
1031 	 * <ul>
1032 	 * <li>The first argument is <code>null</code> or is a string of length
1033 	 * zero.
1034 	 * <li>Any character of the string is not a digit of the specified radix,
1035 	 * except that the first character may be a minus sign <code>'-'</code> (<code>'&#92;u002D'</code>)
1036 	 * provided that the string is longer than length 1.
1037 	 * <li>The value represented by the string is not a value of type
1038 	 * <code>int</code>.
1039 	 * </ul>
1040 	 * <p>
1041 	 * Examples: <blockquote> parseInt(&quot;0&quot;) returns 0 <br>
1042 	 * parseInt(&quot;473&quot;) returns 473 <br>
1043 	 * parseInt(&quot;-0&quot;) returns 0 <br>
1044 	 * parseInt(&quot;2147483647&quot;) returns 2147483647 <br>
1045 	 * parseInt(&quot;-2147483648&quot;) returns -2147483648 <br>
1046 	 * parseInt(&quot;2147483648&quot;) returns Integer#MIN_VALUE <br>
1047 	 * parseInt(&quot;Kona&quot;) returns Integer#MIN_VALUE <br>
1048 	 * </blockquote>
1049 	 * 
1050 	 * @param s
1051 	 *          the <code>String</code> containing the integer representation to
1052 	 *          be parsed
1053 	 * @return the integer represented by the string argument in the 10 radix.
1054 	 *         Return Integer.MIN_VALUE when error
1055 	 * @see Integer#MIN_VALUE
1056 	 */
1057 	public static int parseInt(String s) {
1058 		if (s == null) {
1059 			return Integer.MIN_VALUE;
1060 		}
1061 		int result = 0;
1062 		boolean negative = false;
1063 		int i = 0;
1064 		int max = s.length();
1065 		int limit;
1066 		int multmin;
1067 		int digit;
1068 
1069 		if (max > 0) {
1070 			if (s.charAt(0) == '-') {
1071 				negative = true;
1072 				limit = Integer.MIN_VALUE;
1073 				i++;
1074 			} else {
1075 				limit = -Integer.MAX_VALUE;
1076 			}
1077 			multmin = limit / 10;
1078 			if (i < max) {
1079 				digit = Character.digit(s.charAt(i++), 10);
1080 				if (digit < 0) {
1081 					return Integer.MIN_VALUE;
1082 				}
1083 				result = -digit;
1084 			}
1085 			while (i < max) {
1086 				// Accumulating negatively avoids surprises near MAX_VALUE
1087 				digit = Character.digit(s.charAt(i++), 10);
1088 				if (digit < 0) {
1089 					return Integer.MIN_VALUE;
1090 				}
1091 				if (result < multmin) {
1092 					return Integer.MIN_VALUE;
1093 				}
1094 				result *= 10;
1095 				if (result < limit + digit) {
1096 					return Integer.MIN_VALUE;
1097 				}
1098 				result -= digit;
1099 			}
1100 		} else {
1101 			return Integer.MIN_VALUE;
1102 		}
1103 		if (negative) {
1104 			if (i > 1) {
1105 				return result;
1106 			}
1107 			/* Only got "-" */
1108 			return Integer.MIN_VALUE;
1109 		}
1110 		return -result;
1111 	}
1112 
1113 	/***
1114 	 * Return the canonical path of working directory.
1115 	 * 
1116 	 * @return the canonical path of working directory.
1117 	 * @throws IOException
1118 	 */
1119 	public static String getRelativePath() throws IOException {
1120 		return new File("").getAbsoluteFile().getCanonicalPath().replace('//', '/');
1121 	}
1122 
1123 	/***
1124 	 * Return the specified card name without any special char. The returned name
1125 	 * may be used to build a file. The returned string is in lower case.
1126 	 * 
1127 	 * @param cardName
1128 	 *          the card name as it is displayed.
1129 	 * @return the specified card name without any special char.
1130 	 */
1131 	public static String getKeyName(String cardName) {
1132 		return getExactKeyName(cardName).toLowerCase();
1133 	}
1134 
1135 	/***
1136 	 * Return the specified card name without any special char. The returned name
1137 	 * may be used to build a file.
1138 	 * 
1139 	 * @param cardName
1140 	 *          the card name as it is displayed.
1141 	 * @return the specified card name without any special char.
1142 	 */
1143 	public static String getExactKeyName(String cardName) {
1144 		if (translator == null) {
1145 			try {
1146 				translator = new XmlDeckTranslator(MToolKit.tbsName);
1147 			} catch (Exception e1) {
1148 				e1.printStackTrace();
1149 				Log.warn("NO DECK TRANSLATOR FOUND");
1150 			}
1151 		}
1152 		return translator.convert(cardName);
1153 	}
1154 
1155 	/***
1156 	 * Returns the given path with '\' char replaced by '/' char.
1157 	 * 
1158 	 * @param canonicalPath
1159 	 * @return the given path with '\' char replaced by '/' char.
1160 	 * @throws IOException
1161 	 */
1162 	public static String getRelativePath(String canonicalPath) throws IOException {
1163 		if (canonicalPath.replace('//', '/').startsWith(getRelativePath())) {
1164 			return canonicalPath.substring(getRelativePath().length() + 1).replace(
1165 					'//', '/');
1166 		}
1167 		return canonicalPath;
1168 	}
1169 
1170 	/***
1171 	 * Return logging info of the given card.
1172 	 * 
1173 	 * @param card
1174 	 *          the card to Log.
1175 	 * @return logging info of the given card.
1176 	 */
1177 	public static String getLogCardInfo(MCard card) {
1178 		if (card != null) {
1179 			if (card != SystemCard.instance) {
1180 				return new StringBuilder(", card=").append(card.getName()).append(
1181 						"@" + Integer.toHexString(card.hashCode())).toString();
1182 			}
1183 			return ", card=" + card.getName();
1184 		}
1185 		return "";
1186 	}
1187 
1188 	/***
1189 	 * The translator transforming a card name into a file-serializable value. Is
1190 	 * <code>null</code> while not used.
1191 	 */
1192 	public static XmlDeckTranslator translator = null;
1193 
1194 	/***
1195 	 * Return the specified string without any local white spaces. Would be
1196 	 * replaced when
1197 	 * {@link org.apache.commons.lang.StringUtils#deleteWhitespace(String)} would
1198 	 * work.
1199 	 * 
1200 	 * @param string
1201 	 *          the string to normalize.
1202 	 * @return the given string without any space char.
1203 	 * @see Character#isWhitespace(char)
1204 	 */
1205 	public static String replaceWhiteSpaces(String string) {
1206 		final StringBuilder workingString = new StringBuilder(StringUtils
1207 				.deleteWhitespace(string));
1208 		for (int count = workingString.length(); count-- > 0;) {
1209 			if (Character.isSpaceChar(workingString.charAt(count))) {
1210 				// delete this char
1211 				workingString.deleteCharAt(count);
1212 			}
1213 		}
1214 		return workingString.toString();
1215 	}
1216 
1217 	/***
1218 	 * Return the stream associated to the given resource.
1219 	 * 
1220 	 * @param resource
1221 	 *          the resource path to search.
1222 	 * @return the stream associated to the given resource.
1223 	 */
1224 	public static InputStream getResourceAsStream(String resource) {
1225 		if (new File(resource).exists())
1226 			try {
1227 				return new FileInputStream(resource);
1228 			} catch (Exception e) {
1229 				// Error, so ignore it and get data from another way
1230 			}
1231 		return Thread.currentThread().getContextClassLoader().getResourceAsStream(
1232 				resource);
1233 	}
1234 
1235 	/***
1236 	 * Return the File associated to the given fileName.
1237 	 * 
1238 	 * @param fileName
1239 	 *          the file name path to search.
1240 	 * @param mustExist
1241 	 *          <code>true</code> if the resource to search must exists,
1242 	 *          <code>false</code> either
1243 	 * @return the File associated to the given fileName.
1244 	 */
1245 	public static File getFile(String fileName, boolean mustExist) {
1246 		final File simpleFile = new File(fileName);
1247 		if (simpleFile.exists()) {
1248 			return simpleFile.getAbsoluteFile();
1249 		}
1250 
1251 		String file = fileName;
1252 
1253 		if (file.startsWith("/")) {
1254 			file = file.substring(1);
1255 		}
1256 			
1257 		final File f = new File(getRootDir() + file);
1258 		if (!f.exists() && mustExist) {
1259 			return null;
1260 		}
1261 		return f;
1262 	}
1263 
1264 	/***
1265 	 * Return the File associated to the given fileName.
1266 	 * 
1267 	 * @param fileName
1268 	 *          the file name path to search.
1269 	 * @return the File associated to the given fileName.
1270 	 */
1271 	public static File getFile(String fileName) {
1272 		return getFile(fileName, true);
1273 	}
1274 
1275 	/***
1276 	 * Returns the absolute path of an icon used for the UI.
1277 	 * 
1278 	 * @param fileName
1279 	 *          the icon path relative to the images directory
1280 	 * @return the absolute path of an icon used for the UI
1281 	 */
1282 	public static String getIconPath(String fileName) {
1283 		File file = getFile(IdConst.IMAGES_DIR + fileName);
1284 		if (file == null)
1285 			return "";
1286 		return file.getAbsolutePath();
1287 	}
1288 
1289 	/***
1290 	 * Return the given deck file without the current path off application. If the
1291 	 * given deck is valid, it becomes the new deck reference.
1292 	 * 
1293 	 * @param deckFile
1294 	 *          the full deck file.
1295 	 * @return the given deck file without the current path off application.
1296 	 */
1297 	public static String getShortDeckFile(String deckFile) {
1298 		String fullDeckFile = deckFile.replace('//', '/');
1299 		String shortName = fullDeckFile;
1300 		try {
1301 			if (fullDeckFile.toUpperCase().startsWith(
1302 					MToolKit.getRelativePath().toUpperCase())) {
1303 				shortName = fullDeckFile.substring(
1304 						MToolKit.getRelativePath().length() + 1).replace('//', '/');
1305 			}
1306 		} catch (IOException e) {
1307 			// we'll use the full deck name
1308 		}
1309 		Configuration.addRecentProperty("decks.deck", shortName);
1310 		return shortName;
1311 	}
1312 
1313 	/***
1314 	 * Specifies that a component should have overlay functionality.
1315 	 * 
1316 	 * @param superPanel
1317 	 *          the panel to overlay.
1318 	 */
1319 	public static void addOverlay(JComponent superPanel) {
1320 		/*
1321 		 * superPanel.putClientProperty(SubstanceLookAndFeel.OVERLAY_PROPERTY,
1322 		 * Boolean.TRUE);
1323 		 */
1324 		superPanel.putClientProperty(SubstanceLookAndFeel.BACKGROUND_COMPOSITE,
1325 				new AlphaControlBackgroundComposite(0.3f, 0.5f));
1326 		superPanel.putClientProperty(SubstanceLookAndFeel.GRIP_PAINTER,
1327 				new DragBumpsGripPainter());
1328 	}
1329 
1330 	/***
1331 	 * Sum the mana.
1332 	 * 
1333 	 * @param registers
1334 	 *          the array of mana.
1335 	 * @return the sum of mana.
1336 	 */
1337 	public static int manaPool(int[] registers) {
1338 		int res = 0;
1339 		for (int i = IdCardColors.CARD_COLOR_NAMES.length; i-- > 0;) {
1340 			res += registers[i];
1341 		}
1342 		return res;
1343 	}
1344 
1345 }