/** * Copyright (C) 2002-2012 The FreeCol Team * * This file is part of FreeCol. * * FreeCol is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * FreeCol is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with FreeCol. If not, see <http://www.gnu.org/licenses/>. */ package net.sf.freecol.common.resources; import java.io.File; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Map.Entry; import java.util.logging.Logger; import org.freecolandroid.repackaged.java.awt.Color; import org.freecolandroid.repackaged.java.awt.Dimension; import org.freecolandroid.repackaged.java.awt.Font; import org.freecolandroid.repackaged.java.awt.Image; import org.freecolandroid.repackaged.javax.swing.ImageIcon; import net.sf.freecol.FreeCol; import net.sf.freecol.common.io.sza.SimpleZippedAnimation; /** * Class for getting resources (images, audio etc). */ public class ResourceManager { private static final Logger logger = Logger.getLogger(ResourceManager.class.getName()); // the number of different river styles public static final int RIVER_STYLES = 81; /* * The following fields are mappings from resource IDs * to resources. A mapping is defined within a specific * context. See the comment on each field's setter for * more information: */ private static ResourceMapping baseMapping; private static ResourceMapping tcMapping; private static ResourceMapping campaignMapping; private static ResourceMapping scenarioMapping; private static ResourceMapping gameMapping; private static List<ResourceMapping> modMappings = new LinkedList<ResourceMapping>(); /* * All the mappings above merged into this single * ResourceMapping according to precendence. */ private static ResourceMapping mergedContainer; private static volatile Thread preloadThread = null; private static volatile boolean dirty = false; private static Dimension lastWindowSize; /** * Sets the mappings specified in the date/base-directory * @param _baseMapping The mapping between IDs and files. */ public static void setBaseMapping(final ResourceMapping _baseMapping) { baseMapping = _baseMapping; dirty = true; } /** * Sets the mappings specified for a Total Conversion (TC). * @param _tcMapping The mapping between IDs and files. */ public static void setTcMapping(final ResourceMapping _tcMapping) { tcMapping = _tcMapping; dirty = true; } /** * Sets the mappings specified by mods. * @param _modMappings A list of the mapping between IDs and files. */ public static void setModMappings(final List<ResourceMapping> _modMappings) { modMappings = _modMappings; dirty = true; } /** * Sets the mappings specified in a campaign. * @param _campaignMapping The mapping between IDs and files. */ public static void setCampaignMapping(final ResourceMapping _campaignMapping) { campaignMapping = _campaignMapping; dirty = true; } /** * Sets the mappings specified in a scenario. * @param _scenarioMapping The mapping between IDs and files. */ public static void setScenarioMapping(final ResourceMapping _scenarioMapping) { scenarioMapping = _scenarioMapping; dirty = true; } /** * Sets the mappings specified in a game, such as the player colors. * * @param _gameMapping The mapping between IDs and resources. */ public static void setGameMapping(final ResourceMapping _gameMapping) { gameMapping = _gameMapping; dirty = true; } /** * Add more mappings to the game mapping. * * @param mapping The <code>ResourceMapping</code> to add. */ public static void addGameMapping(final ResourceMapping mapping) { if (gameMapping == null) gameMapping = new ResourceMapping(); gameMapping.addAll(mapping); dirty = true; } /** * Add a single mappings to the game mapping. * * @param key The key. * @param resource The resource to add. */ public static void addGameMapping(String key, Resource resource) { if (gameMapping == null) gameMapping = new ResourceMapping(); gameMapping.add(key, resource); mergedContainer.add(key, resource); } /** * Preload resources. This method is intended to * be called when starting the application, as * it blocks until resources needed for the first * panels have been loaded. * * It also ensures that the {@link #startBackgroundPreloading(Dimension) * background preloading thread} is started. * * @param windowSize */ public static void preload(final Dimension windowSize) { System.out.println("ResourceManager.preload()"); if (lastWindowSize != windowSize) { dirty = true; } lastWindowSize = windowSize; updateIfDirty(); // starts: startBackgroundPreloading } /** * Starts background preloading of resources. * @param windowSize The window size to use when scaling * full screen size images. */ public static void startBackgroundPreloading(final Dimension windowSize) { System.out.println("ResourceManager.startBackgroundPreloading()"); lastWindowSize = windowSize; if (dirty) { updateIfDirty(); return; // startBackgroundPreloading will be called from update } preloadThread = new Thread(FreeCol.CLIENT_THREAD+"Resource loader") { public void run() { // List<Resource> resources = // new LinkedList<Resource>(mergedContainer.getResources().values()); // for (Resource r : resources) { // if (preloadThread != this) { // return; // } // r.preload(); // } // Debug List<Resource> resources = new LinkedList<Resource>(mergedContainer.getResources().values()); for (Entry<String, Resource> e : mergedContainer.getResources().entrySet()) { if (preloadThread != this) { return; } if (e.getValue() == null) { System.err.println("Resource not loaded: " + e.getKey()); } e.getValue().preload(); } } }; preloadThread.setPriority(2); preloadThread.start(); } /** * Updates the resource mappings after making changes. */ private static void updateIfDirty() { if (dirty) { dirty = false; preloadThread = null; createMergedContainer(); startBackgroundPreloading(lastWindowSize); } } /** * Creates a merged container for easy access to resources. */ private static void createMergedContainer() { ResourceMapping _mergedContainer = new ResourceMapping(); _mergedContainer.addAll(baseMapping); _mergedContainer.addAll(tcMapping); _mergedContainer.addAll(campaignMapping); _mergedContainer.addAll(scenarioMapping); ListIterator<ResourceMapping> it = modMappings.listIterator(modMappings.size()); while (it.hasPrevious()) { _mergedContainer.addAll(it.previous()); } _mergedContainer.addAll(gameMapping); mergedContainer = _mergedContainer; } /** * Returns the resource of the given type. * @param <T> The type of the resource to get. * @param resourceId The resource to get. * @param type The type of the resource to get. * @return The resource if there is one with the given * resourceId and type, or else <code>null</code>. */ public static <T> T getResource(final String resourceId, final Class<T> type) { final Resource r = mergedContainer.get(resourceId); if (type.isInstance(r)) { return type.cast(r); } if (r == null) { // Log only unexpected failures if (!resourceId.startsWith("dynamic.")) { logger.finest("getResource(" + resourceId + ", " + type.getName() + ") failed"); } } else { // Log type errors logger.finest("getResource(" + resourceId + ", " + type.getName() + ") -> " + r.getClass().getName()); } return null; } public static boolean hasResource(final String resourceId) { return mergedContainer.containsKey(resourceId); } public static Map<String, Resource> getResources() { return mergedContainer.getResources(); } /** * Returns the animation specified by the given name. * * @param resource The name of the resource to return. * @return The animation identified by <code>resource</code> * or <code>null</code> if there is no animation * identified by that name. */ public static SimpleZippedAnimation getSimpleZippedAnimation(final String resource) { updateIfDirty(); final SZAResource r = getResource(resource, SZAResource.class); return (r != null) ? r.getSimpleZippedAnimation() : null; } /** * Gets the <code>Video</code> represented by the given resource. * @return The <code>Video</code> in it's original size. */ public static Video getVideo(final String resource) { updateIfDirty(); final VideoResource r = getResource(resource, VideoResource.class); return (r != null) ? r.getVideo() : null; } /** * Returns the animation specified by the given name. * * @param resource The name of the resource to return. * @param scale The size of the requested animation (with 1 * being normal size, 2 twice the size, 0.5 half the * size etc). Rescaling will be performed unless using 1. * @return The animation identified by <code>resource</code> * or <code>null</code> if there is no animation * identified by that name. */ public static SimpleZippedAnimation getSimpleZippedAnimation(final String resource, final double scale) { updateIfDirty(); final SZAResource r = getResource(resource, SZAResource.class); return (r != null) ? r.getSimpleZippedAnimation(scale) : null; } /** * Returns the image specified by the given name. * * @param resource The name of the resource to return. * @return The image identified by <code>resource</code> * or <code>null</code> if there is no image * identified by that name. */ public static Image getImage(final String resource) { updateIfDirty(); final ImageResource r = getResource(resource, ImageResource.class); return (r != null) ? r.getImage() : null; } /** * Returns the image specified by the given name. * * @param resource The name of the resource to return. * @param scale The size of the requested image (with 1 being normal size, * 2 twice the size, 0.5 half the size etc). Rescaling * will be performed unless using 1. * @return The image identified by <code>resource</code> * or <code>null</code> if there is no image * identified by that name. */ public static Image getImage(final String resource, final double scale) { updateIfDirty(); final ImageResource r = getResource(resource, ImageResource.class); return (r != null) ? r.getImage(scale) : null; } /** * Returns the image specified by the given name. * * @param resource The name of the resource to return. * @param size The size of the requested image. Rescaling * will be performed if necessary. * @return The image identified by <code>resource</code> * or <code>null</code> if there is no image * identified by that name. */ public static Image getImage(final String resource, final Dimension size) { updateIfDirty(); final ImageResource r = getResource(resource, ImageResource.class); return (r != null) ? r.getImage(size) : null; } /** * Returns the a grayscale version of the image specified by * the given name. * * @param resource The name of the resource to return. * @param size The size of the requested image. Rescaling * will be performed if necessary. * @return The image identified by <code>resource</code> * or <code>null</code> if there is no image * identified by that name. */ public static Image getGrayscaleImage(final String resource, final Dimension size) { updateIfDirty(); final ImageResource r = getResource(resource, ImageResource.class); return (r != null) ? r.getGrayscaleImage(size) : null; } /** * Returns the grayscale version of the image specified by the given name. * * @param resource The name of the resource to return. * @param scale The size of the requested image (with 1 being normal size, * 2 twice the size, 0.5 half the size etc). Rescaling * will be performed unless using 1. * @return The image identified by <code>resource</code> * or <code>null</code> if there is no image * identified by that name. */ public static Image getGrayscaleImage(final String resource, final double scale) { updateIfDirty(); final ImageResource r = getResource(resource, ImageResource.class); return (r != null) ? r.getGrayscaleImage(scale) : null; } /** * Creates an <code>ImageIcon</code> for the image of * the given name. * * @param resource The name of the resource to return. * @return An <code>ImageIcon</code> created with the image * identified by <code>resource</code> or * <code>null</code> if there is no image identified * by that name. * @see #getImage(String) */ public static ImageIcon getImageIcon(final String resource) { updateIfDirty(); final Image im = getImage(resource); return (im != null) ? new ImageIcon(im) : null; } /** * Returns the <code>Color</code> with the given name. * * @param resource The name of the resource to return. * @return An <code>Color</code> created with the image * identified by <code>resource</code> or * <code>null</code> if there is no color identified * by that name. * @see #getImage(String) */ public static Color getColor(final String resource) { updateIfDirty(); final ColorResource r = getResource(resource, ColorResource.class); return (r != null) ? r.getColor() : null; } /** * Returns the <code>Color</code> for the given production bonus. * * @param bonus The production bonus to look up. * @return An <code>Color</code> created with the image * identified by <code>resource</code> or * <code>null</code> if there is no color identified * by that name. */ public static Color getProductionColor(int bonus) { return ResourceManager.getColor("productionBonus." + bonus + ".color"); } /** * Returns the <code>Image</code> with the given name. * * @param resource The name of the resource to return. * @return An <code>Image</code> created with the image * identified by <code>resource</code> or * <code>null</code> if there is no image identified * by that name. * @see #getImage(String) */ public static Image getChip(final String resource) { updateIfDirty(); final ChipResource r = getResource(resource, ChipResource.class); return (r != null) ? r.getImage() : null; } public static Image getChip(final String resource, double scale) { updateIfDirty(); final ChipResource r = getResource(resource, ChipResource.class); return (r != null) ? r.getImage(scale) : null; } /** * Gets the font with the given name. * * @param resource The name of the resource to query. * @return The <code>Font</code> found in a FontResource, which * may default to the Java default font if the resource failed * to load. */ public static Font getFont(final String resource) { updateIfDirty(); final FontResource r = getResource(resource, FontResource.class); if (r == null) return FontResource.getEmergencyFont(); return r.getFont(); } /** * Gets the font with the given name and applies a style change. * * @param resource The name of the resource to query. * @return The <code>Font</code> found in a FontResource, which * may default to the Java default font if the resource failed * to load. */ public static Font getFont(final String resource, int style) { Font font = ResourceManager.getFont(resource); return font.deriveFont(style); } /** * Gets the font with the given name and applies a size change. * * @param resource The name of the resource to query. * @return The <code>Font</code> found in a FontResource, which * may default to the Java default font if the resource failed * to load. */ public static Font getFont(final String resource, float size) { Font font = ResourceManager.getFont(resource); return font.deriveFont(size); } /** * Gets the font with the given name and applies style and size changes. * * @param resource The name of the resource to query. * @return The <code>Font</code> found in a FontResource, which * may default to the Java default font if the resource failed * to load. */ public static Font getFont(final String resource, int style, float size) { Font font = ResourceManager.getFont(resource); return font.deriveFont(style, size); } /** * Gets a FAFile resource with the given name. * * @param resource The name of the resource to query. * @return The <code>FAFile</code> found in a FAFileResource. */ public static FAFile getFAFile(final String resource) { final FAFileResource r = getResource(resource, FAFileResource.class); return (r == null) ? null : r.getFAFile(); } /** * Gets an audio resource with the given name. * * @param resource The name of the resource to query. * @return A <code>File</code> containing the audio data. */ public static File getAudio(final String resource) { final AudioResource r = getResource(resource, AudioResource.class); return (r == null) ? null : r.getAudio(); } }