Revamped the way TileSetBundler mogrifies and writes out bundles
Introduced a Writer class to hold all the options to the createBundle method and help clarify things. This also ended up being a convenient place to put the previous member variables since they affect the process in pretty much the same way. Got rid of DirectoryTileSetBundler. This had a mutated copy of a method from TileSetBundler, that appeared to be missing some bug fixes. Anyway, it shouldn't be needed anymore, callers can use a BundleWriter that is a directory. Added an option for outputting tileset bundle metadata as json. This includes some utility classes and the net.sf.json-lib dependency. This follows the same pattern as some other tools only jars so should end up being ignored by production applications.
This commit is contained in:
committed by
Michael Bayne
parent
426165b85e
commit
bce83d858e
@@ -35,10 +35,14 @@ import com.threerings.resource.ResourceBundle;
|
||||
*/
|
||||
public class BundleUtil
|
||||
{
|
||||
/** The path to the metadata resource that we will attempt to load
|
||||
* from our resource set. */
|
||||
/** The path to the binary metadata resource that jvm clients will attempt to load
|
||||
* from the bundle's resource set. */
|
||||
public static final String METADATA_PATH = "tsbundles.dat";
|
||||
|
||||
/** The path to the text metadata resource that playn clients will attempt to load
|
||||
* from the bundle's resource set. */
|
||||
public static final String METADATA_JSON_PATH = "tsbundles.json";
|
||||
|
||||
/**
|
||||
* Extracts, but does not initialize, a serialized tileset bundle
|
||||
* instance from the supplied resource bundle.
|
||||
|
||||
@@ -38,6 +38,13 @@
|
||||
<artifactId>playn-core</artifactId>
|
||||
<version>1.5.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.sf.json-lib</groupId>
|
||||
<artifactId>json-lib</artifactId>
|
||||
<version>2.4</version>
|
||||
<classifier>jdk15</classifier>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<!-- if you add a dependency here, be sure to hack it into build.xml, yay Ant! -->
|
||||
|
||||
<!-- test/build dependencies -->
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// 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.media.tile.bundle.tools;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.zip.Deflater;
|
||||
|
||||
/**
|
||||
* Writes files to disk either in a directory in a jar. The majority of the calling code doesn't
|
||||
* need to distinguish. Note that jars are not opened for appending, the class is intended to be
|
||||
* used in situations where the contents can be generated entirely.
|
||||
*/
|
||||
public class BundleWriter
|
||||
{
|
||||
/**
|
||||
* Creates a new bundle writer that will write to the given target jar file.
|
||||
* @param target file to open as a jar
|
||||
* @param uncompressed whether compression is off
|
||||
*/
|
||||
public BundleWriter (File target, final boolean uncompressed)
|
||||
throws IOException
|
||||
{
|
||||
_target = target;
|
||||
_dir = null;
|
||||
_jar = new JarImpl();
|
||||
_jar.uncompressed = uncompressed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new bundle writer that will write files into the given directory.
|
||||
* @param targetDir directory to write files to
|
||||
* @throws IOException if the directory cannot be created
|
||||
*/
|
||||
public BundleWriter (File targetDir)
|
||||
throws IOException
|
||||
{
|
||||
_target = targetDir;
|
||||
_jar = null;
|
||||
_dir = new DirImpl();
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a new file for writing in the jar or directory.
|
||||
*/
|
||||
public OutputStream startNewFile (String path)
|
||||
throws IOException
|
||||
{
|
||||
if (_jar != null) {
|
||||
_jar.get().putNextEntry(new JarEntry(path));
|
||||
return _jar.get();
|
||||
} else {
|
||||
_dir.closeFile();
|
||||
return _dir.newFile(path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if the target is newer than the given time. Note that if we are in directory-mode,
|
||||
* false is returned.
|
||||
*/
|
||||
public boolean isNewerThan (long newest)
|
||||
{
|
||||
if (_jar != null) {
|
||||
return _target.lastModified() > newest;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the writer.
|
||||
*/
|
||||
public void close ()
|
||||
throws IOException
|
||||
{
|
||||
if (_jar != null) {
|
||||
_jar.get().close();
|
||||
} else {
|
||||
_dir.closeFile();
|
||||
}
|
||||
}
|
||||
|
||||
public String toString ()
|
||||
{
|
||||
return _target.toString() + (_jar == null ? "/" : "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the target.
|
||||
*/
|
||||
public boolean delete ()
|
||||
{
|
||||
if (_jar != null) {
|
||||
return _target.delete();
|
||||
}
|
||||
// TODO: delete directory
|
||||
return false;
|
||||
}
|
||||
|
||||
protected class JarImpl
|
||||
{
|
||||
JarOutputStream get ()
|
||||
throws IOException
|
||||
{
|
||||
if (jar == null) {
|
||||
FileOutputStream fout = new FileOutputStream(_target);
|
||||
Manifest manifest = new Manifest();
|
||||
jar = new JarOutputStream(fout, manifest);
|
||||
jar.setLevel(uncompressed ? Deflater.NO_COMPRESSION : Deflater.BEST_COMPRESSION);
|
||||
}
|
||||
return jar;
|
||||
}
|
||||
|
||||
JarOutputStream jar;
|
||||
boolean uncompressed;
|
||||
}
|
||||
|
||||
protected class DirImpl
|
||||
{
|
||||
public DirImpl ()
|
||||
throws IOException
|
||||
{
|
||||
_target.mkdirs();
|
||||
if (!_target.isDirectory()) {
|
||||
throw new IOException("Not a directory: " + _target);
|
||||
}
|
||||
}
|
||||
|
||||
public void closeFile ()
|
||||
{
|
||||
if (current != null) {
|
||||
try {
|
||||
current.close();
|
||||
} catch (IOException ex) {
|
||||
System.err.println("Unable to close file: " + current);
|
||||
}
|
||||
current = null;
|
||||
}
|
||||
}
|
||||
|
||||
public OutputStream newFile (String path)
|
||||
throws IOException
|
||||
{
|
||||
return current = new FileOutputStream(new File(_target, path));
|
||||
}
|
||||
|
||||
OutputStream current;
|
||||
}
|
||||
|
||||
protected final File _target;
|
||||
protected final JarImpl _jar;
|
||||
protected final DirImpl _dir;
|
||||
}
|
||||
-171
@@ -1,171 +0,0 @@
|
||||
//
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// https://github.com/threerings/nenya
|
||||
//
|
||||
// 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.media.tile.bundle.tools;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectOutputStream;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import com.samskivert.io.StreamUtil;
|
||||
|
||||
import com.threerings.media.tile.ImageProvider;
|
||||
import com.threerings.media.tile.ObjectTileSet;
|
||||
import com.threerings.media.tile.TileSet;
|
||||
import com.threerings.media.tile.TrimmedObjectTileSet;
|
||||
import com.threerings.media.tile.bundle.BundleUtil;
|
||||
import com.threerings.media.tile.bundle.TileSetBundle;
|
||||
|
||||
import static com.threerings.media.Log.log;
|
||||
|
||||
public class DirectoryTileSetBundler extends TileSetBundler
|
||||
{
|
||||
public DirectoryTileSetBundler (File configFile)
|
||||
throws IOException
|
||||
{
|
||||
super(configFile);
|
||||
}
|
||||
|
||||
public DirectoryTileSetBundler (String configPath)
|
||||
throws IOException
|
||||
{
|
||||
super(configPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean createBundle (
|
||||
File target, TileSetBundle bundle, ImageProvider improv, String imageBase, long newestMod)
|
||||
throws IOException
|
||||
{
|
||||
try {
|
||||
// write all of the image files to the bundle's target path, converting the
|
||||
// tilesets to trimmed tilesets in the process
|
||||
Iterator<Integer> iditer = bundle.enumerateTileSetIds();
|
||||
while (iditer.hasNext()) {
|
||||
int tileSetId = iditer.next().intValue();
|
||||
TileSet set = bundle.getTileSet(tileSetId);
|
||||
String imagePath = set.getImagePath();
|
||||
|
||||
// sanity checks
|
||||
if (imagePath == null) {
|
||||
log.warning("Tileset contains no image path " +
|
||||
"[set=" + set + "]. It ain't gonna work.");
|
||||
continue;
|
||||
}
|
||||
|
||||
// if this is an object tileset, trim it
|
||||
if (set instanceof ObjectTileSet) {
|
||||
// set the tileset up with an image provider; we
|
||||
// need to do this so that we can trim it!
|
||||
set.setImageProvider(improv);
|
||||
|
||||
try {
|
||||
// create a trimmed object tileset, which will
|
||||
// write the trimmed tileset image to the destination output stream
|
||||
File outFile = new File(target, imagePath);
|
||||
FileOutputStream fout = null;
|
||||
|
||||
if (outFile.lastModified() > newestMod) {
|
||||
// Our file's newer than the newest bundle mod - up to date.
|
||||
// So don't actually do anything
|
||||
|
||||
// TODO: Ideally, we'd like to skip re-trimming altogether, since that's
|
||||
// expensive, but for the moment, we're at least doing half as much by
|
||||
// not writing out the trimmed image.
|
||||
|
||||
} else {
|
||||
// It's changed, so let's open the file & do all that jazz
|
||||
outFile.getParentFile().mkdirs();
|
||||
fout = new FileOutputStream(outFile);
|
||||
}
|
||||
|
||||
TrimmedObjectTileSet tset =
|
||||
TrimmedObjectTileSet.trimObjectTileSet((ObjectTileSet)set, fout, "png");
|
||||
tset.setImagePath(imagePath);
|
||||
// replace the original set with the trimmed tileset in the tileset bundle
|
||||
bundle.addTileSet(tileSetId, tset);
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace(System.err);
|
||||
|
||||
String msg = "Error adding tileset to bundle " + imagePath +
|
||||
", " + set.getName() + ": " + e;
|
||||
throw (IOException) new IOException(msg).initCause(e);
|
||||
}
|
||||
|
||||
} else {
|
||||
// read the image file and write it to the proper place
|
||||
File ifile = new File(imageBase, imagePath);
|
||||
if (ifile.lastModified() > newestMod) {
|
||||
// Our file's newer than the newest bundle mod - up to date.
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
// We read the image to ensure it is a valid image.
|
||||
ImageIO.read(ifile);
|
||||
|
||||
File outFile = new File(target, imagePath);
|
||||
if (outFile.lastModified() > newestMod) {
|
||||
// Our file's newer than the newest bundle mod - up to date.
|
||||
continue;
|
||||
}
|
||||
outFile.getParentFile().mkdirs();
|
||||
FileOutputStream fout = new FileOutputStream(outFile);
|
||||
FileInputStream imgin = new FileInputStream(ifile);
|
||||
StreamUtil.copy(imgin, fout);
|
||||
} catch (Exception e) {
|
||||
String msg = "Failure bundling image " + ifile + ": " + e;
|
||||
throw (IOException) new IOException(msg).initCause(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// now write a serialized representation of the tileset bundle
|
||||
// object to the bundle jar file
|
||||
File outFile = new File(target, BundleUtil.METADATA_PATH);
|
||||
|
||||
outFile.getParentFile().mkdirs();
|
||||
FileOutputStream fout = new FileOutputStream(outFile);
|
||||
ObjectOutputStream oout = new ObjectOutputStream(fout);
|
||||
oout.writeObject(bundle);
|
||||
oout.flush();
|
||||
fout.close();
|
||||
|
||||
return true;
|
||||
|
||||
} catch (Exception e) {
|
||||
String errmsg = "Failed to create bundle " + target + ": " + e;
|
||||
throw (IOException) new IOException(errmsg).initCause(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean skipIfTargetNewer ()
|
||||
{
|
||||
// We have to check modification later on a file-by-file basis, so cannot skip.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+2
-8
@@ -46,17 +46,11 @@ public class DirectoryTileSetBundlerTask extends TileSetBundlerTask
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TileSetBundler createBundler ()
|
||||
protected BundleWriter createWriter (File fromDir, String path)
|
||||
throws IOException
|
||||
{
|
||||
return new DirectoryTileSetBundler(_config);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTargetPath (File fromDir, String path)
|
||||
{
|
||||
File xmlFile = new File(path.replace(fromDir.getPath(), _deployDir.getPath()));
|
||||
return xmlFile.getParent();
|
||||
return new BundleWriter(xmlFile.getParentFile());
|
||||
}
|
||||
|
||||
/** The directory in which we want to place our tile set files for deployment. */
|
||||
|
||||
@@ -21,21 +21,21 @@ package com.threerings.media.tile.bundle.tools;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarOutputStream;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.zip.Deflater;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import net.sf.json.JSONArray;
|
||||
import net.sf.json.JSONObject;
|
||||
|
||||
import org.apache.commons.digester.Digester;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
@@ -45,6 +45,8 @@ import com.samskivert.util.HashIntMap;
|
||||
|
||||
import com.threerings.resource.FastImageIO;
|
||||
|
||||
import com.threerings.tools.JSONConversion;
|
||||
|
||||
import com.threerings.media.tile.ImageProvider;
|
||||
import com.threerings.media.tile.ObjectTileSet;
|
||||
import com.threerings.media.tile.SimpleCachingImageProvider;
|
||||
@@ -112,6 +114,90 @@ import static com.threerings.media.Log.log;
|
||||
*/
|
||||
public class TileSetBundler
|
||||
{
|
||||
/**
|
||||
* Wraps up the configuration options for writing a tile set bundle to disk and provides a
|
||||
* method to perform the write.
|
||||
*/
|
||||
public static class Writer
|
||||
{
|
||||
/** The target jar file or directory. */
|
||||
public final BundleWriter bwriter;
|
||||
|
||||
/** The bundle to write. */
|
||||
public final TileSetBundle bundle;
|
||||
|
||||
/** Loads images for the bundle. */
|
||||
public final ImageProvider improv;
|
||||
|
||||
/**
|
||||
* Creates a new writer.
|
||||
*
|
||||
* @param bwriter the tileset bundle file or directory that will be created.
|
||||
* @param bundle contains the tilesets we'd like to save out to the bundle.
|
||||
* @param improv the image provider.
|
||||
*/
|
||||
public Writer (BundleWriter bwriter, TileSetBundle bundle, ImageProvider improv)
|
||||
{
|
||||
this.bundle = bundle;
|
||||
this.bwriter = bwriter;
|
||||
this.improv = improv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether or not we trim the tileset's image in the process of writing out the
|
||||
* new bundle. Trimming will mutate tilesets directly, so beware.
|
||||
*/
|
||||
public Writer trimImages (boolean trim)
|
||||
{
|
||||
this.trim = trim;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether or not we write out raw images.
|
||||
*/
|
||||
public Writer useRawImages (boolean raw)
|
||||
{
|
||||
this.raw = raw;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the image base.
|
||||
* TODO: learn what this does and improve comment. should it just be a special improv?
|
||||
*/
|
||||
public Writer imageBase (String imageBase)
|
||||
{
|
||||
this.imageBase = imageBase;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether we write out a json file containing the meta data.
|
||||
* @param json if non-null, used to write out the bundle contents to tsbundles.json text
|
||||
* file. Otherwise, the default binary obbject output is used.
|
||||
*/
|
||||
public Writer useJson (JSONConversion.Config json)
|
||||
{
|
||||
this.json = json;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Using the configured options, creates the new target bundle.
|
||||
*/
|
||||
public void create ()
|
||||
throws IOException
|
||||
{
|
||||
TileSetBundler.createBundle(this);
|
||||
}
|
||||
|
||||
boolean trim = true;
|
||||
boolean raw = true;
|
||||
String imageBase;
|
||||
JSONConversion.Config json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a tileset bundler with the specified path to a bundler
|
||||
* configuration file. The configuration file will be loaded and used
|
||||
@@ -130,19 +216,6 @@ public class TileSetBundler
|
||||
public TileSetBundler (File configFile)
|
||||
throws IOException
|
||||
{
|
||||
this(configFile, false, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a tileset bundler with the specified bundler config
|
||||
* file and whether to keep pngs as-is or if not, re-encode them.
|
||||
*/
|
||||
public TileSetBundler (File configFile, boolean keepRawPngs, boolean uncompressed)
|
||||
throws IOException
|
||||
{
|
||||
_keepRawPngs = keepRawPngs;
|
||||
_uncompressed = uncompressed;
|
||||
|
||||
// we parse our configuration with a digester
|
||||
Digester digester = new Digester();
|
||||
|
||||
@@ -198,46 +271,24 @@ public class TileSetBundler
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a tileset bundle at the location specified by the
|
||||
* <code>targetPath</code> parameter, based on the description
|
||||
* Prepares to create a tileset bundle at the location specified by the
|
||||
* <code>target</code> parameter, based on the description
|
||||
* provided via the <code>bundleDesc</code> parameter.
|
||||
*
|
||||
* @param idBroker the tileset id broker that will be used to map
|
||||
* tileset names to tileset ids.
|
||||
* @param bundleDesc a file object pointing to the bundle description
|
||||
* file.
|
||||
* @param targetPath the path of the tileset bundle file that will be
|
||||
* created.
|
||||
* @param target the tileset bundle file or directory that will be created.
|
||||
*
|
||||
* @return a writer object that can be used to configure and create the target
|
||||
* bundle, or null if the target is up to date with respect to all source files
|
||||
*
|
||||
* @exception IOException thrown if an error occurs reading, writing
|
||||
* or processing anything.
|
||||
*/
|
||||
public void createBundle (
|
||||
TileSetIDBroker idBroker, File bundleDesc, String targetPath)
|
||||
throws IOException
|
||||
{
|
||||
createBundle(idBroker, bundleDesc, new File(targetPath));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a tileset bundle at the location specified by the
|
||||
* <code>targetPath</code> parameter, based on the description
|
||||
* provided via the <code>bundleDesc</code> parameter.
|
||||
*
|
||||
* @param idBroker the tileset id broker that will be used to map
|
||||
* tileset names to tileset ids.
|
||||
* @param bundleDesc a file object pointing to the bundle description
|
||||
* file.
|
||||
* @param target the tileset bundle file that will be created.
|
||||
*
|
||||
* @return true if the bundle was rebuilt, false if it was not because
|
||||
* the bundle file was newer than all involved source files.
|
||||
*
|
||||
* @exception IOException thrown if an error occurs reading, writing
|
||||
* or processing anything.
|
||||
*/
|
||||
public boolean createBundle (
|
||||
TileSetIDBroker idBroker, final File bundleDesc, File target)
|
||||
public Writer process (
|
||||
TileSetIDBroker idBroker, final File bundleDesc, BundleWriter target)
|
||||
throws IOException
|
||||
{
|
||||
// stick an array list on the top of the stack into which we will
|
||||
@@ -319,8 +370,8 @@ public class TileSetBundler
|
||||
}
|
||||
|
||||
// see if our newest file is newer than the tileset bundle
|
||||
if (skipIfTargetNewer() && newest < target.lastModified()) {
|
||||
return false;
|
||||
if (target.isNewerThan(newest)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// create an image provider for loading our tileset images
|
||||
@@ -332,59 +383,26 @@ public class TileSetBundler
|
||||
}
|
||||
};
|
||||
|
||||
return createBundle(target, bundle, improv, bundleDesc.getParent(), newest);
|
||||
return new Writer(target, bundle, improv).imageBase(bundleDesc.getParent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish the creation of a tileset bundle jar file.
|
||||
*
|
||||
* @param target the tileset bundle file that will be created.
|
||||
* @param bundle contains the tilesets we'd like to save out to the bundle.
|
||||
* @param improv the image provider.
|
||||
* @param imageBase the base directory for getting images for non
|
||||
* @param newestMod the most recent modification to any part of the bundle. By default we
|
||||
* ignore this since we normally duck out if we're up to date.
|
||||
* ObjectTileSet tilesets.
|
||||
* Create the tileset bundle on disk using previously configured options.
|
||||
*/
|
||||
public boolean createBundle (
|
||||
File target, TileSetBundle bundle, ImageProvider improv, String imageBase, long newestMod)
|
||||
protected static void createBundle (Writer target)
|
||||
throws IOException
|
||||
{
|
||||
return createBundleJar(target, bundle, improv, imageBase, _keepRawPngs, _uncompressed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a tileset bundle jar file.
|
||||
*
|
||||
* @param target the tileset bundle file that will be created.
|
||||
* @param bundle contains the tilesets we'd like to save out to the bundle.
|
||||
* @param improv the image provider.
|
||||
* @param imageBase the base directory for getting images for non-ObjectTileSet tilesets.
|
||||
* @param keepOriginalPngs bundle up the original PNGs as PNGs instead of converting to the
|
||||
* FastImageIO raw format
|
||||
*/
|
||||
public static boolean createBundleJar (
|
||||
File target, TileSetBundle bundle, ImageProvider improv, String imageBase,
|
||||
boolean keepOriginalPngs, boolean uncompressed)
|
||||
throws IOException
|
||||
{
|
||||
// now we have to create the actual bundle file
|
||||
FileOutputStream fout = new FileOutputStream(target);
|
||||
Manifest manifest = new Manifest();
|
||||
JarOutputStream jar = new JarOutputStream(fout, manifest);
|
||||
jar.setLevel(uncompressed ? Deflater.NO_COMPRESSION : Deflater.BEST_COMPRESSION);
|
||||
|
||||
try {
|
||||
// write all of the image files to the bundle, converting the
|
||||
// tilesets to trimmed tilesets in the process
|
||||
Iterator<Integer> iditer = bundle.enumerateTileSetIds();
|
||||
Iterator<Integer> iditer = target.bundle.enumerateTileSetIds();
|
||||
|
||||
// Store off the updated TileSets in a separate Map so we can wait to change the
|
||||
// bundle till we're done iterating.
|
||||
HashIntMap<TileSet> toUpdate = new HashIntMap<TileSet>();
|
||||
while (iditer.hasNext()) {
|
||||
int tileSetId = iditer.next().intValue();
|
||||
TileSet set = bundle.getTileSet(tileSetId);
|
||||
TileSet set = target.bundle.getTileSet(tileSetId);
|
||||
String imagePath = set.getImagePath();
|
||||
|
||||
// sanity checks
|
||||
@@ -395,22 +413,23 @@ public class TileSetBundler
|
||||
}
|
||||
|
||||
// if this is an object tileset, trim it
|
||||
if (!keepOriginalPngs && (set instanceof ObjectTileSet)) {
|
||||
if (target.trim && (set instanceof ObjectTileSet)) {
|
||||
// set the tileset up with an image provider; we
|
||||
// need to do this so that we can trim it!
|
||||
set.setImageProvider(improv);
|
||||
set.setImageProvider(target.improv);
|
||||
|
||||
// we're going to trim it, so adjust the path
|
||||
// add .raw if requested
|
||||
if (target.raw) {
|
||||
imagePath = adjustImagePath(imagePath);
|
||||
jar.putNextEntry(new JarEntry(imagePath));
|
||||
}
|
||||
OutputStream dest = target.bwriter.startNewFile(imagePath);
|
||||
|
||||
try {
|
||||
// create a trimmed object tileset, which will
|
||||
// write the trimmed tileset image to the jar
|
||||
// output stream
|
||||
// write the trimmed tileset image to the target file
|
||||
TrimmedObjectTileSet tset =
|
||||
TrimmedObjectTileSet.trimObjectTileSet(
|
||||
(ObjectTileSet)set, jar);
|
||||
TrimmedObjectTileSet.trimObjectTileSet((ObjectTileSet)set, dest,
|
||||
target.raw ? FastImageIO.FILE_SUFFIX : "png");
|
||||
tset.setImagePath(imagePath);
|
||||
// replace the original set with the trimmed
|
||||
// tileset in the tileset bundle
|
||||
@@ -427,18 +446,21 @@ public class TileSetBundler
|
||||
} else {
|
||||
// read the image file and convert it to our custom
|
||||
// format in the bundle
|
||||
File ifile = new File(imageBase, imagePath);
|
||||
File ifile = new File(target.imageBase, imagePath);
|
||||
try {
|
||||
BufferedImage image = ImageIO.read(ifile);
|
||||
if (!keepOriginalPngs && FastImageIO.canWrite(image)) {
|
||||
if (target.raw && FastImageIO.canWrite(image)) {
|
||||
imagePath = adjustImagePath(imagePath);
|
||||
jar.putNextEntry(new JarEntry(imagePath));
|
||||
// NOTE: DirectoryTileSetBundler used to check the modification time
|
||||
// of the target image here. There may be something to it, but it
|
||||
// looked unnecessary
|
||||
OutputStream dest = target.bwriter.startNewFile(imagePath);
|
||||
set.setImagePath(imagePath);
|
||||
FastImageIO.write(image, jar);
|
||||
FastImageIO.write(image, dest);
|
||||
} else {
|
||||
jar.putNextEntry(new JarEntry(imagePath));
|
||||
OutputStream dest = target.bwriter.startNewFile(imagePath);
|
||||
FileInputStream imgin = new FileInputStream(ifile);
|
||||
StreamUtil.copy(imgin, jar);
|
||||
StreamUtil.copy(imgin, dest);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
String msg = "Failure bundling image " + ifile +
|
||||
@@ -447,25 +469,39 @@ public class TileSetBundler
|
||||
}
|
||||
}
|
||||
}
|
||||
bundle.putAll(toUpdate);
|
||||
target.bundle.putAll(toUpdate);
|
||||
|
||||
// now write a serialized representation of the tileset bundle
|
||||
if (target.json != null) {
|
||||
JSONArray array = new JSONArray();
|
||||
for (Iterator<Integer> tileSetId = target.bundle.enumerateTileSetIds();
|
||||
tileSetId.hasNext(); ) {
|
||||
JSONObject tset = new JSONObject();
|
||||
int id = tileSetId.next();
|
||||
tset.put("id", id);
|
||||
tset.put("set", target.json.convert(target.bundle.get(id)));
|
||||
array.add(tset);
|
||||
}
|
||||
|
||||
// now write a serialized representation of the tileset bundle
|
||||
OutputStream fout = target.bwriter.startNewFile(BundleUtil.METADATA_JSON_PATH);
|
||||
fout.write(array.toString().getBytes());
|
||||
fout.close();
|
||||
} else {
|
||||
// object to the bundle jar file
|
||||
JarEntry entry = new JarEntry(BundleUtil.METADATA_PATH);
|
||||
jar.putNextEntry(entry);
|
||||
ObjectOutputStream oout = new ObjectOutputStream(jar);
|
||||
oout.writeObject(bundle);
|
||||
ObjectOutputStream oout = new ObjectOutputStream(
|
||||
target.bwriter.startNewFile(BundleUtil.METADATA_PATH));
|
||||
oout.writeObject(target.bundle);
|
||||
oout.flush();
|
||||
}
|
||||
|
||||
// finally close up the jar file and call ourself done
|
||||
jar.close();
|
||||
|
||||
return true;
|
||||
target.bwriter.close();
|
||||
|
||||
} catch (Exception e) {
|
||||
// remove the incomplete jar file and rethrow the exception
|
||||
jar.close();
|
||||
if (!target.delete()) {
|
||||
target.bwriter.close();
|
||||
if (!target.bwriter.delete()) {
|
||||
log.warning("Failed to close botched bundle '" + target + "'.");
|
||||
}
|
||||
String errmsg = "Failed to create bundle " + target + ": " + e;
|
||||
@@ -473,14 +509,6 @@ public class TileSetBundler
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether we should skip updating the bundle if the target is newer than any component.
|
||||
*/
|
||||
protected boolean skipIfTargetNewer ()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Replaces the image suffix with <code>.raw</code>. */
|
||||
protected static String adjustImagePath (String imagePath)
|
||||
{
|
||||
@@ -511,10 +539,4 @@ public class TileSetBundler
|
||||
|
||||
/** The digester we use to parse bundle descriptions. */
|
||||
protected Digester _digester;
|
||||
|
||||
/** Whether we should keep pngs as-is rather than re-encoding. */
|
||||
protected boolean _keepRawPngs;
|
||||
|
||||
/** Normally we compress the jar, but if we want to leave them uncompressed, we set this. */
|
||||
protected boolean _uncompressed;
|
||||
}
|
||||
|
||||
+10
-15
@@ -97,7 +97,7 @@ public class TileSetBundlerTask extends Task
|
||||
File cfile = null;
|
||||
try {
|
||||
// create a tileset bundler
|
||||
TileSetBundler bundler = createBundler();
|
||||
TileSetBundler bundler = new TileSetBundler(_config);
|
||||
|
||||
// create our tileset id broker
|
||||
MapFileTileSetIDBroker broker =
|
||||
@@ -123,16 +123,18 @@ public class TileSetBundlerTask extends Task
|
||||
"Config file should end with .xml.");
|
||||
continue;
|
||||
}
|
||||
String bpath = getTargetPath(fromDir, cpath);
|
||||
File bfile = new File(bpath);
|
||||
BundleWriter bwriter = createWriter(fromDir, cpath);
|
||||
|
||||
// create the bundle
|
||||
if (bundler.createBundle(broker, cfile, bfile)) {
|
||||
TileSetBundler.Writer writer = bundler.process(broker, cfile, bwriter);
|
||||
if (writer != null) {
|
||||
writer.useRawImages(!_keepRawPngs); // something is lost in translation here
|
||||
writer.create();
|
||||
System.out.println(
|
||||
"Created bundle from '" + cpath + "'...");
|
||||
} else {
|
||||
System.out.println(
|
||||
"Tileset bundle up to date '" + bpath + "'.");
|
||||
"Tileset bundle up to date '" + bwriter + "'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -150,18 +152,11 @@ public class TileSetBundlerTask extends Task
|
||||
/**
|
||||
* Create the bundler to use during creation.
|
||||
*/
|
||||
protected TileSetBundler createBundler ()
|
||||
protected BundleWriter createWriter (File fromDir, String path)
|
||||
throws IOException
|
||||
{
|
||||
return new TileSetBundler(_config, _keepRawPngs, _uncompressed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the target path in which our bundler will write the tile set.
|
||||
*/
|
||||
protected String getTargetPath (File fromDir, String path)
|
||||
{
|
||||
return path.substring(0, path.length()-4) + ".jar";
|
||||
return new BundleWriter(
|
||||
new File(path.substring(0, path.length()-4) + ".jar"), _uncompressed);
|
||||
}
|
||||
|
||||
protected void ensureSet (Object value, String errmsg)
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// 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.tools;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Point;
|
||||
import java.awt.Rectangle;
|
||||
|
||||
import net.sf.json.JSONArray;
|
||||
|
||||
import com.threerings.tools.JSONConversion.Config;
|
||||
import com.threerings.tools.JSONConversion.Converter;
|
||||
|
||||
/**
|
||||
* Defines some common formats for converting nenya's uses of java.awt geometric types to json.
|
||||
*/
|
||||
public class AWTConversions
|
||||
{
|
||||
/**
|
||||
* Adds all our conversion to the given config and returns it for chaining.
|
||||
*/
|
||||
public static Config addAll (Config config)
|
||||
{
|
||||
return config.
|
||||
addClassConverter(Rectangle.class, RECTANGLE).
|
||||
addClassConverter(Point.class, POINT).
|
||||
addClassConverter(Dimension.class, DIMENSION).
|
||||
addClassConverter(Color.class, COLOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts instances of color to a standard RGB byte triplet.
|
||||
*/
|
||||
public static final Converter<Color> COLOR = new Converter<Color>() {
|
||||
public Object convert (Color c, Config cfg) {
|
||||
return new Integer((c.getRed() << 16) | (c.getGreen()<<8) | c.getBlue());
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a rectangle into an json array with entries x, y, width, height.
|
||||
*/
|
||||
public static final Converter<Rectangle> RECTANGLE = new Converter<Rectangle>() {
|
||||
public Object convert (Rectangle r, Config cfg) {
|
||||
JSONArray out = new JSONArray();
|
||||
out.add(r.x);
|
||||
out.add(r.y);
|
||||
out.add(r.width);
|
||||
out.add(r.height);
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a point into a json array with entries x, y.
|
||||
*/
|
||||
public static final Converter<Point> POINT = new Converter<Point>() {
|
||||
public Object convert (Point p, Config cfg) {
|
||||
JSONArray out = new JSONArray();
|
||||
out.add(p.x);
|
||||
out.add(p.y);
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a dimension into an array with entries width, height.
|
||||
*/
|
||||
public static final Converter<Dimension> DIMENSION = new Converter<Dimension>() {
|
||||
public Object convert (Dimension d, Config cfg) {
|
||||
JSONArray out = new JSONArray();
|
||||
out.add(d.width);
|
||||
out.add(d.height);
|
||||
return out;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,399 @@
|
||||
// Nenya library - tools for developing networked games
|
||||
// Copyright (C) 2002-2012 Three Rings Design, Inc., All Rights Reserved
|
||||
// http://code.google.com/p/nenya/
|
||||
//
|
||||
// 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.tools;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedActionException;
|
||||
import java.security.PrivilegedExceptionAction;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import com.samskivert.util.ClassUtil;
|
||||
import com.samskivert.util.StringUtil;
|
||||
|
||||
import net.sf.json.JSON;
|
||||
import net.sf.json.JSONArray;
|
||||
import net.sf.json.JSONObject;
|
||||
|
||||
|
||||
/**
|
||||
* Utilities for converting instances of java objects to json. This is basically automatic for
|
||||
* simple classes, but field enumeration and overall conversion can be customized. <br/><br/>
|
||||
*
|
||||
* Example: <pre>
|
||||
* Config cfg = new Config();
|
||||
* System.out.println(cfg.convert(new Pojo()));
|
||||
* </pre>
|
||||
* Converting a type universally: <pre>
|
||||
Converter<Color> colorConv = new Converter<Color>() {
|
||||
public Object convert (Color c, Config cfg) {
|
||||
return new Integer((c.getRed() << 16) | (c.getGreen()<<8) | c.getBlue());
|
||||
}
|
||||
};
|
||||
|
||||
cfg.addClassConverter(Color.class, colorConv);
|
||||
* </pre>
|
||||
* Customizing a specific field of a class:<pre>
|
||||
FieldAccessor<SomeImpl, Collection<?>> entryConv =
|
||||
new FieldAccessor<SomeImpl, Collection<?>>() {
|
||||
public Collection<SomeValue> get (SomeImpl impl) {
|
||||
return impl.getValuesAsCollection();
|
||||
}
|
||||
};
|
||||
cfg.addFieldConfig(SomeImpl.class, new FieldConfig<SomeImpl>().
|
||||
addExclusions("privateStuff").
|
||||
addFieldConverter("valueMap", new ConvertCollectionFieldToArray<SomeImpl>(entryConv)));
|
||||
* </pre>
|
||||
*/
|
||||
public class JSONConversion
|
||||
{
|
||||
/**
|
||||
* Controls verbose logging for the class.
|
||||
*/
|
||||
public static boolean vlog = false;
|
||||
|
||||
/**
|
||||
* Logs the given arguments separated by spaces, if {@link #vlog} is set.
|
||||
*/
|
||||
public static void vlog (Object...args)
|
||||
{
|
||||
if (vlog) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("JSONConversion: ");
|
||||
StringUtil.toString(sb, args, "", "", " ");
|
||||
System.out.println(sb.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts T to a suitable parameter for {@link JSONArray#add(Object)},
|
||||
* {@link JSONObject#element(String, Object)}, etc.
|
||||
*/
|
||||
public static interface Converter<T>
|
||||
{
|
||||
/**
|
||||
* Returns an instance of {@code T} in json form. The cfg may be used to convert aggregated
|
||||
* objects.
|
||||
*/
|
||||
Object convert (T obj, Config cfg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugs into some common cases for automating the conversion of special fields. For example
|
||||
* a map that is reconstructed from the values on deserialization.
|
||||
*/
|
||||
public static interface FieldAccessor<T, F>
|
||||
{
|
||||
F get (T obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the field layout for serializing instances of {@code T}.
|
||||
*/
|
||||
public static class FieldConfig<T>
|
||||
{
|
||||
/**
|
||||
* Fields that should not be serialized.
|
||||
*/
|
||||
public List<String> excludeFields = Lists.newArrayList();
|
||||
|
||||
/**
|
||||
* Fields with custom conversions.
|
||||
*/
|
||||
public Map<String, Converter<?>> customFields = Maps.newHashMap();
|
||||
|
||||
/**
|
||||
* Adds some excluded fields and returns this for chaining.
|
||||
*/
|
||||
public FieldConfig<T> addExclusions (String ...names)
|
||||
{
|
||||
excludeFields.addAll(Arrays.asList(names));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a custom converter for a field and returns this for chaining.
|
||||
*/
|
||||
public FieldConfig<T> addFieldConverter (String name, Converter<T> field)
|
||||
{
|
||||
customFields.put(name, (Converter<?>)field);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if a field ought to be excluded, based on current configuration.
|
||||
*/
|
||||
public boolean excludeField (Field field)
|
||||
{
|
||||
int mask = Modifier.FINAL | Modifier.TRANSIENT | Modifier.STATIC;
|
||||
return (field.getModifiers() & mask) != 0 ||
|
||||
excludeFields.contains(field.getName());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the conversion of java objects to json. Works mostly by reflection, with
|
||||
* customizability.
|
||||
*/
|
||||
public static class Config
|
||||
{
|
||||
/**
|
||||
* Overrides the field treatment for encountered instances.
|
||||
*/
|
||||
public Map<Class<?>, FieldConfig<?>> classes = Maps.newHashMap();
|
||||
|
||||
/**
|
||||
* Overrides the conversion process for encountered instances.
|
||||
*/
|
||||
public Map<Class<?>, Converter<?>> converters = Maps.newHashMap();
|
||||
|
||||
/**
|
||||
* Overrides the way fields are serialized for instances of {@code T} and returns this
|
||||
* for chaining.
|
||||
*/
|
||||
public <T> Config addFieldConfig (Class<T> clazz, FieldConfig<T> config)
|
||||
{
|
||||
classes.put(clazz, config);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the way instances of a given type are serialized and returns this for chaining.
|
||||
*/
|
||||
public <T> Config addClassConverter (Class<T> clazz, Converter<T> converter)
|
||||
{
|
||||
converters.put(clazz, converter);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up the field access for a class. This also makes a fairly lame attempt to
|
||||
* ensure the most derived entry is used.
|
||||
* TODO: spruce up class sort
|
||||
*/
|
||||
public FieldConfig<?> findConfig (Class<?> clazz)
|
||||
{
|
||||
Class<?> found = null;
|
||||
for (Class<?> entry : classes.keySet()) {
|
||||
if (entry.isAssignableFrom(clazz)) {
|
||||
if (found == null || found.isAssignableFrom(entry)) {
|
||||
found = entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
return classes.get(found);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if a class is primitive for our purposes.
|
||||
*/
|
||||
public boolean isPrimitive (Class<?> cl)
|
||||
{
|
||||
return cl.isPrimitive() ||
|
||||
Number.class.isAssignableFrom(cl) ||
|
||||
String.class.isAssignableFrom(cl) ||
|
||||
Boolean.class.isAssignableFrom(cl) ||
|
||||
(cl.isArray() && isPrimitive(cl.getComponentType()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given object to json.
|
||||
* @return either a {@link JSON} instance of an object suitable for insertion to
|
||||
* {@link JSONObject#element(String, Object)}, etc.
|
||||
*/
|
||||
public Object convert (final Object obj)
|
||||
{
|
||||
if (obj == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
vlog("Converting object", obj, "of", obj.getClass());
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Converter<Object> converter = (Converter<Object>)converters.get(obj.getClass());
|
||||
if (converter != null) {
|
||||
vlog("..using converter:", converter);
|
||||
return converter.convert(obj, this);
|
||||
}
|
||||
|
||||
if (isPrimitive(obj.getClass())) {
|
||||
vlog("..returning primitive");
|
||||
return obj;
|
||||
}
|
||||
|
||||
if (obj.getClass().isArray()) {
|
||||
vlog("..aggregating array");
|
||||
JSONArray array = new JSONArray();
|
||||
Object[] arr = (Object[])obj;
|
||||
for (int ii = 0; ii < arr.length; ++ii) {
|
||||
array.add(convert(arr[ii]));
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
if (List.class.isAssignableFrom(obj.getClass())) {
|
||||
vlog("..aggregating list");
|
||||
JSONArray array = new JSONArray();
|
||||
List<?> arr = (List<?>)obj;
|
||||
for (int ii = 0; ii < arr.size(); ++ii) {
|
||||
array.add(convert(arr.get(ii)));
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
// TODO: add more collection types as needed
|
||||
|
||||
try {
|
||||
return AccessController.doPrivileged(new PrivilegedExceptionAction<JSONObject>() {
|
||||
public JSONObject run ()
|
||||
throws Exception
|
||||
{
|
||||
vlog("..aggregating fields");
|
||||
return convertFields(obj);
|
||||
}
|
||||
});
|
||||
} catch (PrivilegedActionException ex) {
|
||||
throw new RuntimeException("Failed to make object", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an object to json using it's declared fields. This can be called on an
|
||||
* object known to be of an appropriate composition, otherwise use {@link #convert(Object)}.
|
||||
*/
|
||||
public JSONObject convertFields (Object obj)
|
||||
{
|
||||
try {
|
||||
FieldConfig<?> config = findConfig(obj.getClass());
|
||||
List<Field> fields = cachedFields.get(obj.getClass());
|
||||
if (fields == null) {
|
||||
cachedFields.put(obj.getClass(), fields = Lists.newArrayList());
|
||||
ClassUtil.getFields(obj.getClass(), fields);
|
||||
vlog("Got fields for class", obj.getClass(), "fields", fields);
|
||||
}
|
||||
|
||||
JSONObject jobj = new JSONObject();
|
||||
for (Field field : fields) {
|
||||
if (config != null && config.excludeField(field)) {
|
||||
continue;
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
Converter<Object> fieldConverter = config != null ?
|
||||
(Converter<Object>)config.customFields.get(field.getName()) : null;
|
||||
String fieldName = getFieldName(field);
|
||||
if (fieldConverter != null) {
|
||||
vlog("....converting field", field, "with name", fieldName,
|
||||
"using custom converter", fieldConverter);
|
||||
jobj.element(fieldName, fieldConverter.convert(obj, this));
|
||||
} else {
|
||||
vlog("....converting field", field, "with name", fieldName);
|
||||
jobj.element(fieldName, convert(field.get(obj)));
|
||||
}
|
||||
}
|
||||
return jobj;
|
||||
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException("Failed to make object", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the field from an object of a given name, using a doPrivileged call so that
|
||||
* protected members can be accessed.
|
||||
*/
|
||||
public Object getFieldValue (final Object obj, final String fieldName)
|
||||
{
|
||||
try {
|
||||
return AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
|
||||
public Object run ()
|
||||
throws Exception
|
||||
{
|
||||
List<Field> fields = cachedFields.get(obj.getClass());
|
||||
if (fields == null) {
|
||||
cachedFields.put(obj.getClass(), fields = Lists.newArrayList());
|
||||
ClassUtil.getFields(obj.getClass(), fields);
|
||||
}
|
||||
for (Field field : fields) {
|
||||
if (field.getName().equals(fieldName)) {
|
||||
return field.get(obj);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
} catch (PrivilegedActionException ex) {
|
||||
throw new RuntimeException("Failed to make object", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to call {@link #convert(Object)} each item in an iterable and return
|
||||
* an array of the results.
|
||||
*/
|
||||
public <T> JSONArray toArray (Iterable<T> objects)
|
||||
{
|
||||
JSONArray arr = new JSONArray();
|
||||
for (Object item : objects) {
|
||||
arr.add(convert(item));
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of a field, stripping off the leading underscore if present.
|
||||
*/
|
||||
public String getFieldName (Field field)
|
||||
{
|
||||
String name = field.getName();
|
||||
if (name.startsWith("_")) {
|
||||
name = name.substring(1);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
private Map<Class<?>, List<Field>> cachedFields = Maps.newHashMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an object to an array using an collection member. This covers the fairly common
|
||||
* case where for example a {@code FooSet} needs to be stored as a json array of {@code Foo}
|
||||
* instances, but for practical reasons the iterable is a member.
|
||||
*/
|
||||
public static class ConvertCollectionFieldToArray<T> implements Converter<T>
|
||||
{
|
||||
public final FieldAccessor<T, Collection<?>> accessor;
|
||||
|
||||
public ConvertCollectionFieldToArray (FieldAccessor<T, Collection<?>> accessor)
|
||||
{
|
||||
this.accessor = accessor;
|
||||
}
|
||||
|
||||
public JSON convert (T obj, Config cfg)
|
||||
{
|
||||
return cfg.toArray(accessor.get(obj));
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -45,7 +45,7 @@ public class BuildTestTileSetBundle
|
||||
// create our bundler and get going
|
||||
TileSetBundler bundler = new TileSetBundler(configPath);
|
||||
File descFile = new File(descPath);
|
||||
bundler.createBundle(broker, descFile, targetPath);
|
||||
bundler.process(broker, descFile, new BundleWriter(new File(targetPath))).create();
|
||||
|
||||
} catch (IOException ioe) {
|
||||
ioe.printStackTrace();
|
||||
|
||||
Reference in New Issue
Block a user