Behold, Nenya, Ring of Water and repository for our media and animation related
goodies, both Java 2D and LWJGL/JME 3D. git-svn-id: svn+ssh://src.earth.threerings.net/nenya/trunk@1 ed5b42cb-e716-0410-a449-f6a68f950b19
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
//
|
||||
// $Id: BundleUtil.java 4191 2006-06-13 22:42:20Z ray $
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library 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
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.cast.bundle;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InvalidClassException;
|
||||
import java.io.ObjectInputStream;
|
||||
|
||||
import com.samskivert.io.StreamUtil;
|
||||
|
||||
import com.threerings.cast.Log;
|
||||
import com.threerings.resource.ResourceBundle;
|
||||
|
||||
/**
|
||||
* Utility functions related to creating and manipulating component
|
||||
* bundles.
|
||||
*/
|
||||
public class BundleUtil
|
||||
{
|
||||
/** The path in the metadata bundle to the serialized action table. */
|
||||
public static final String ACTIONS_PATH = "actions.dat";
|
||||
|
||||
/** The path in the metadata bundle to the serialized action tile sets
|
||||
* table. */
|
||||
public static final String ACTION_SETS_PATH = "action_sets.dat";
|
||||
|
||||
/** The path in the metadata bundle to the serialized component class
|
||||
* table. */
|
||||
public static final String CLASSES_PATH = "classes.dat";
|
||||
|
||||
/** The path in the component bundle to the serialized component id to
|
||||
* class/type mapping. */
|
||||
public static final String COMPONENTS_PATH = "components.dat";
|
||||
|
||||
/** The file extension of our action tile images. */
|
||||
public static final String IMAGE_EXTENSION = ".png";
|
||||
|
||||
/** The serialized tileset extension for our action tilesets. */
|
||||
public static final String TILESET_EXTENSION = ".dat";
|
||||
|
||||
/**
|
||||
* Attempts to load an object from the supplied resource bundle with
|
||||
* the specified path.
|
||||
*
|
||||
* @param wipeBundleOnFailure if there is an error reading the object
|
||||
* from the bundle and this parameter is true, we will instruct the
|
||||
* bundle to delete its underlying jar file before propagating the
|
||||
* exception with the expectation that it will be redownloaded and
|
||||
* repaired the next time the application is run.
|
||||
*
|
||||
* @return the unserialized object in question.
|
||||
*
|
||||
* @exception IOException thrown if an I/O error occurs while reading
|
||||
* the object from the bundle.
|
||||
*/
|
||||
public static Object loadObject (ResourceBundle bundle, String path,
|
||||
boolean wipeBundleOnFailure)
|
||||
throws IOException, ClassNotFoundException
|
||||
{
|
||||
InputStream bin = null;
|
||||
try {
|
||||
bin = bundle.getResource(path);
|
||||
if (bin == null) {
|
||||
return null;
|
||||
}
|
||||
return new ObjectInputStream(bin).readObject();
|
||||
|
||||
} catch (InvalidClassException ice) {
|
||||
Log.warning("Aiya! Serialized object is hosed " +
|
||||
"[bundle=" + bundle.getSource().getPath() +
|
||||
", element=" + path +
|
||||
", error=" + ice.getMessage() + "].");
|
||||
return null;
|
||||
|
||||
} catch (IOException ioe) {
|
||||
Log.warning("Error reading resource from bundle " +
|
||||
"[bundle=" + bundle + ", path=" + path +
|
||||
", wiping?=" + wipeBundleOnFailure + "].");
|
||||
if (wipeBundleOnFailure) {
|
||||
StreamUtil.close(bin);
|
||||
bin = null;
|
||||
bundle.wipeBundle(false);
|
||||
}
|
||||
throw ioe;
|
||||
|
||||
} finally {
|
||||
StreamUtil.close(bin);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,528 @@
|
||||
//
|
||||
// $Id: BundledComponentRepository.java 4023 2006-04-14 21:52:06Z mdb $
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library 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
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.cast.bundle;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.Rectangle;
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
|
||||
import com.samskivert.util.HashIntMap;
|
||||
import com.samskivert.util.IntIntMap;
|
||||
import com.samskivert.util.Tuple;
|
||||
|
||||
import org.apache.commons.collections.iterators.FilterIterator;
|
||||
import org.apache.commons.collections.Predicate;
|
||||
|
||||
import com.threerings.resource.ResourceBundle;
|
||||
import com.threerings.resource.ResourceManager;
|
||||
|
||||
import com.threerings.media.image.Colorization;
|
||||
import com.threerings.media.image.FastImageIO;
|
||||
import com.threerings.media.image.ImageDataProvider;
|
||||
import com.threerings.media.image.ImageManager;
|
||||
|
||||
import com.threerings.media.tile.IMImageProvider;
|
||||
import com.threerings.media.tile.Tile;
|
||||
import com.threerings.media.tile.TileSet;
|
||||
import com.threerings.media.tile.TrimmedTile;
|
||||
|
||||
import com.threerings.util.DirectionCodes;
|
||||
|
||||
import com.threerings.cast.ActionFrames;
|
||||
import com.threerings.cast.ActionSequence;
|
||||
import com.threerings.cast.CharacterComponent;
|
||||
import com.threerings.cast.ComponentClass;
|
||||
import com.threerings.cast.ComponentRepository;
|
||||
import com.threerings.cast.FrameProvider;
|
||||
import com.threerings.cast.Log;
|
||||
import com.threerings.cast.NoSuchComponentException;
|
||||
import com.threerings.cast.StandardActions;
|
||||
import com.threerings.cast.TrimmedMultiFrameImage;
|
||||
|
||||
/**
|
||||
* A component repository implementation that obtains information from
|
||||
* resource bundles.
|
||||
*
|
||||
* @see ResourceManager
|
||||
*/
|
||||
public class BundledComponentRepository
|
||||
implements DirectionCodes, ComponentRepository
|
||||
{
|
||||
/**
|
||||
* Constructs a repository which will obtain its resource set from the
|
||||
* supplied resource manager.
|
||||
*
|
||||
* @param rmgr the resource manager from which to obtain our resource
|
||||
* set.
|
||||
* @param imgr the image manager that we'll use to decode and cache
|
||||
* images.
|
||||
* @param name the name of the resource set from which we will be
|
||||
* loading our component data.
|
||||
*
|
||||
* @exception IOException thrown if an I/O error occurs while reading
|
||||
* our metadata from the resource bundles.
|
||||
*/
|
||||
public BundledComponentRepository (
|
||||
ResourceManager rmgr, ImageManager imgr, String name)
|
||||
throws IOException
|
||||
{
|
||||
// keep this guy around
|
||||
_imgr = imgr;
|
||||
|
||||
// first we obtain the resource set from whence will come our
|
||||
// bundles
|
||||
ResourceBundle[] rbundles = rmgr.getResourceSet(name);
|
||||
|
||||
// look for our metadata info in each of the bundles
|
||||
try {
|
||||
int rcount = (rbundles == null) ? 0 : rbundles.length;
|
||||
for (int i = 0; i < rcount; i++) {
|
||||
if (_actions == null) {
|
||||
_actions = (HashMap)BundleUtil.loadObject(
|
||||
rbundles[i], BundleUtil.ACTIONS_PATH, true);
|
||||
}
|
||||
if (_actionSets == null) {
|
||||
_actionSets = (HashMap)BundleUtil.loadObject(
|
||||
rbundles[i], BundleUtil.ACTION_SETS_PATH, true);
|
||||
}
|
||||
if (_classes == null) {
|
||||
_classes = (HashMap)BundleUtil.loadObject(
|
||||
rbundles[i], BundleUtil.CLASSES_PATH, true);
|
||||
}
|
||||
}
|
||||
|
||||
// now go back and load up all of the component information
|
||||
for (int i = 0; i < rcount; i++) {
|
||||
HashIntMap comps = null;
|
||||
comps = (HashIntMap)BundleUtil.loadObject(
|
||||
rbundles[i], BundleUtil.COMPONENTS_PATH, true);
|
||||
if (comps == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// create a frame provider for this bundle
|
||||
FrameProvider fprov =
|
||||
new ResourceBundleProvider(_imgr, rbundles[i]);
|
||||
|
||||
// now create character component instances for each component
|
||||
// in the serialized table
|
||||
Iterator iter = comps.keySet().iterator();
|
||||
while (iter.hasNext()) {
|
||||
int componentId = ((Integer)iter.next()).intValue();
|
||||
Tuple info = (Tuple)comps.get(componentId);
|
||||
createComponent(componentId, (String)info.left,
|
||||
(String)info.right, fprov);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (ClassNotFoundException cnfe) {
|
||||
throw (IOException) new IOException(
|
||||
"Internal error unserializing metadata").initCause(cnfe);
|
||||
}
|
||||
|
||||
// if we failed to load our classes or actions, create empty
|
||||
// hashtables so that we can safely enumerate our emptiness
|
||||
if (_actions == null) {
|
||||
_actions = new HashMap();
|
||||
}
|
||||
if (_classes == null) {
|
||||
_classes = new HashMap();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the bundled component repository to wipe any bundles
|
||||
* that report certain kinds of failure. In the event that an unpacked
|
||||
* bundle becomes corrupt, this is useful in that it will force the
|
||||
* bundle to be unpacked on the next application invocation,
|
||||
* potentially remedying the problem of a corrupt unpacking.
|
||||
*/
|
||||
public void setWipeOnFailure (boolean wipeOnFailure)
|
||||
{
|
||||
_wipeOnFailure = true;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public CharacterComponent getComponent (int componentId)
|
||||
throws NoSuchComponentException
|
||||
{
|
||||
CharacterComponent component = (CharacterComponent)
|
||||
_components.get(componentId);
|
||||
if (component == null) {
|
||||
throw new NoSuchComponentException(componentId);
|
||||
}
|
||||
return component;
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public CharacterComponent getComponent (String className, String compName)
|
||||
throws NoSuchComponentException
|
||||
{
|
||||
// look up the list for that class
|
||||
ArrayList comps = (ArrayList)_classComps.get(className);
|
||||
if (comps != null) {
|
||||
// scan the list for the named component
|
||||
int ccount = comps.size();
|
||||
for (int i = 0; i < ccount; i++) {
|
||||
CharacterComponent comp = (CharacterComponent)comps.get(i);
|
||||
if (comp.name.equals(compName)) {
|
||||
return comp;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new NoSuchComponentException(className, compName);
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public ComponentClass getComponentClass (String className)
|
||||
{
|
||||
return (ComponentClass)_classes.get(className);
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public Iterator enumerateComponentClasses ()
|
||||
{
|
||||
return _classes.values().iterator();
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public Iterator enumerateActionSequences ()
|
||||
{
|
||||
return _actions.values().iterator();
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public Iterator enumerateComponentIds (final ComponentClass compClass)
|
||||
{
|
||||
Predicate classP = new Predicate() {
|
||||
public boolean evaluate (Object input) {
|
||||
CharacterComponent comp = (CharacterComponent)
|
||||
_components.get(input);
|
||||
return comp.componentClass.equals(compClass);
|
||||
}
|
||||
};
|
||||
return new FilterIterator(_components.keySet().iterator(), classP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a component and inserts it into the component table.
|
||||
*/
|
||||
protected void createComponent (
|
||||
int componentId, String cclass, String cname, FrameProvider fprov)
|
||||
{
|
||||
// look up the component class information
|
||||
ComponentClass clazz = (ComponentClass)_classes.get(cclass);
|
||||
if (clazz == null) {
|
||||
Log.warning("Non-existent component class " +
|
||||
"[class=" + cclass + ", name=" + cname +
|
||||
", id=" + componentId + "].");
|
||||
return;
|
||||
}
|
||||
|
||||
// create the component
|
||||
CharacterComponent component = new CharacterComponent(
|
||||
componentId, cname, clazz, fprov);
|
||||
|
||||
// stick it into the appropriate tables
|
||||
_components.put(componentId, component);
|
||||
|
||||
// we have a hash of lists for mapping components by class/name
|
||||
ArrayList comps = (ArrayList)_classComps.get(cclass);
|
||||
if (comps == null) {
|
||||
comps = new ArrayList();
|
||||
_classComps.put(cclass, comps);
|
||||
}
|
||||
if (!comps.contains(component)) {
|
||||
comps.add(component);
|
||||
} else {
|
||||
Log.info("Requested to register the same component twice? " +
|
||||
"[component=" + component + "].");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Instances of these provide images to our component action tilesets
|
||||
* and frames to our components.
|
||||
*/
|
||||
protected class ResourceBundleProvider extends IMImageProvider
|
||||
implements ImageDataProvider, FrameProvider
|
||||
{
|
||||
/**
|
||||
* Constructs an instance that will obtain image data from the
|
||||
* specified resource bundle.
|
||||
*/
|
||||
public ResourceBundleProvider (ImageManager imgr, ResourceBundle bundle)
|
||||
{
|
||||
super(imgr, (String)null);
|
||||
_dprov = this;
|
||||
_bundle = bundle;
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public String getIdent ()
|
||||
{
|
||||
return "bcr:" + _bundle.getSource();
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public BufferedImage loadImage (String path) throws IOException
|
||||
{
|
||||
return FastImageIO.read(_bundle.getResourceFile(path));
|
||||
}
|
||||
|
||||
// documentation inherited
|
||||
public ActionFrames getFrames (
|
||||
CharacterComponent component, String action, String type)
|
||||
{
|
||||
// obtain the action sequence definition for this action
|
||||
ActionSequence actseq = (ActionSequence)_actions.get(action);
|
||||
if (actseq == null) {
|
||||
Log.warning("Missing action sequence definition " +
|
||||
"[action=" + action +
|
||||
", component=" + component + "].");
|
||||
return null;
|
||||
}
|
||||
|
||||
// determine our image path name
|
||||
String imgpath = action, dimgpath = ActionSequence.DEFAULT_SEQUENCE;
|
||||
if (type != null) {
|
||||
imgpath += "_" + type;
|
||||
dimgpath += "_" + type;
|
||||
}
|
||||
|
||||
String root = component.componentClass.name + "/" +
|
||||
component.name + "/";
|
||||
String cpath = root + imgpath + BundleUtil.TILESET_EXTENSION;
|
||||
String dpath = root + dimgpath + BundleUtil.TILESET_EXTENSION;
|
||||
|
||||
// look to see if this tileset is already cached (as the
|
||||
// custom action or the default action)
|
||||
TileSet aset = (TileSet)_setcache.get(cpath);
|
||||
if (aset == null) {
|
||||
aset = (TileSet)_setcache.get(dpath);
|
||||
if (aset != null) {
|
||||
// save ourselves a lookup next time
|
||||
_setcache.put(cpath, aset);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// then try loading up a tileset customized for this action
|
||||
if (aset == null) {
|
||||
aset = (TileSet)BundleUtil.loadObject(
|
||||
_bundle, cpath, false);
|
||||
}
|
||||
|
||||
// if that failed, try loading the default tileset
|
||||
if (aset == null) {
|
||||
aset = (TileSet)BundleUtil.loadObject(
|
||||
_bundle, dpath, false);
|
||||
_setcache.put(dpath, aset);
|
||||
}
|
||||
|
||||
// if that failed too, we're hosed
|
||||
if (aset == null) {
|
||||
// if this is a shadow image, no need to freak out as they
|
||||
// are optional
|
||||
if (!StandardActions.SHADOW_TYPE.equals(type)) {
|
||||
Log.warning("Unable to locate tileset for action '" +
|
||||
imgpath + "' " + component + ".");
|
||||
if (_wipeOnFailure) {
|
||||
_bundle.wipeBundle(false);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
aset.setImageProvider(this);
|
||||
_setcache.put(cpath, aset);
|
||||
return new TileSetFrameImage(aset, actseq);
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.warning("Error loading tileset for action '" + imgpath +
|
||||
"' " + component + ".");
|
||||
Log.logStackTrace(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** The resource bundle from which we obtain image data. */
|
||||
protected ResourceBundle _bundle;
|
||||
|
||||
/** Cache of tilesets loaded from our bundle. */
|
||||
protected HashMap _setcache = new HashMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to provide multiframe images using data obtained from a
|
||||
* tileset.
|
||||
*/
|
||||
protected static class TileSetFrameImage implements ActionFrames
|
||||
{
|
||||
/**
|
||||
* Constructs a tileset frame image with the specified tileset and
|
||||
* for the specified orientation.
|
||||
*/
|
||||
public TileSetFrameImage (TileSet set, ActionSequence actseq)
|
||||
{
|
||||
_set = set;
|
||||
_actseq = actseq;
|
||||
|
||||
// compute these now to avoid pointless recomputation later
|
||||
_ocount = actseq.orients.length;
|
||||
_fcount = set.getTileCount() / _ocount;
|
||||
|
||||
// create our mapping from orientation to animation sequence
|
||||
// index
|
||||
for (int ii = 0; ii < _ocount; ii++) {
|
||||
_orients.put(actseq.orients[ii], ii);
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public int getOrientationCount ()
|
||||
{
|
||||
return _ocount;
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public TrimmedMultiFrameImage getFrames (final int orient)
|
||||
{
|
||||
return new TrimmedMultiFrameImage() {
|
||||
// documentation inherited
|
||||
public int getFrameCount ()
|
||||
{
|
||||
return _fcount;
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public int getWidth (int index)
|
||||
{
|
||||
Tile tile = getTile(orient, index);
|
||||
return (tile == null) ? 0 : tile.getWidth();
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public int getHeight (int index)
|
||||
{
|
||||
Tile tile = getTile(orient, index);
|
||||
return (tile == null) ? 0 : tile.getHeight();
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void paintFrame (Graphics2D g, int index, int x, int y)
|
||||
{
|
||||
Tile tile = getTile(orient, index);
|
||||
if (tile != null) {
|
||||
tile.paint(g, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public boolean hitTest (int index, int x, int y)
|
||||
{
|
||||
Tile tile = getTile(orient, index);
|
||||
return (tile != null) ? tile.hitTest(x, y) : false;
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public void getTrimmedBounds (int index, Rectangle bounds)
|
||||
{
|
||||
Tile tile = getTile(orient, index);
|
||||
if (tile instanceof TrimmedTile) {
|
||||
((TrimmedTile)tile).getTrimmedBounds(bounds);
|
||||
} else {
|
||||
bounds.setBounds(
|
||||
0, 0, tile.getWidth(), tile.getHeight());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public int getXOrigin (int orient, int index)
|
||||
{
|
||||
return _actseq.origin.x;
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public int getYOrigin (int orient, int index)
|
||||
{
|
||||
return _actseq.origin.y;
|
||||
}
|
||||
|
||||
// documentation inherited from interface
|
||||
public ActionFrames cloneColorized (Colorization[] zations)
|
||||
{
|
||||
return new TileSetFrameImage(_set.clone(zations), _actseq);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the requested tile.
|
||||
*/
|
||||
protected Tile getTile (int orient, int index)
|
||||
{
|
||||
int tileIndex = _orients.get(orient) * _fcount + index;
|
||||
return _set.getTile(tileIndex);
|
||||
}
|
||||
|
||||
/** The tileset from which we obtain our frame images. */
|
||||
protected TileSet _set;
|
||||
|
||||
/** The action sequence for which we're providing frame images. */
|
||||
protected ActionSequence _actseq;
|
||||
|
||||
/** Frame and orientation counts. */
|
||||
protected int _fcount, _ocount;
|
||||
|
||||
/** A mapping from orientation code to animation sequence
|
||||
* index. */
|
||||
protected IntIntMap _orients = new IntIntMap();
|
||||
}
|
||||
|
||||
/** We use the image manager to decode and cache images. */
|
||||
protected ImageManager _imgr;
|
||||
|
||||
/** A table of action sequences. */
|
||||
protected HashMap _actions;
|
||||
|
||||
/** A table of action sequence tilesets. */
|
||||
protected HashMap _actionSets;
|
||||
|
||||
/** A table of component classes. */
|
||||
protected HashMap _classes;
|
||||
|
||||
/** A table of component lists indexed on classname. */
|
||||
protected HashMap _classComps = new HashMap();
|
||||
|
||||
/** The component table. */
|
||||
protected HashIntMap _components = new HashIntMap();
|
||||
|
||||
/** Whether or not we wipe our bundles on any failure. */
|
||||
protected boolean _wipeOnFailure;
|
||||
}
|
||||
@@ -0,0 +1,624 @@
|
||||
//
|
||||
// $Id: ComponentBundlerTask.java 4145 2006-05-24 01:24:24Z ray $
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library 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
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.cast.bundle.tools;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectOutputStream;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.zip.Deflater;
|
||||
|
||||
import org.apache.commons.digester.Digester;
|
||||
|
||||
import com.samskivert.io.PersistenceException;
|
||||
import com.samskivert.util.ComparableArrayList;
|
||||
import com.samskivert.util.FileUtil;
|
||||
import com.samskivert.util.HashIntMap;
|
||||
import com.samskivert.util.Tuple;
|
||||
|
||||
import org.apache.tools.ant.BuildException;
|
||||
import org.apache.tools.ant.DirectoryScanner;
|
||||
import org.apache.tools.ant.Task;
|
||||
import org.apache.tools.ant.types.FileSet;
|
||||
|
||||
import com.threerings.media.tile.ImageProvider;
|
||||
import com.threerings.media.tile.SimpleCachingImageProvider;
|
||||
import com.threerings.media.tile.TileSet;
|
||||
import com.threerings.media.tile.TrimmedTileSet;
|
||||
import com.threerings.media.tile.tools.xml.SwissArmyTileSetRuleSet;
|
||||
|
||||
import com.threerings.cast.ComponentIDBroker;
|
||||
import com.threerings.cast.StandardActions;
|
||||
import com.threerings.cast.bundle.BundleUtil;
|
||||
import com.threerings.cast.tools.xml.ActionRuleSet;
|
||||
|
||||
/**
|
||||
* Ant task for creating component bundles. This task must be configured
|
||||
* with a number of parameters:
|
||||
*
|
||||
* <pre>
|
||||
* target=[path to bundle file, which will be created]
|
||||
* mapfile=[path to the component map file which maintains a mapping from
|
||||
* component id to component class/name, it will be created the
|
||||
* first time and updated as new components are mapped]
|
||||
* </pre>
|
||||
*
|
||||
* It should also contain one or more nested <fileset> elements that
|
||||
* enumerate the action tileset images that should be included in the
|
||||
* component bundle.
|
||||
*/
|
||||
public class ComponentBundlerTask extends Task
|
||||
{
|
||||
/**
|
||||
* Sets the path to the bundle file that we'll be creating.
|
||||
*/
|
||||
public void setTarget (File target)
|
||||
{
|
||||
_target = target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the path to the component map file that we'll use to obtain
|
||||
* component ids for the bundled components.
|
||||
*/
|
||||
public void setMapfile (File mapfile)
|
||||
{
|
||||
_mapfile = mapfile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the path to the action tilesets definition file.
|
||||
*/
|
||||
public void setActiondef (File actiondef)
|
||||
{
|
||||
_actionDef = actiondef;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the root path which will be stripped from the image paths
|
||||
* prior to parsing them to obtain the component class, type and
|
||||
* action names.
|
||||
*/
|
||||
public void setRoot (File root)
|
||||
{
|
||||
_root = root.getPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a nested <fileset> element.
|
||||
*/
|
||||
public void addFileset (FileSet set)
|
||||
{
|
||||
_filesets.add(set);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the actual work of the task.
|
||||
*/
|
||||
public void execute () throws BuildException
|
||||
{
|
||||
// make sure everything was set up properly
|
||||
ensureSet(_target, "Must specify the path to the target bundle " +
|
||||
"file via the 'target' attribute.");
|
||||
ensureSet(_mapfile, "Must specify the path to the component map " +
|
||||
"file via the 'mapfile' attribute.");
|
||||
ensureSet(_actionDef, "Must specify the action sequence " +
|
||||
"definitions via the 'actiondef' attribute.");
|
||||
|
||||
// parse in the action tilesets
|
||||
HashMap actsets = parseActionTileSets();
|
||||
|
||||
// load up our component ID broker
|
||||
ComponentIDBroker broker = loadBroker(_mapfile);
|
||||
|
||||
// check to see if any of the source files are newer than the
|
||||
// target file
|
||||
ArrayList sources = (ArrayList)_filesets.clone();
|
||||
sources.add(_mapfile);
|
||||
sources.add(_actionDef);
|
||||
if (!outOfDate(sources, _target)) {
|
||||
System.out.println(_target.getPath() + " is up to date.");
|
||||
return;
|
||||
}
|
||||
|
||||
// create an image provider for loading our component images
|
||||
ImageProvider improv = new SimpleCachingImageProvider() {
|
||||
protected BufferedImage loadImage (String path)
|
||||
throws IOException {
|
||||
return ImageIO.read(new File(path));
|
||||
}
|
||||
};
|
||||
|
||||
System.out.println("Generating " + _target.getPath() + "...");
|
||||
|
||||
try {
|
||||
// make sure we can create our bundle file
|
||||
FileOutputStream fout = new FileOutputStream(_target);
|
||||
JarOutputStream jout = new JarOutputStream(fout);
|
||||
jout.setLevel(Deflater.BEST_COMPRESSION);
|
||||
|
||||
// we'll fill this with component id to tuple mappings
|
||||
HashIntMap mapping = new HashIntMap();
|
||||
|
||||
// herein we'll insert trimmed tileset objects that go along
|
||||
// with each of the trimmed action images
|
||||
HashIntMap actionSets = new HashIntMap();
|
||||
|
||||
// deal with the filesets
|
||||
for (int i = 0; i < _filesets.size(); i++) {
|
||||
FileSet fs = (FileSet)_filesets.get(i);
|
||||
DirectoryScanner ds = fs.getDirectoryScanner(getProject());
|
||||
File fromDir = fs.getDir(getProject());
|
||||
String[] srcFiles = ds.getIncludedFiles();
|
||||
|
||||
for (int f = 0; f < srcFiles.length; f++) {
|
||||
File cfile = new File(fromDir, srcFiles[f]);
|
||||
// determine the [class, name, action] triplet
|
||||
String[] info = decomposePath(cfile.getPath());
|
||||
|
||||
// make sure we have an action tileset definition
|
||||
TileSet aset = (TileSet)actsets.get(info[2]);
|
||||
if (aset == null) {
|
||||
System.err.println(
|
||||
"No tileset definition for component action " +
|
||||
"[class=" + info[0] + ", name=" + info[1] +
|
||||
", action=" + info[2] + "].");
|
||||
continue;
|
||||
}
|
||||
aset.setImageProvider(improv);
|
||||
|
||||
// obtain the component id from our id broker
|
||||
int cid = broker.getComponentID(info[0], info[1]);
|
||||
// add a mapping for this component
|
||||
mapping.put(cid, new Tuple(info[0], info[1]));
|
||||
|
||||
// process and store the main component image
|
||||
processComponent(info, aset, cfile, jout);
|
||||
|
||||
// pick up any auxiliary images as well like the shadow or
|
||||
// crop files
|
||||
String action = info[2];
|
||||
String ext = BundleUtil.IMAGE_EXTENSION;
|
||||
for (int aa = 0; aa < AUX_EXTS.length; aa++) {
|
||||
File afile = new File(
|
||||
FileUtil.resuffix(cfile, ext, AUX_EXTS[aa] + ext));
|
||||
if (afile.exists()) {
|
||||
info[2] = action + AUX_EXTS[aa];
|
||||
processComponent(info, aset, afile, jout);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// write our mapping table to the jar file as well
|
||||
jout.putNextEntry(new JarEntry(BundleUtil.COMPONENTS_PATH));
|
||||
ObjectOutputStream oout = new ObjectOutputStream(jout);
|
||||
oout.writeObject(mapping);
|
||||
oout.flush();
|
||||
|
||||
// seal up our jar file
|
||||
jout.close();
|
||||
|
||||
} catch (IOException ioe) {
|
||||
String errmsg = "Unable to create component bundle.";
|
||||
throw new BuildException(errmsg, ioe);
|
||||
|
||||
} catch (PersistenceException pe) {
|
||||
String errmsg = "Unable to obtain component ID mapping.";
|
||||
throw new BuildException(errmsg, pe);
|
||||
}
|
||||
|
||||
// save our updated component ID broker
|
||||
saveBroker(_mapfile, broker);
|
||||
}
|
||||
|
||||
protected void processComponent (
|
||||
String[] info, TileSet aset, File cfile, JarOutputStream jout)
|
||||
throws IOException
|
||||
{
|
||||
// construct the path that'll go in the jar file
|
||||
String ipath = composePath(
|
||||
info, BundleUtil.IMAGE_EXTENSION);
|
||||
jout.putNextEntry(new JarEntry(ipath));
|
||||
|
||||
// create a trimmed tileset based on the source action tileset and
|
||||
// stuff the new trimmed image into the jar file at the same time
|
||||
aset.setImagePath(cfile.getPath());
|
||||
|
||||
TrimmedTileSet tset = null;
|
||||
try {
|
||||
tset = TrimmedTileSet.trimTileSet(aset, jout);
|
||||
tset.setImagePath(ipath);
|
||||
} catch (Throwable t) {
|
||||
System.err.println(
|
||||
"Failure trimming tileset " +
|
||||
"[class=" + info[0] + ", name=" + info[1] +
|
||||
", action=" + info[2] +
|
||||
", srcimg=" + aset.getImagePath() + "].");
|
||||
t.printStackTrace(System.err);
|
||||
}
|
||||
|
||||
// then write our trimmed tileset to the jar file
|
||||
if (tset != null) {
|
||||
String tpath = composePath(
|
||||
info, BundleUtil.TILESET_EXTENSION);
|
||||
jout.putNextEntry(new JarEntry(tpath));
|
||||
ObjectOutputStream oout = new ObjectOutputStream(jout);
|
||||
oout.writeObject(tset);
|
||||
oout.flush();
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean outOfDate (ArrayList sources, File target)
|
||||
{
|
||||
for (int ii = 0; ii < sources.size(); ii++) {
|
||||
if (outOfDate(sources.get(ii), target)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected boolean outOfDate (Object source, File target)
|
||||
{
|
||||
if (source instanceof FileSet) {
|
||||
FileSet fs = (FileSet)source;
|
||||
DirectoryScanner ds = fs.getDirectoryScanner(getProject());
|
||||
File fromDir = fs.getDir(getProject());
|
||||
String[] srcFiles = ds.getIncludedFiles();
|
||||
for (int f = 0; f < srcFiles.length; f++) {
|
||||
File cfile = new File(fromDir, srcFiles[f]);
|
||||
if (newer(cfile, target)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
} else if (source instanceof File) {
|
||||
return newer((File)source, target);
|
||||
|
||||
} else {
|
||||
System.err.println("Can't compare " + source +
|
||||
" to " + target + ".");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if <code>source</code> is newer than
|
||||
* <code>target</code>.
|
||||
*/
|
||||
protected boolean newer (File source, File target)
|
||||
{
|
||||
return source.lastModified() > target.lastModified();
|
||||
}
|
||||
|
||||
/**
|
||||
* Decomposes the full path to a component image into a [class, name,
|
||||
* action] triplet.
|
||||
*/
|
||||
protected String[] decomposePath (String path)
|
||||
throws BuildException
|
||||
{
|
||||
// first strip off the root
|
||||
if (!path.startsWith(_root)) {
|
||||
throw new BuildException("Can't bundle images outside the " +
|
||||
"root directory [root=" + _root +
|
||||
", path=" + path + "].");
|
||||
}
|
||||
path = path.substring(_root.length());
|
||||
|
||||
// strip off any preceding slash
|
||||
if (path.startsWith("/")) {
|
||||
path = path.substring(1);
|
||||
}
|
||||
|
||||
// now strip off the file extension
|
||||
if (!path.endsWith(BundleUtil.IMAGE_EXTENSION)) {
|
||||
throw new BuildException("Can't bundle malformed image file " +
|
||||
"[path=" + path + "].");
|
||||
}
|
||||
path = path.substring(0, path.length() -
|
||||
BundleUtil.IMAGE_EXTENSION.length());
|
||||
|
||||
// now decompose the path; the component type and action must
|
||||
// always be a single string but the class can span multiple
|
||||
// directories for easier component organization; thus
|
||||
// "male/head/goatee/standing" will be parsed as [class=male/head,
|
||||
// type=goatee, action=standing]
|
||||
String malmsg = "Can't decode malformed image path: '" + path + "'";
|
||||
String[] info = new String[3];
|
||||
int lsidx = path.lastIndexOf(File.separator);
|
||||
if (lsidx == -1) {
|
||||
throw new BuildException(malmsg);
|
||||
}
|
||||
info[2] = path.substring(lsidx+1);
|
||||
int slsidx = path.lastIndexOf(File.separator, lsidx-1);
|
||||
if (slsidx == -1) {
|
||||
throw new BuildException(malmsg);
|
||||
}
|
||||
info[1] = path.substring(slsidx+1, lsidx);
|
||||
info[0] = path.substring(0, slsidx);
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Composes a triplet of [class, name, action] into the path that
|
||||
* should be supplied to the JarEntry that contains the associated
|
||||
* image data.
|
||||
*/
|
||||
protected String composePath (String[] info, String extension)
|
||||
{
|
||||
return (info[0] + File.separator + info[1] +
|
||||
File.separator + info[2] + extension);
|
||||
}
|
||||
|
||||
protected void ensureSet (Object value, String errmsg)
|
||||
throws BuildException
|
||||
{
|
||||
if (value == null) {
|
||||
throw new BuildException(errmsg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the action tileset definitions and puts them into a hash
|
||||
* map, keyed on action name.
|
||||
*/
|
||||
protected HashMap parseActionTileSets ()
|
||||
{
|
||||
Digester digester = new Digester();
|
||||
SwissArmyTileSetRuleSet srules = new SwissArmyTileSetRuleSet();
|
||||
String aprefix = "actions" + ActionRuleSet.ACTION_PATH;
|
||||
srules.setPrefix(aprefix);
|
||||
|
||||
digester.addRuleSet(srules);
|
||||
digester.addSetProperties(aprefix);
|
||||
digester.addSetNext(aprefix + SwissArmyTileSetRuleSet.TILESET_PATH,
|
||||
"addTileSet", TileSet.class.getName());
|
||||
|
||||
HashMap actsets = new ActionMap();
|
||||
digester.push(actsets);
|
||||
|
||||
try {
|
||||
FileInputStream fin = new FileInputStream(_actionDef);
|
||||
BufferedInputStream bin = new BufferedInputStream(fin);
|
||||
digester.parse(bin);
|
||||
|
||||
} catch (FileNotFoundException fnfe) {
|
||||
String errmsg = "Unable to load action definition file " +
|
||||
"[path=" + _actionDef.getPath() + "].";
|
||||
throw new BuildException(errmsg, fnfe);
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new BuildException("Parsing error.", e);
|
||||
}
|
||||
|
||||
return actsets;
|
||||
}
|
||||
|
||||
/** Used when parsing action tilesets. */
|
||||
public static class ActionMap extends HashMap
|
||||
{
|
||||
public void setName (String name) {
|
||||
_name = name;
|
||||
}
|
||||
public void addTileSet (TileSet set) {
|
||||
set.setName(_name);
|
||||
put(_name, set);
|
||||
}
|
||||
protected String _name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the hashmap ID broker from its persistent representation in
|
||||
* the specified file.
|
||||
*/
|
||||
protected HashMapIDBroker loadBroker (File mapfile)
|
||||
throws BuildException
|
||||
{
|
||||
HashMapIDBroker broker = new HashMapIDBroker();
|
||||
|
||||
try {
|
||||
BufferedReader bin = new BufferedReader(new FileReader(mapfile));
|
||||
broker.readFrom(bin);
|
||||
bin.close();
|
||||
|
||||
} catch (FileNotFoundException fnfe) {
|
||||
// if the file doesn't yet exist, start with a blank broker
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new BuildException("Error loading component ID map " +
|
||||
"[mapfile=" + mapfile + "]", e);
|
||||
}
|
||||
|
||||
return broker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a persistent representation of the supplied hashmap ID
|
||||
* broker in the specified file.
|
||||
*/
|
||||
protected void saveBroker (File mapfile, ComponentIDBroker broker)
|
||||
throws BuildException
|
||||
{
|
||||
HashMapIDBroker hbroker = (HashMapIDBroker)broker;
|
||||
|
||||
// bail if the broker wasn't modified
|
||||
if (!hbroker.isModified()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
BufferedWriter bout = new BufferedWriter(new FileWriter(mapfile));
|
||||
hbroker.writeTo(bout);
|
||||
bout.close();
|
||||
} catch (IOException ioe) {
|
||||
throw new BuildException("Unable to store component ID map " +
|
||||
"[mapfile=" + mapfile + "]", ioe);
|
||||
}
|
||||
}
|
||||
|
||||
protected static class HashMapIDBroker
|
||||
extends HashMap implements ComponentIDBroker
|
||||
{
|
||||
public int getComponentID (String cclass, String cname)
|
||||
throws PersistenceException
|
||||
{
|
||||
Tuple key = new Tuple(cclass, cname);
|
||||
Integer cid = (Integer)get(key);
|
||||
if (cid == null) {
|
||||
cid = Integer.valueOf(++_nextCID);
|
||||
put(key, cid);
|
||||
}
|
||||
return cid.intValue();
|
||||
}
|
||||
|
||||
public void commit ()
|
||||
throws PersistenceException
|
||||
{
|
||||
// nothing doing
|
||||
}
|
||||
|
||||
public boolean isModified ()
|
||||
{
|
||||
return _nextCID != _startCID;
|
||||
}
|
||||
|
||||
public void writeTo (BufferedWriter bout)
|
||||
throws IOException
|
||||
{
|
||||
// write out our most recently assigned component id
|
||||
String cidline = "" + _nextCID;
|
||||
bout.write(cidline, 0, cidline.length());
|
||||
bout.newLine();
|
||||
|
||||
// write out the keys and values
|
||||
ComparableArrayList lines = new ComparableArrayList();
|
||||
Iterator keys = keySet().iterator();
|
||||
while (keys.hasNext()) {
|
||||
Tuple key = (Tuple)keys.next();
|
||||
Integer value = (Integer)get(key);
|
||||
String line = key.left + SEP_STR + key.right + SEP_STR + value;
|
||||
lines.add(line);
|
||||
}
|
||||
|
||||
// sort the output
|
||||
lines.sort();
|
||||
|
||||
// now write it to the file
|
||||
int lcount = lines.size();
|
||||
for (int ii = 0; ii < lcount; ii++) {
|
||||
String line = (String)lines.get(ii);
|
||||
bout.write(line, 0, line.length());
|
||||
bout.newLine();
|
||||
}
|
||||
}
|
||||
|
||||
public void readFrom (BufferedReader bin)
|
||||
throws IOException
|
||||
{
|
||||
// read in our most recently assigned component id
|
||||
_nextCID = readInt(bin);
|
||||
// keep track of this so that we can tell if we were modified
|
||||
_startCID = _nextCID;
|
||||
|
||||
// now read in our keys and values
|
||||
String line;
|
||||
while ((line = bin.readLine()) != null) {
|
||||
String orig = line;
|
||||
int sidx = line.indexOf(SEP_STR);
|
||||
if (sidx == -1) {
|
||||
throw new IOException("Malformed line '" + orig + "'");
|
||||
}
|
||||
String cclass = line.substring(0, sidx);
|
||||
line = line.substring(sidx + SEP_STR.length());
|
||||
sidx = line.indexOf(SEP_STR);
|
||||
if (sidx == -1) {
|
||||
throw new IOException("Malformed line '" + orig + "'");
|
||||
}
|
||||
String cname = line.substring(0, sidx);
|
||||
line = line.substring(sidx + SEP_STR.length());
|
||||
try {
|
||||
put(new Tuple(cclass, cname), Integer.valueOf(line));
|
||||
} catch (NumberFormatException nfe) {
|
||||
String err = "Malformed line, invalid code '" + orig + "'";
|
||||
throw new IOException(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected int readInt (BufferedReader bin)
|
||||
throws IOException
|
||||
{
|
||||
String line = bin.readLine();
|
||||
try {
|
||||
return Integer.parseInt(line);
|
||||
} catch (NumberFormatException nfe) {
|
||||
throw new IOException("Expected number, got '" + line + "'");
|
||||
}
|
||||
}
|
||||
|
||||
protected int _nextCID = 0;
|
||||
protected int _startCID = 0;
|
||||
}
|
||||
|
||||
/** The path to our component bundle file. */
|
||||
protected File _target;
|
||||
|
||||
/** The path to our component map file. */
|
||||
protected File _mapfile;
|
||||
|
||||
/** The path to our action tilesets definition file. */
|
||||
protected File _actionDef;
|
||||
|
||||
/** The component directory root. */
|
||||
protected String _root;
|
||||
|
||||
/** A list of filesets that contain tile images. */
|
||||
protected ArrayList _filesets = new ArrayList();
|
||||
|
||||
/** Used to separate keys and values in the map file. */
|
||||
protected static final String SEP_STR = " := ";
|
||||
|
||||
/** Used to process auxilliary tilesets. */
|
||||
protected static final String[] AUX_EXTS = {
|
||||
"_" + StandardActions.SHADOW_TYPE,
|
||||
"_" + StandardActions.CROP_TYPE };
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
//
|
||||
// $Id: DumpBundle.java 4191 2006-06-13 22:42:20Z ray $
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library 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
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.cast.bundle.tools;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
import com.samskivert.util.HashIntMap;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import com.threerings.resource.ResourceBundle;
|
||||
import com.threerings.cast.bundle.BundleUtil;
|
||||
|
||||
/**
|
||||
* Dumps the contents of a component bundle to stdout.
|
||||
*/
|
||||
public class DumpBundle
|
||||
{
|
||||
public static void main (String[] args)
|
||||
{
|
||||
if (args.length < 1) {
|
||||
String usage = "Usage: DumpBundle bundle.jar [bundle.jar ...]";
|
||||
System.err.println(usage);
|
||||
System.exit(-1);
|
||||
}
|
||||
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
File file = new File(args[i]);
|
||||
try {
|
||||
ResourceBundle bundle = new ResourceBundle(file);
|
||||
|
||||
HashMap actions = (HashMap)BundleUtil.loadObject(
|
||||
bundle, BundleUtil.ACTIONS_PATH, false);
|
||||
dumpTable("actions: ", actions);
|
||||
|
||||
HashMap actionSets = (HashMap)BundleUtil.loadObject(
|
||||
bundle, BundleUtil.ACTION_SETS_PATH, false);
|
||||
dumpTable("actionSets: ", actionSets);
|
||||
|
||||
HashMap classes = (HashMap)BundleUtil.loadObject(
|
||||
bundle, BundleUtil.CLASSES_PATH, false);
|
||||
dumpTable("classes: ", classes);
|
||||
|
||||
HashIntMap comps = (HashIntMap)BundleUtil.loadObject(
|
||||
bundle, BundleUtil.COMPONENTS_PATH, false);
|
||||
dumpTable("components: ", comps);
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("Error dumping bundle [path=" + args[i] +
|
||||
", error=" + e + "].");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static void dumpTable (String prefix, Map table)
|
||||
{
|
||||
if (table != null) {
|
||||
Iterator iter = table.entrySet().iterator();
|
||||
System.out.println(prefix + StringUtil.toString(iter));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
//
|
||||
// $Id: MetadataBundlerTask.java 4191 2006-06-13 22:42:20Z ray $
|
||||
//
|
||||
// Narya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://www.threerings.net/code/narya/
|
||||
//
|
||||
// This library is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License as published
|
||||
// by the Free Software Foundation; either version 2.1 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This library 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
|
||||
// Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public
|
||||
// License along with this library; if not, write to the Free Software
|
||||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
package com.threerings.cast.bundle.tools;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectOutputStream;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.zip.Deflater;
|
||||
|
||||
import org.apache.tools.ant.BuildException;
|
||||
import org.apache.tools.ant.Task;
|
||||
|
||||
import org.apache.commons.digester.Digester;
|
||||
|
||||
import com.samskivert.util.Tuple;
|
||||
|
||||
import com.threerings.media.tile.TileSet;
|
||||
import com.threerings.media.tile.tools.xml.SwissArmyTileSetRuleSet;
|
||||
|
||||
import com.threerings.cast.ActionSequence;
|
||||
import com.threerings.cast.ComponentClass;
|
||||
import com.threerings.cast.bundle.BundleUtil;
|
||||
|
||||
import com.threerings.cast.tools.xml.ActionRuleSet;
|
||||
import com.threerings.cast.tools.xml.ClassRuleSet;
|
||||
|
||||
/**
|
||||
* Ant task for creating metadata bundles, which contain action sequence
|
||||
* and component class definition information. This task must be
|
||||
* configured with a number of parameters:
|
||||
*
|
||||
* <pre>
|
||||
* actiondef=[path to actions.xml]
|
||||
* classdef=[path to classes.xml]
|
||||
* file=[path to metadata bundle, which will be created]
|
||||
* </pre>
|
||||
*/
|
||||
public class MetadataBundlerTask extends Task
|
||||
{
|
||||
public void setActiondef (String actiondef)
|
||||
{
|
||||
_actionDef = actiondef;
|
||||
}
|
||||
|
||||
public void setClassdef (String classdef)
|
||||
{
|
||||
_classDef = classdef;
|
||||
}
|
||||
|
||||
public void setTarget (File target)
|
||||
{
|
||||
_target = target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the actual work of the task.
|
||||
*/
|
||||
public void execute ()
|
||||
throws BuildException
|
||||
{
|
||||
// make sure everythign was set up properly
|
||||
ensureSet(_actionDef, "Must specify the action sequence " +
|
||||
"definitions via the 'actiondef' attribute.");
|
||||
ensureSet(_classDef, "Must specify the component class definitions " +
|
||||
"via the 'classdef' attribute.");
|
||||
ensureSet(_target, "Must specify the path to the target bundle " +
|
||||
"file via the 'target' attribute.");
|
||||
|
||||
// make sure we can write to the target bundle file
|
||||
FileOutputStream fout = null;
|
||||
try {
|
||||
fout = new FileOutputStream(_target);
|
||||
|
||||
// parse our metadata
|
||||
Tuple tuple = parseActions();
|
||||
HashMap actions = (HashMap)tuple.left;
|
||||
HashMap actionSets = (HashMap)tuple.right;
|
||||
HashMap classes = parseClasses();
|
||||
|
||||
// and create the bundle file
|
||||
JarOutputStream jout = new JarOutputStream(fout);
|
||||
jout.setLevel(Deflater.BEST_COMPRESSION);
|
||||
|
||||
// throw the serialized actions table in there
|
||||
JarEntry aentry = new JarEntry(BundleUtil.ACTIONS_PATH);
|
||||
jout.putNextEntry(aentry);
|
||||
ObjectOutputStream oout = new ObjectOutputStream(jout);
|
||||
oout.writeObject(actions);
|
||||
oout.flush();
|
||||
|
||||
// throw the serialized action tilesets table in there
|
||||
JarEntry sentry = new JarEntry(BundleUtil.ACTION_SETS_PATH);
|
||||
jout.putNextEntry(sentry);
|
||||
oout = new ObjectOutputStream(jout);
|
||||
oout.writeObject(actionSets);
|
||||
oout.flush();
|
||||
|
||||
// throw the serialized classes table in there
|
||||
JarEntry centry = new JarEntry(BundleUtil.CLASSES_PATH);
|
||||
jout.putNextEntry(centry);
|
||||
oout = new ObjectOutputStream(jout);
|
||||
oout.writeObject(classes);
|
||||
oout.flush();
|
||||
|
||||
// close it up and we're done
|
||||
jout.close();
|
||||
|
||||
} catch (IOException ioe) {
|
||||
String errmsg = "Unable to output to target bundle " +
|
||||
"[path=" + _target.getPath() + "].";
|
||||
throw new BuildException(errmsg, ioe);
|
||||
|
||||
} finally {
|
||||
if (fout != null) {
|
||||
try {
|
||||
fout.close();
|
||||
} catch (IOException ioe) {
|
||||
// nothing to complain about here
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected Tuple parseActions ()
|
||||
throws BuildException
|
||||
{
|
||||
// scan through the XML once to read the actions
|
||||
Digester digester = new Digester();
|
||||
ActionRuleSet arules = new ActionRuleSet();
|
||||
arules.setPrefix("actions");
|
||||
digester.addRuleSet(arules);
|
||||
digester.addSetNext("actions" + ActionRuleSet.ACTION_PATH,
|
||||
"add", Object.class.getName());
|
||||
ArrayList actlist = parseList(digester, _actionDef);
|
||||
|
||||
// now go through a second time reading the tileset info
|
||||
digester = new Digester();
|
||||
SwissArmyTileSetRuleSet srules = new SwissArmyTileSetRuleSet();
|
||||
srules.setPrefix("actions" + ActionRuleSet.ACTION_PATH);
|
||||
digester.addRuleSet(srules);
|
||||
digester.addSetNext("actions" + ActionRuleSet.ACTION_PATH +
|
||||
SwissArmyTileSetRuleSet.TILESET_PATH, "add",
|
||||
Object.class.getName());
|
||||
ArrayList setlist = parseList(digester, _actionDef);
|
||||
|
||||
// sanity check
|
||||
if (actlist.size() != setlist.size()) {
|
||||
String errmsg = "An action is missing its tileset " +
|
||||
"definition, or something even wackier is going on.";
|
||||
throw new BuildException(errmsg);
|
||||
}
|
||||
|
||||
// now create our mappings
|
||||
HashMap actmap = new HashMap();
|
||||
HashMap setmap = new HashMap();
|
||||
|
||||
// create the action map
|
||||
for (int i = 0; i < setlist.size(); i++) {
|
||||
TileSet set = (TileSet)setlist.get(i);
|
||||
ActionSequence act = (ActionSequence)actlist.get(i);
|
||||
// make sure nothing was missing in the action sequence
|
||||
// definition parsed from XML
|
||||
String errmsg = ActionRuleSet.validate(act);
|
||||
if (errmsg != null) {
|
||||
errmsg = "Action sequence invalid [seq=" + act +
|
||||
", error=" + errmsg + "].";
|
||||
throw new BuildException(errmsg);
|
||||
}
|
||||
actmap.put(act.name, act);
|
||||
setmap.put(act.name, set);
|
||||
}
|
||||
|
||||
return new Tuple(actmap, setmap);
|
||||
}
|
||||
|
||||
protected HashMap parseClasses ()
|
||||
throws BuildException
|
||||
{
|
||||
// load up our action and class info
|
||||
Digester digester = new Digester();
|
||||
|
||||
// add our action rule set and a a rule to grab parsed actions
|
||||
ClassRuleSet crules = new ClassRuleSet();
|
||||
crules.setPrefix("classes");
|
||||
digester.addRuleSet(crules);
|
||||
digester.addSetNext("classes" + ClassRuleSet.CLASS_PATH,
|
||||
"add", Object.class.getName());
|
||||
|
||||
ArrayList setlist = parseList(digester, _classDef);
|
||||
HashMap clmap = new HashMap();
|
||||
|
||||
// create the action map
|
||||
for (int i = 0; i < setlist.size(); i++) {
|
||||
ComponentClass cl = (ComponentClass)setlist.get(i);
|
||||
clmap.put(cl.name, cl);
|
||||
}
|
||||
|
||||
return clmap;
|
||||
}
|
||||
|
||||
protected ArrayList parseList (Digester digester, String path)
|
||||
throws BuildException
|
||||
{
|
||||
try {
|
||||
FileInputStream fin = new FileInputStream(path);
|
||||
BufferedInputStream bin = new BufferedInputStream(fin);
|
||||
|
||||
ArrayList setlist = new ArrayList();
|
||||
digester.push(setlist);
|
||||
|
||||
// now fire up the digester to parse the stream
|
||||
try {
|
||||
digester.parse(bin);
|
||||
} catch (Exception e) {
|
||||
throw new BuildException("Parsing error.", e);
|
||||
}
|
||||
|
||||
return setlist;
|
||||
|
||||
} catch (FileNotFoundException fnfe) {
|
||||
String errmsg = "Unable to load metadata definition file " +
|
||||
"[path=" + path + "].";
|
||||
throw new BuildException(errmsg, fnfe);
|
||||
}
|
||||
}
|
||||
|
||||
protected void ensureSet (Object value, String errmsg)
|
||||
throws BuildException
|
||||
{
|
||||
if (value == null) {
|
||||
throw new BuildException(errmsg);
|
||||
}
|
||||
}
|
||||
|
||||
protected String _actionDef;
|
||||
protected String _classDef;
|
||||
protected File _target;
|
||||
}
|
||||
Reference in New Issue
Block a user