package net.sf.colossus.util; import java.awt.Color; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.MediaTracker; import java.awt.Toolkit; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.image.WritableRaster; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintWriter; import java.lang.reflect.Constructor; import java.net.MalformedURLException; import java.net.Socket; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.ImageIcon; import javax.swing.text.DefaultStyledDocument; import javax.swing.text.Document; import javax.swing.text.StyledDocument; import javax.swing.text.html.HTMLDocument; import javax.swing.text.html.HTMLEditorKit; import org.jdom.Element; /** * Class ResourceLoader is an utility class to load a resource from * a filename and a list of directory. * * @author Romain Dolbeau * @author David Ripton */ public final class StaticResourceLoader { private static final Logger LOGGER = Logger .getLogger(StaticResourceLoader.class.getName()); public static final String FILESERVER_IGNOREFAIL_SIGNAL = "~/~Ignore-Fail~/~"; /** * Class ColossusClassLoader allows for class loading outside the * CLASSPATH, i.e. from the various variant directories. */ private static class ColossusClassLoader extends ClassLoader { private List<String> directories = null; ColossusClassLoader(ClassLoader parent) { super(parent); } @Override public Class<?> findClass(String className) throws ClassNotFoundException { try { int index = className.lastIndexOf("."); String shortClassName = className.substring(index + 1); if (index == -1) { LOGGER.log(Level.SEVERE, "Loading of class \"" + className + "\" failed (no dot in class name)"); return null; } InputStream classDataIS = getInputStream(shortClassName + ".class", directories); if (classDataIS == null) { LOGGER.log(Level.SEVERE, "Couldn't find the class file anywhere ! (" + shortClassName + ".class)"); throw new FileNotFoundException("missing " + shortClassName + ".class"); } byte[] classDataBytes = new byte[classDataIS.available()]; classDataIS.read(classDataBytes); return defineClass(className, classDataBytes, 0, classDataBytes.length); } catch (FileNotFoundException e) { return super.findClass(className); } catch (IOException e) { return super.findClass(className); } } void setDirectories(List<String> d) { directories = d; } } public static final String KEY_CONTENT_TYPE = "ResourceLoaderContentType"; private static final String DEFAULT_FONT_NAME = "Lucida Sans Bold"; private static final int DEFAULT_FONT_STYLE = Font.PLAIN; private static final int DEFAULT_FONT_SIZE = 12; public static final Font DEFAULT_FONT = new Font(DEFAULT_FONT_NAME, DEFAULT_FONT_STYLE, DEFAULT_FONT_SIZE); // File.separator does not work in jar files, except in Unix. // A hardcoded '/' works in Unix, Windows, MacOS X, and jar files. private static final String pathSeparator = "/"; private static final String[] imageExtension = { ".png", ".gif" }; private static final ClassLoader baseCL = StaticResourceLoader.class .getClassLoader(); private static final ColossusClassLoader cl = new ColossusClassLoader( baseCL); private static final Map<String, Object> imageCache = Collections .synchronizedMap(new HashMap<String, Object>()); private static final Map<String, byte[]> fileCache = Collections .synchronizedMap(new HashMap<String, byte[]>()); // We used to use the normal separator from Constants, but that // does not need to be like that. Now introduced own constant // for that purpose to get in ResourceLoader rid of dependency // to server.Constants. public final static String REQUEST_TOKEN_SEPARATOR = " ~ "; private static String server = null; private static int serverPort = 0; public static void setDataServer(String server, int port) { StaticResourceLoader.server = server; StaticResourceLoader.serverPort = port; } public static void resetDataServer() { StaticResourceLoader.server = null; StaticResourceLoader.serverPort = 0; } /** * Give the String to mark directories. * @return The String to mark directories. */ public static String getPathSeparator() { return pathSeparator; } /** empty the cache so that all Chits have to be redrawn */ public synchronized static void purgeImageCache() { LOGGER.log(Level.FINEST, "Purging Image Cache."); imageCache.clear(); } /** empty the cache so that all files have to be reloaded */ public synchronized static void purgeFileCache() { LOGGER.log(Level.FINEST, "Purging File Cache."); fileCache.clear(); } /** * Return the first Image of name filename in the list of directories. * @param filename Name of the Image file to load (without extension). * @param directories List of directories to search (in order). * @return The Image, or null if it was not found. */ public synchronized static Image getImage(String filename, List<String> directories, int width, int height) { Image image = null; String mapKey = getMapKey(filename, directories); mapKey = mapKey + "(" + width + "," + height + ")"; Object cached = imageCache.get(mapKey); if ((cached != null) && (cached instanceof Image)) { image = (Image)cached; } if ((cached != null) && (cached instanceof ImageIcon)) { image = ((ImageIcon)cached).getImage(); } Iterator<String> it = directories.iterator(); while (it.hasNext() && (image == null)) { String path = it.next(); for (int i = 0; ((i < imageExtension.length) && (image == null)); i++) { image = tryLoadImageFromFile(filename + imageExtension[i], path, width, height); if (image == null) { ImageIcon temp = tryLoadImageIconFromResource(filename + imageExtension[i], path, width, height); if (temp != null) { image = temp.getImage(); } } } if (image != null) { imageCache.put(mapKey, image); } } if (image != null) { waitOnImage(image); } return (image); } /** * Return the first ImageIcon of name filename in the list of directories. * @param filename Name of the ImageIcon file to load (without extension). * @param directories List of directories to search (in order). * @return The ImageIcon, or null if it was not found. */ public synchronized static ImageIcon getImageIcon(String filename, List<String> directories, int width, int height) { ImageIcon icon = null; String mapKey = getMapKey(filename, directories); mapKey = mapKey + "(" + width + "," + height + ")"; Object cached = imageCache.get(mapKey); if ((cached != null) && (cached instanceof Image)) { icon = new ImageIcon((Image)cached); } if ((cached != null) && (cached instanceof ImageIcon)) { icon = (ImageIcon)cached; } Iterator<String> it = directories.iterator(); while (it.hasNext() && (icon == null)) { String path = it.next(); for (int i = 0; ((i < imageExtension.length) && (icon == null)); i++) { Image temp = tryLoadImageFromFile( filename + imageExtension[i], path, width, height); if (temp == null) { icon = tryLoadImageIconFromResource(filename + imageExtension[i], path, width, height); } else { icon = new ImageIcon(temp); } } if (icon != null) { imageCache.put(mapKey, icon); } } while (icon != null && icon.getImageLoadStatus() == MediaTracker.LOADING) { // no need for CPU time Thread.yield(); } return (icon); } /** * Try loading the file with the given filename in the given path * as an Image. * @param filename Name of the file to load. * @param path Path to search for the file * @return Resulting Image, or null if it fails. */ private static Image tryLoadImageFromFile(String filename, String path, int width, int height) { Image image = null; try { URL url; url = new URL("file:" + path + pathSeparator + fixFilename(filename)); // URL will not be null even if the file doesn't exist, // so we need to check if connection can be opened if (url.openStream() != null) { image = Toolkit.getDefaultToolkit().getImage(url); } } catch (MalformedURLException e) { // nothing to do } catch (IOException e) { // nothing to do } if (image != null) { return image.getScaledInstance(width, height, java.awt.Image.SCALE_SMOOTH); } else { return null; } } /** * Try loading the file file with the given filename in the given path * as an ImageIcon, through a Class loader. * @param filename Name of the file to load. * @param path Path to search for the file * @return Resulting ImageIcon, or null if it fails. */ private static ImageIcon tryLoadImageIconFromResource(String filename, String path, int width, int height) { ImageIcon icon = null; try { URL url = cl.getResource(path + pathSeparator + fixFilename(filename)); // URL will can be null even if the file exist, // so we need to check if connection can be opened if (url != null && url.openStream() != null) { icon = new ImageIcon(url); } } catch (Exception e) { // nothing to do } if (icon == null) { return null; } if ((icon.getIconWidth() == width) && (icon.getIconHeight() == height)) { return icon; } else { return new ImageIcon(icon.getImage().getScaledInstance(width, height, java.awt.Image.SCALE_SMOOTH)); } } /** * Return the first InputStream from file of name filename in the * list of directories, tell the getInputStream not to complain * if not found. * @param filename Name of the file to load. * @param directories List of directories to search (in order). * @return The InputStream, or null if it was not found. */ public static InputStream getInputStreamIgnoreFail(String filename, List<String> directories) { return getInputStream(filename, directories, server != null, false, true); } /** * Return the first InputStream from file of name filename in the * list of directories. * @param filename Name of the file to load. * @param directories List of directories to search (in order). * @return The InputStream, or null if it was not found. */ public static InputStream getInputStream(String filename, List<String> directories) { return getInputStream(filename, directories, server != null, false, false); } /** * Return the first InputStream from file of name filename in * the list of directories. * @param filename Name of the file to load. * @param directories List of directories to search (in order). * @param remote Ask the server for the stream. * @param cachedOnly Only look in the cache file, * do not try to load the file from permanent storage. * @param ignoreFail (=don't complain) if file not found * @return The InputStream, or null if it was not found. */ private static InputStream getInputStream(String filename, List<String> directories, boolean remote, boolean cachedOnly, boolean ignoreFail) { String mapKey = getMapKey(filename, directories); Object cached = fileCache.get(mapKey); byte[] data = null; if ((cached == null) && cachedOnly) { if (!ignoreFail) { LOGGER.log(Level.WARNING, "Requested file " + filename + " is requested cached-only but is not is cache."); } return null; } if ((cached == null) && ((!remote) || (server == null))) { synchronized (fileCache) { InputStream stream = null; Iterator<String> it = directories.iterator(); while (it.hasNext() && (stream == null)) { String path = it.next(); String fullPath = path + pathSeparator + fixFilename(filename); try { File tempFile = new File(fullPath); stream = new FileInputStream(tempFile); } catch (Exception e) { stream = cl.getResourceAsStream(fullPath); } } if (stream == null) { if (!remote && ignoreFail) { // If someone locally requests it as ignoreFail, // let's assume a remote requester later sees it the // same way. // Right now, the remote-requesting is not able to // submit the "ignore-fail" property... // TODO: submit that properly? // fileCacheIgnoreFail.put(mapKey, new Boolean(true)); } if (!ignoreFail) { LOGGER.log(Level.WARNING, "getInputStream:: " + " Couldn't get InputStream for file " + filename + " in " + directories + (cachedOnly ? " (cached only)" : "")); // TODO this sounds more serious than just a warning in the logs // Anyway now at least MarkersLoader does not complain any more... } } else { data = getBytesFromInputStream(stream); fileCache.put(mapKey, data); } } } else { synchronized (fileCache) { if (cached != null) { data = (byte[])cached; } else { try { Socket fileSocket = new Socket(server, serverPort); InputStream is = fileSocket.getInputStream(); if (is == null) { LOGGER.log(Level.WARNING, "getInputStream:: " + " Couldn't get InputStream from socket" + " for file " + filename + " in " + directories + (cachedOnly ? " (cached only)" : "")); // TODO this sounds more serious than just a warning in the logs } else { PrintWriter out = new PrintWriter( fileSocket.getOutputStream(), true); if (ignoreFail) { out.print(FILESERVER_IGNOREFAIL_SIGNAL + REQUEST_TOKEN_SEPARATOR); } out.print(filename); Iterator<String> it = directories.iterator(); while (it.hasNext()) { out.print(REQUEST_TOKEN_SEPARATOR + it.next()); } out.println(); data = getBytesFromInputStream(is); if (data != null && data.length == 0 && !ignoreFail) { LOGGER.log( Level.WARNING, "Got empty contents for file " + filename + " directories " + directories.toString()); } fileSocket.close(); fileCache.put(mapKey, data); } } catch (Exception e) { LOGGER.log(Level.SEVERE, "Failed to read from stream", e); } } } } return (data == null ? null : getInputStreamFromBytes(data)); } /** * Return the content of the specified file as an array of byte. * @param filename Name of the file to load. * @param directories List of directories to search (in order). * @param cachedOnly Only look in the cache file, * do not try to load the file from permanent storage. * @return An array of byte representing the content of the file, * or null if it fails. */ public static byte[] getBytesFromFile(String filename, List<String> directories, boolean cachedOnly, boolean ignoreFail) { InputStream is = getInputStream(filename, directories, server != null, cachedOnly, ignoreFail); if (is == null) { // right now only FileServerThread is using this method at all. if (!ignoreFail) { LOGGER.log(Level.WARNING, "getBytesFromFile:: " + " Couldn't get InputStream for file " + filename + " in " + directories + (cachedOnly ? " (cached only)" : "")); } return null; } return getBytesFromInputStream(is); } /** * Return the content of the specified InputStream as an array of byte. * @param InputStream The InputStream to use. * @return An array of byte representing the content * of the InputStream, or null if it fails. */ private static byte[] getBytesFromInputStream(InputStream is) { byte[] all = new byte[0]; try { byte[] data = new byte[1024 * 64]; int r = is.read(data); while (r > 0) { byte[] temp = new byte[all.length + r]; for (int i = 0; i < all.length; i++) { temp[i] = all[i]; } for (int i = 0; i < r; i++) { temp[i + all.length] = data[i]; } all = temp; r = is.read(data); } } catch (Exception e) { LOGGER.log(Level.SEVERE, "Can't Stringify stream " + is + ".", e); } return all; } /** * Return the content of the specified byte array as an InputStream. * @param data The byte array to convert. * @return An InputStream whose content is the data byte array. */ private static InputStream getInputStreamFromBytes(byte[] data) { if (data == null) { LOGGER.log(Level.WARNING, "getInputStreamFromBytes:: " + " Can't create InputStream from null byte array"); return null; } return new ByteArrayInputStream(data); } /** * Return the first OutputStream from file of name filename in * the list of directories. * @param filename Name of the file to load. * @param directories List of directories to search (in order). * @return The OutputStream, or null if it was not found. */ public static OutputStream getOutputStream(String filename, List<String> directories) { OutputStream stream = null; Iterator<String> it = directories.iterator(); while (it.hasNext() && (stream == null)) { String path = it.next(); String fullPath = path + pathSeparator + fixFilename(filename); try { stream = new FileOutputStream(fullPath); } catch (Exception e) { LOGGER.log(Level.FINEST, "getOutputStream:: " + " Couldn't get OutputStream for file " + filename + " in " + directories + "(" + e.getMessage() + ")"); } } return (stream); } /** * Return the first Document from file of name filename in * the list of directories. * It also add a property of key keyContentType and of type String * describing the content type of the Document. * This can currently load HTML and pure text. * @param filename Name of the file to load. * @param directories List of directories to search (in order). * @return The Document, or null if it was not found. */ public static Document getDocument(String filename, List<String> directories) { InputStream htmlIS = getInputStreamIgnoreFail(filename + ".html", directories); if (htmlIS != null) { try { HTMLEditorKit htedk = new HTMLEditorKit(); HTMLDocument htdoc = new HTMLDocument(htedk.getStyleSheet()); htdoc.putProperty(KEY_CONTENT_TYPE, "text/html"); htedk.read(htmlIS, htdoc, 0); return htdoc; } catch (Exception e) { LOGGER.log(Level.SEVERE, "html document exists, but cannot be loaded (" + filename + "): ", e); } return null; } InputStream textIS = getInputStreamIgnoreFail(filename + ".txt", directories); if (textIS == null) { textIS = getInputStreamIgnoreFail(filename, directories); } if (textIS != null) { try { // Must be a StyledDocument not a PlainDocument for // JEditorPane.setDocument() StyledDocument txtdoc = new DefaultStyledDocument(); char[] buffer = new char[128]; InputStreamReader textISR = new InputStreamReader(textIS); int read = 0; int offset = 0; while (read != -1) { read = textISR.read(buffer, 0, 128); if (read != -1) { txtdoc.insertString(offset, new String(buffer, 0, read), null); offset += read; } } txtdoc.putProperty(KEY_CONTENT_TYPE, "text/plain"); textISR.close(); return txtdoc; } catch (Exception e) { LOGGER.log(Level.SEVERE, "text document exists, but cannot be loaded (" + filename + "): " + e, e); } return null; } LOGGER.log(Level.WARNING, "No document for basename " + filename + " found " + "(neither .html, .txt nor without extension)!"); return null; } /** * Return the key to use in the image and file caches. * @param filename Name of the file. * @param directories List of directories. * @return A String to use as a key when storing/loading in a cache * the specified file from the specified list of directories. */ private static String getMapKey(String filename, List<String> directories) { String[] filenames = new String[1]; filenames[0] = filename; return getMapKey(filenames, directories); } /** * Return the key to use in the image cache. * @param filenames Array of name of files. * @param directories List of directories. * @return A String to use as a key when storing/loading in a cache * the specified array of name of files from the specified * list of directories. */ private static String getMapKey(String[] filenames, List<String> directories) { StringBuilder buf = new StringBuilder(filenames[0]); for (int i = 1; i < filenames.length; i++) { buf.append(","); buf.append(filenames[i]); } Iterator<String> it = directories.iterator(); while (it.hasNext()) { String dir = it.next(); buf.append(","); buf.append(dir); } return buf.toString(); } /** * Return the composite image made from blending the given filenames * from the given directories. * @param filenames Names of the Images files to load (without extension). * @param directories List of directories to search (in order). * @return The composite Image, or null if any part was not found. */ public synchronized static Image getCompositeImage(String[] filenames, List<String> directories, int width, int height) { BufferedImage bi; String mapKey = getMapKey(filenames, directories); mapKey = mapKey + "(" + width + "," + height + ")"; Object cached = imageCache.get(mapKey); if ((cached != null) && (cached instanceof Image)) { return (Image)cached; } if ((cached != null) && (cached instanceof ImageIcon)) { return ((ImageIcon)cached).getImage(); } Image[] tempImage = new Image[filenames.length]; for (int i = 0; i < filenames.length; i++) { tempImage[i] = getImage(filenames[i], directories, width, height); if (tempImage[i] == null) { tempImage[i] = tryBuildingNonexistentImage(filenames[i], width, height, directories); } if (tempImage[i] == null) { LOGGER.log(Level.SEVERE, "during creation of [" + mapKey + "], loading failed for " + filenames[i]); return null; } } bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); Graphics2D biContext = bi.createGraphics(); for (int i = 0; (biContext != null) && (i < filenames.length); i++) { biContext.drawImage(tempImage[i], 0, 0, width, height, null); waitOnImage(bi); } imageCache.put(mapKey, bi); return bi; } /** * Try to build an image when there is no source file to create it. * Includes generation of some dynamic layers of images for * composite image building. * @see #getCompositeImage(String[], List) * @param filename The name of the missing file. * @param width Width of the image to create. * @param height Height of the image to create. * @param directories List of searched directories. * @return The generated Image. */ private synchronized static Image tryBuildingNonexistentImage( String filename, int width, int height, List<String> directories) { Image tempImage = null; if (filename.startsWith("Plain-")) { tempImage = createPlainImage(width, height, colorFromFilename(filename, "Plain-")); } if (filename.startsWith("Power-")) { int val = numberFromFilename(filename, "Power-"); tempImage = createNumberImage(width, height, val, false, colorFromFilename(filename, "Power-")); } if (filename.startsWith("Skill-")) { int val = numberFromFilename(filename, "Skill-"); tempImage = createNumberImage(width, height, val, true, colorFromFilename(filename, "Skill-")); } if (filename.startsWith("Flying") || filename.startsWith("Rangestrike")) { int fly_ix = filename.indexOf("Flying"); int rgs_ix = filename.indexOf("Rangestrike"); String prefix = (fly_ix != -1 ? "Flying" : "") + (rgs_ix != -1 ? "Rangestrike" : ""); tempImage = createColorizedImage(prefix + "Base", colorFromFilename(filename, prefix), directories, width, height); } if (filename.indexOf("-Name") != -1) { String name = filename.substring(0, filename.indexOf("-Name")); tempImage = createNameImage(width, height, name, false, colorFromFilename(filename, name + "-Name")); } if (filename.indexOf("-Subscript") != -1) { String name = filename .substring(0, filename.indexOf("-Subscript")); tempImage = createNameImage(width, height, name, true, colorFromFilename(filename, name + "-Subscript")); } if (tempImage == null) { LOGGER.log(Level.WARNING, "WARNING: creation failed for " + filename); return createPlainImage(width, height, Color.white, true); } waitOnImage(tempImage); String mapKey = getMapKey(filename, directories); mapKey = mapKey + "(" + width + "," + height + ")"; imageCache.put(mapKey, tempImage); return (tempImage); } /** * Create an Image with only the given number on it. * @param width Width of the image to create. * @param height Height of the image to create. * @param value The number to draw on the image. * @param right The number is on the right side (default is left side). * @param color The color to use to draw the number. * @return The generated Image. */ private static Image createNumberImage(int width, int height, int value, boolean right, Color color) { BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); Graphics2D biContext = bi.createGraphics(); biContext.setColor(new Color((float)1., (float)1., (float)1., (float)0.)); biContext.fillRect(0, 0, width, height); biContext.setColor(color); int fontsize = (width + height) / 10; biContext.setFont(DEFAULT_FONT.deriveFont((float)fontsize)); FontMetrics fm = biContext.getFontMetrics(); Rectangle2D sb = fm.getStringBounds("" + value, biContext); int sw = (int)sb.getWidth(); String valueTxt = (value > 0 ? "" + value : "X"); if (right) { biContext.drawString(valueTxt, (width - (sw + 2)), height - 2); } else { biContext.drawString(valueTxt, 2, height - 2); } waitOnImage(bi); return bi; } /** * Create an Image with only the given String on it. * @param width Width of the image to create. * @param height Height of the image to create. * @param name The String to draw on the image. * @param down The name is on the bottom (default is top). * @param color The color to use to draw the String. * @return The generated Image. */ private static Image createNameImage(int width, int height, String name, boolean down, Color color) { BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); Graphics2D biContext = bi.createGraphics(); biContext.setColor(new Color((float)1., (float)1., (float)1., (float)0.)); biContext.fillRect(0, 0, width, height); biContext.setColor(color); int fontsize = (width + height) / 10; biContext.setFont(DEFAULT_FONT.deriveFont((float)fontsize)); Font font = biContext.getFont(); int size = font.getSize(); FontMetrics fm = biContext.getFontMetrics(); Rectangle2D sb = fm.getStringBounds(name, biContext); int sw = (int)sb.getWidth(); while ((sw >= width) && (size > 1)) { size--; biContext.setFont(font.deriveFont((float)size)); fm = biContext.getFontMetrics(); sb = fm.getStringBounds(name, biContext); sw = (int)sb.getWidth(); } int offset = (width - sw) / 2; biContext.drawString(name, offset, (down ? (height - 2) : (1 + fm.getMaxAscent()))); waitOnImage(bi); return bi; } /** * Create an Image that is only a plain rectangle. * @param width Width of the image to create. * @param height Height of the image to create. * @param color The color to use to fill the rectangle. * @return The generated Image. */ private static Image createPlainImage(int width, int height, Color color) { return createPlainImage(width, height, color, 0, 0, width, height, false); } /** * Create an Image that is only a plain rectangle, with an optional border. * @param width Width of the image to create. * @param height Height of the image to create. * @param color The color to use to fill the rectangle. * @param border Whether to add a black border. * @return The generated Image. */ private static Image createPlainImage(int width, int height, Color color, boolean border) { return createPlainImage(width, height, color, 0, 0, width, height, border); } /** * Create an Image that only contains a colored rectangle, * with an optional border. * @param width Width of the image to create. * @param height Height of the image to create * @param color The color to use to fill the rectangle. * @param t_x Left border of the rectangle. * @param t_y Top border of the rectangle. * @param t_w Width of the rectangle. * @param t_h Height of the rectangle. * @param border Whether to add a black border. * @return The generated Image. */ private static Image createPlainImage(int width, int height, Color color, int t_x, int t_y, int t_w, int t_h, boolean border) { BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); Graphics2D biContext = bi.createGraphics(); biContext.setColor(new Color((float)1., (float)1., (float)1., (float)0.)); biContext.fillRect(0, 0, width, height); biContext.setColor(color); biContext.fillRect(t_x, t_y, t_w, t_h); if (border) { biContext.setColor(Color.black); biContext.drawRect(0, 0, width - 1, height - 1); } waitOnImage(bi); return bi; } /** * Create a colorized version of the image contained in the given file. * @param filename Name of the Image file to load. * @param directories List of directories to search (in order). * @param color Color to use. * @return An Image composed of the content of the file, * with the opaque part filled the the given color, and everythin else * white. The alpha channel (aka transparency) is untouched. */ private static Image createColorizedImage(String filename, Color color, List<String> directories, int width, int height) { Image temp = getImage(filename, directories, width, height); ImageIcon tempIcon = new ImageIcon(temp); while (tempIcon.getImageLoadStatus() == MediaTracker.LOADING) { Thread.yield(); } if (tempIcon.getImageLoadStatus() != MediaTracker.COMPLETE) { LOGGER.log(Level.SEVERE, "Image loading of " + filename + " failed (" + tempIcon.getImageLoadStatus() + ")"); return null; } BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); Graphics2D biContext = bi.createGraphics(); biContext.drawImage(temp, 0, 0, width, height, null); waitOnImage(bi); int[] pi; WritableRaster ra = bi.getRaster(); // rebuild the image from the Alpha Channel // fully-opaque pixel are set to the color, // everything else is white. // this should have been a LookupOp, but // I couldn't make it reliable across platform :-( for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { pi = ra.getPixel(x, y, (int[])null); if (pi[3] == 0xFF) // fully opaque { pi[0] = color.getRed(); pi[1] = color.getGreen(); pi[2] = color.getBlue(); } else { pi[0] = pi[1] = pi[2] = 0xFF; } ra.setPixel(x, y, pi); } } return bi; } /** * Wait until the Image in parameter is fully drawn. * @param image Image to wait upon. */ private static void waitOnImage(Image image) { ImageIcon icon = new ImageIcon(image); while (icon.getImageLoadStatus() == MediaTracker.LOADING) { Thread.yield(); } if (icon.getImageLoadStatus() != MediaTracker.COMPLETE) { LOGGER.log(Level.SEVERE, "Image loading failed (" + icon.getImageLoadStatus() + ")"); } } /** * Extract a number from a filename, ignoring a prefix. * @param filename File name to extract from. * @param prefix Prefix to ignore. Has to match the prefix of the file name, otherwise * 0 will be returned. * @return The extracted number. */ private static int numberFromFilename(String filename, String prefix) { if (!(filename.startsWith(prefix))) { LOGGER.log(Level.WARNING, "Warning: " + prefix + " is not prefix of " + filename); // boil out if we are on a developer box, use default otherwise assert false : "illegal combination for filename and prefix"; return 0; } int index = prefix.length(); int index2 = index; if (index2 >= filename.length()) { return 0; } char c = filename.charAt(index2); if (c == '-') { index2++; if (index2 < filename.length()) { c = filename.charAt(index2); } else { c = '*'; } } while ((c >= '0') && (c <= '9')) { index2++; if (index2 < filename.length()) { c = filename.charAt(index2); } else { c = '*'; } } String sub = filename.substring(index, index2); int val = 0; try { val = Integer.parseInt(sub); } catch (Exception e) { LOGGER.log(Level.SEVERE, "during number extraction: " + e.getMessage(), e); } return val; } /** * Extract a color name from a filename, ignoring a prefix * @param filename File name to extract from. * @param prefix Prefix to ignore. Has to match the beginning of the file * name, otherwise "black" will be used as default value. * @return The extracted color name. */ private static String colorNameFromFilename(String filename, String prefix) { if (!(filename.startsWith(prefix))) { LOGGER .log(Level.WARNING, prefix + " is not prefix of " + filename); // bail out if we are on a developer box, use default otherwise assert false : "illegal combination for filename and prefix"; return "black"; } int index = prefix.length(); int index2 = index; if (index2 >= filename.length()) { return "black"; } char c = filename.charAt(index2); if (c == '-') { index2++; if (index2 < filename.length()) { c = filename.charAt(index2); } else { c = '*'; } } while ((c >= '0') && (c <= '9')) { index2++; if (index2 < filename.length()) { c = filename.charAt(index2); } else { c = '*'; } } if (c == '-') { index2++; } if (index2 >= filename.length()) { return "black"; } String sub = filename.substring(index2); return sub; } /** * Extract a color from a filename, ignoring a prefix. * @param filename File name to extract from. * @param prefix Prefix to ignore. * @return The extracted Color. */ private static Color colorFromFilename(String filename, String prefix) { return HTMLColor .stringToColor(colorNameFromFilename(filename, prefix)); } /** * Fix a filename by replacing space with underscore. * @param filename Filename to fix. * @return The fixed filename. */ private static String fixFilename(String filename) { return filename.replace(' ', '_'); } /** * Create an instance of the class whose name is in parameter. * * @param className The name of the class to use. * @param directories List of directories to search (in order). * @return A new object, instance from the given class. * @throws ObjectCreationException iff the object could not be created */ public static Object getNewObject(String className, List<String> directories) throws ObjectCreationException { return getNewObject(className, directories, null); } /** * Create an instance of the class whose name is in parameter, * using parameters. * * If no parameters are given, the default constructor is used. * * @param className The name of the class to use, must not be null. * @param directories List of directories to search (in order), must not be null. * @param parameter Array of parameters to pass to the constructor, can be null. * @return A new object, instance from the given class or null if * instantiation failed. * @throws ObjectCreationException iff the object could not be created for some reason */ public static Object getNewObject(String className, List<String> directories, Object[] parameter) throws ObjectCreationException { Class<?> theClass = null; cl.setDirectories(directories); try { theClass = cl.loadClass(className); } catch (Exception e) { throw new ObjectCreationException( "Could not load class with name '" + className + "'", e); } if (parameter != null) { Class<?>[] paramClasses = new Class[parameter.length]; for (int i = 0; i < parameter.length; i++) { paramClasses[i] = parameter[i].getClass(); } try { Constructor<?> c = theClass.getConstructor(paramClasses); return c.newInstance(parameter); } catch (Exception e) { LOGGER.log(Level.INFO, "Loading or instantiating class' constructor for \"" + className + "\" failed", e); Constructor<?>[] constructors = theClass.getConstructors(); for (int i = 0; i < constructors.length; i++) { LOGGER.log(Level.INFO, "I have access to: " + constructors[i]); } throw new ObjectCreationException( "Loading or instantiating class' constructor for \"" + className + "\" failed", e); } } else { try { return theClass.newInstance(); } catch (Exception e) { throw new ObjectCreationException( "Call to default constructor of '" + className + "' failed", e); } } } /** * Force adding the given data as belonging to the given key * in the file cache. * @see #getMapKey(String, List) * @see #getMapKey(String[], List) * @param mapKey Key to use in the cache. * @param data File content to add. */ public static void putIntoFileCache(String mapKey, byte[] data) { fileCache.put(mapKey, data); } /** * Dump the file cache as a List of XML "DataFile" Element, * with the file key as attribute "DataFileKey", and the * file data as a CDATA content. * @return A list of XML Element. */ public static List<Element> getFileCacheDump() { List<Element> allElement = new ArrayList<Element>(); Set<String> allKeys = fileCache.keySet(); Iterator<String> it = allKeys.iterator(); while (it.hasNext()) { String mapKey = it.next(); // Heuristic: do not store class files. Their map key looks e.g.: // "PET3variantHint.class,C:\workspace\variants-own\PET3variant,Default" if (mapKey.indexOf("Hint.class,") != -1) { // Do not dump class file - it expects only XML datafiles. } else { byte[] data = fileCache.get(mapKey); Element el = new Element("DataFile"); el.setAttribute("DataFileKey", mapKey); el.addContent(new org.jdom.CDATA(new String(data))); allElement.add(el); } } return allElement; } }