/**
* 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.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Image;
import java.io.File;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import 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 = null;
/**
* 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.
*
* @param windowSize
*/
public static void preload(final Dimension windowSize) {
if (windowSize != null && !windowSize.equals(lastWindowSize)) {
dirty = true;
logger.info("Window size changes from " + lastWindowSize
+ " to " + windowSize);
lastWindowSize = windowSize;
}
updateIfDirty();
}
/**
* Create and start a new background preload thread.
*/
private static void startBackgroundPreloading() {
if ("true".equals(System.getProperty("java.awt.headless", "false"))) {
return; // Do not preload in headless mode
}
if (lastWindowSize == null) return; // Wait for initial preload.
preloadThread = new Thread(FreeCol.CLIENT_THREAD+"Resource loader") {
public void run() {
// Make a local copy of the resources to load.
List<Resource> resources
= new LinkedList<Resource>(mergedContainer.getResources().values());
int n = 0;
for (Resource r : resources) {
if (preloadThread != this) return; // Cancelled!
r.preload();
n++;
}
logger.info("Background thread preloaded " + n
+ " resources.");
}
};
preloadThread.setPriority(2);
preloadThread.start();
}
/**
* Updates the resource mappings after making changes.
*/
private static void updateIfDirty() {
if (dirty) {
dirty = false;
preloadThread = null;
createMergedContainer();
startBackgroundPreloading();
}
}
/**
* Creates a merged container containing all the resources.
*/
private static void createMergedContainer() {
ResourceMapping mc = new ResourceMapping();
mc.addAll(baseMapping);
mc.addAll(tcMapping);
mc.addAll(campaignMapping);
mc.addAll(scenarioMapping);
for (ResourceMapping rm : modMappings) mc.addAll(rm);
mc.addAll(gameMapping);
mergedContainer = mc;
}
/**
* Gets 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) {
updateIfDirty();
final Resource r = mergedContainer.get(resourceId);
if (r == null) { // Log only unexpected failures
if (!resourceId.startsWith("dynamic.")) {
logger.finest("getResource(" + resourceId
+ ", " + type.getName() + ") failed");
}
return null;
}
if (!type.isInstance(r)) { // Log type errors
logger.warning("getResource(" + resourceId
+ ", " + type.getName() + ") -> "
+ r.getClass().getName());
return null;
}
return type.cast(r);
}
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
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) {
final ChipResource r = getResource(resource, ChipResource.class);
return (r != null) ? r.getImage() : null;
}
public static Image getChip(final String resource, double scale) {
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) {
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();
}
}