package com.indyforge.twod.engine.resources.assets;
import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import javax.imageio.ImageIO;
import com.indyforge.twod.engine.graphics.GraphicsRoutines;
import com.indyforge.twod.engine.io.IoRoutines;
/**
* This class simplifies the handling of resources. Let it be images, maps,
* sounds or something else. An asset manager basically creates assets which
* represent data from an archive.
*
* An asset manager and all its components are serializable to heavily simplify
* the networking part. When you serialize assets you really just serialize its
* paths. The deserializing component must have the same asset bundles to
* restore the native assets. This concept is the best attempt to simplify the
* exchange while maintaining consistency.
*
* @author Christopher Probst
* @author Matthias Hesse
* @see Asset
* @see AssetBundle
* @see AssetLoader
*/
public final class AssetManager implements Serializable {
/**
* @return true if the environment is headless.
*/
public static boolean isHeadless() {
return GraphicsEnvironment.isHeadless();
}
/*
* The font loader.
*/
private static final AssetLoader<Font> FONT_LOADER = new AssetLoader<Font>() {
/**
*
*/
private static final long serialVersionUID = 1L;
@Override
public Font loadAsset(AssetManager assetManager, String assetPath)
throws Exception {
return Font.createFont(Font.TRUETYPE_FONT,
assetManager.open(assetPath));
}
};
/*
* The byte array loader.
*/
private static final AssetLoader<byte[]> BYTE_ARRAY_LOADER = new AssetLoader<byte[]>() {
/**
*
*/
private static final long serialVersionUID = 1L;
@Override
public byte[] loadAsset(AssetManager assetManager, String assetPath)
throws Exception {
return IoRoutines.readFully(assetManager.open(assetPath));
}
};
/*
* The image loader.
*/
private static final AssetLoader<BufferedImage> IMAGE_LOADER = new AssetLoader<BufferedImage>() {
/**
*
*/
private static final long serialVersionUID = 1L;
@Override
public BufferedImage loadAsset(AssetManager assetManager,
String assetPath) throws Exception {
return ImageIO.read(assetManager.open(assetPath));
}
};
/*
* The optimized image loader.
*/
private static final AssetLoader<BufferedImage> OPTIMIZED_IMAGE_LOADER = new AssetLoader<BufferedImage>() {
/**
*
*/
private static final long serialVersionUID = 1L;
@Override
public BufferedImage loadAsset(AssetManager assetManager,
String assetPath) throws Exception {
return GraphicsRoutines.optimizeImage(IMAGE_LOADER.loadAsset(
assetManager, assetPath));
}
};
/**
*
*/
private static final long serialVersionUID = 1L;
/*
* Here we store all asset bundles.
*/
private final Map<File, AssetBundle> assetBundles;
/*
* Here we store all asset bundle hashes.
*/
private final Map<File, String> assetBundleHashes;
/*
* Here we store all archives of this asset manager.
*/
private transient Set<ZipFile> archives;
/**
* @return a set with all open zip files.
* @throws Exception
* If an exception occurs.
*/
private Set<ZipFile> openArchives() throws Exception {
Set<ZipFile> tmp = new LinkedHashSet<ZipFile>();
// Iterate over all asset bundles
for (AssetBundle assetBundle : assetBundles.values()) {
// Open new zip file
tmp.add(new ZipFile(assetBundle.getArchive()));
}
return tmp;
}
/*
* When loading the asset manager from stream all archive should be opened
* here.
*/
private void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
// Restore all vars
in.defaultReadObject();
try {
// Try to open the zip files
archives = openArchives();
} catch (Exception e) {
throw new IOException("Failed to load asset", e);
}
}
/**
* Creates a new asset manager using the given archive file. All included
* asset bundles will be loaded automatically.
*
* @param archive
* The archive file you want to load.
* @throws Exception
* If an exception occurs.
*/
public AssetManager(File archive) throws Exception {
if (archive == null) {
throw new NullPointerException("archive");
}
// Used to store the archive files during collection
Set<File> destination = new LinkedHashSet<File>();
// Collect all archives
AssetBundle.collectArchives(archive, destination);
// Tmp asset bundles
Map<File, AssetBundle> tmpAssetBundles = new LinkedHashMap<File, AssetBundle>();
// Tmp file hashes
Map<File, String> tmpAssetBundleHashes = new LinkedHashMap<File, String>();
// Iterate over the destinations
for (File file : destination) {
// Create a new asset bundle
AssetBundle assetBundle = new AssetBundle(file);
// Add a new asset bundle for each file
tmpAssetBundles.put(file, assetBundle);
// Store archive hash
tmpAssetBundleHashes.put(file, assetBundle.getArchiveHash());
}
// Save if everything is ok
assetBundles = Collections.unmodifiableMap(tmpAssetBundles);
assetBundleHashes = Collections.unmodifiableMap(tmpAssetBundleHashes);
// Open the archives
archives = openArchives();
}
/**
* @return all asset bundles of this asset manager.
*/
public Map<File, AssetBundle> getAssetBundles() {
return assetBundles;
}
/**
* @return all asset bundle hashes.
*/
public Map<File, String> getAssetBundleHashes() {
return assetBundleHashes;
}
/**
* Loads the image.
*
* @param assetPath
* The asset path of the image.
* @param optimized
* If true the loaded image will be optimized.
* @return an asset containing the image.
* @throws Exception
* If an exception occurs.
*/
public Asset<BufferedImage> loadImage(String assetPath, boolean optimized)
throws Exception {
return loadAsset(assetPath, optimized ? OPTIMIZED_IMAGE_LOADER
: IMAGE_LOADER, false);
}
/**
* Loads a font.
*
* @param assetPath
* The asset path of the asset.
* @return an asset containing the font.
* @throws Exception
* If an exception occurs.
*/
public Asset<Font> loadFont(String assetPath) throws Exception {
return loadAsset(assetPath, FONT_LOADER, false);
}
/**
* Loads a byte array.
*
* @param assetPath
* The asset path of the asset.
* @param ignoreHeadless
* The ignore-headless flag.
* @return an asset containing the byte array.
* @throws Exception
* If an exception occurs.
*/
public Asset<byte[]> loadBytes(String assetPath, boolean ignoreHeadless)
throws Exception {
return loadAsset(assetPath, BYTE_ARRAY_LOADER, ignoreHeadless);
}
/**
* Loads an asset using the given asset loader.
*
* @param assetPath
* The asset path of the asset.
* @param assetLoader
* The asset loader of the asset.
* @param ignoreHeadless
* The ignore-headless flag.
* @return an asset.
* @throws Exception
* If an exception occurs.
*/
public <T> Asset<T> loadAsset(String assetPath, AssetLoader<T> assetLoader,
boolean ignoreHeadless) throws Exception {
return new Asset<T>(this, assetPath, assetLoader, ignoreHeadless);
}
/**
* Opens an input stream to an asset.
*
* @param assetPath
* The asset path.
* @return the opened input stream.
* @throws Exception
* If an exception occurs.
*/
public InputStream open(String assetPath) throws Exception {
for (ZipFile source : archives) {
ZipEntry entry = source.getEntry(assetPath);
if (entry != null) {
return source.getInputStream(entry);
}
}
throw new IOException("Zip path \"" + assetPath + "\" does not exist");
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((assetBundles == null) ? 0 : assetBundles.hashCode());
return result;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof AssetManager)) {
return false;
}
AssetManager other = (AssetManager) obj;
if (assetBundles == null) {
if (other.assetBundles != null) {
return false;
}
} else if (!assetBundles.equals(other.assetBundles)) {
return false;
}
return true;
}
}