/*
* This file is part of the Haven & Hearth game client.
* Copyright (C) 2009 Fredrik Tolf <fredrik@dolda2000.com>, and
* Björn Johannessen <johannessen.bjorn@gmail.com>
*
* Redistribution and/or modification of this file is subject to the
* terms of the GNU Lesser General Public License, version 3, as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* Other parts of this source tree adhere to other copying
* rights. Please see the file `COPYING' in the root directory of the
* source tree for details.
*
* A copy the GNU Lesser General Public License is distributed along
* with the source tree of which this file is a part in the file
* `doc/LPGL-3'. If it is missing for any reason, please see the Free
* Software Foundation's website at <http://www.fsf.org/>, or write
* to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307 USA
*/
package haven;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.annotation.*;
import java.util.*;
import java.net.*;
import java.io.*;
import javax.imageio.*;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
public class Resource implements Comparable<Resource>, Prioritized, Serializable {
private final static Map<String, Resource> cache;
private static Loader loader;
private static CacheSource prscache;
public static ThreadGroup loadergroup = null;
private static Map<String, LayerFactory<?>> ltypes = new TreeMap<String, LayerFactory<?>>();
static Set<Resource> loadwaited = new HashSet<Resource>();
public static Class<Image> imgc = Image.class;
public static Class<Tile> tile = Tile.class;
public static Class<Neg> negc = Neg.class;
public static Class<Anim> animc = Anim.class;
public static Class<Tileset> tileset = Tileset.class;
public static Class<Pagina> pagina = Pagina.class;
public static Class<AButton> action = AButton.class;
public static Class<Audio> audio = Audio.class;
public static Class<Tooltip> tooltip = Tooltip.class;
static {
if(Config.softres)
cache = new CacheMap<String, Resource>();
else
cache = new TreeMap<String, Resource>();
}
static {
try {
chainloader(new Loader(new FileSource(new File(Config.userhome+"/custom_res"))));
String dir = Config.resdir;
if(dir == null)
dir = System.getenv("SALEM_RESDIR");
if(dir != null)
chainloader(new Loader(new FileSource(new File(dir))));
} catch(Exception e) {
/* Ignore these. We don't want to be crashing the client
* for users just because of errors in development
* aids. */
}
if(!Config.nolocalres)
chainloader(new Loader(new JarSource()));
}
private LoadException error;
private Collection<Layer> layers = new LinkedList<Layer>();
public final String name;
public int ver;
public boolean loading;
public ResSource source;
private transient Indir<Resource> indir = null;
int prio = 0;
public static class Spec implements Indir<Resource> {
public final String name;
public final int ver;
public Spec(String name, int ver) {
this.name = name;
this.ver = ver;
}
public Resource get(int prio) {
return(load(name, ver));
}
public Resource get() {
return(get(0));
}
}
private Resource(String name, int ver) {
this.name = name;
this.ver = ver;
error = null;
loading = true;
}
public static void addcache(ResCache cache) {
CacheSource src = new CacheSource(cache);
prscache = src;
chainloader(new Loader(src));
}
public static void addurl(URL url) {
ResSource src = new HttpSource(url);
final CacheSource mc = prscache;
if(mc != null) {
src = new TeeSource(src) {
public OutputStream fork(String name) throws IOException {
return(mc.cache.store("res/" + name));
}
};
}
chainloader(new Loader(src));
}
private static void chainloader(Loader nl) {
synchronized(Resource.class) {
if(loader == null) {
loader = nl;
} else {
Loader l;
for(l = loader; l.next != null; l = l.next);
l.chain(nl);
}
}
}
public static Resource load(String name, int ver, int prio) {
Resource res;
synchronized(cache) {
res = cache.get(name);
if(res != null) {
if((res.ver != -1) && (ver != -1)) {
if(res.ver < ver) {
res = null;
cache.remove(name);
} else if(res.ver > ver) {
/* Throw LoadException rather than
* RuntimeException here, to make sure
* obsolete resources doing nested loading get
* properly handled. This could be the wrong
* way of going about it, however; I'm not
* sure. */
throw(new LoadException(String.format("Weird version number on %s (%d > %d), loaded from %s", res.name, res.ver, ver, res.source), res));
}
} else if(ver == -1) {
if(res.error != null) {
res = null;
cache.remove(name);
}
}
}
if(res != null) {
res.boostprio(prio);
return(res);
}
res = new Resource(name, ver);
res.prio = prio;
cache.put(name, res);
}
loader.load(res);
return(res);
}
public static int numloaded() {
synchronized(cache) {
return(cache.size());
}
}
public static Collection<Resource> cached() {
synchronized(cache) {
return(cache.values());
}
}
public static Resource load(String name, int ver) {
return(load(name, ver, 0));
}
public static int qdepth() {
int ret = 0;
for(Loader l = loader; l != null; l = l.next)
ret += l.queue.size();
return(ret);
}
public static Resource load(String name) {
return(load(name, -1));
}
public void boostprio(int newprio) {
if(prio < newprio)
prio = newprio;
}
public Resource loadwaitint() throws InterruptedException {
synchronized(this) {
boostprio(10);
while(loading) {
wait();
}
}
return(this);
}
public String basename() {
int p = name.lastIndexOf('/');
if(p < 0)
return(name);
return(name.substring(p + 1));
}
public Resource loadwait() {
boolean i = false;
synchronized(loadwaited) {
loadwaited.add(this);
}
synchronized(this) {
boostprio(10);
while(loading) {
try {
wait();
} catch(InterruptedException e) {
i = true;
}
}
}
if(i)
Thread.currentThread().interrupt();
return(this);
}
public static interface ResSource {
public InputStream get(String name) throws IOException;
}
public static abstract class TeeSource implements ResSource, Serializable {
public ResSource back;
public TeeSource(ResSource back) {
this.back = back;
}
public InputStream get(String name) throws IOException {
StreamTee tee = new StreamTee(back.get(name));
tee.setncwe();
tee.attach(fork(name));
return(tee);
}
public abstract OutputStream fork(String name) throws IOException;
public String toString() {
return("forking source backed by " + back);
}
}
public static class CacheSource implements ResSource, Serializable {
public transient ResCache cache;
public CacheSource(ResCache cache) {
this.cache = cache;
}
public InputStream get(String name) throws IOException {
return(cache.fetch("res/" + name));
}
public String toString() {
return("cache source backed by " + cache);
}
}
public static class FileSource implements ResSource, Serializable {
File base;
public FileSource(File base) {
this.base = base;
}
public InputStream get(String name) throws FileNotFoundException {
File cur = base;
String[] parts = name.split("/");
for(int i = 0; i < parts.length - 1; i++)
cur = new File(cur, parts[i]);
cur = new File(cur, parts[parts.length - 1] + ".res");
return(new FileInputStream(cur));
}
public String toString() {
return("filesystem res source (" + base + ")");
}
}
public static class JarSource implements ResSource, Serializable {
public InputStream get(String name) throws FileNotFoundException {
InputStream s = Resource.class.getResourceAsStream("/res/" + name + ".res");
if(s == null)
throw(new FileNotFoundException("Could not find resource locally: " + name));
return(s);
}
public String toString() {
return("local res source");
}
}
public static class HttpSource implements ResSource, Serializable {
private final transient SslHelper ssl;
public URL baseurl;
{
ssl = new SslHelper();
try {
ssl.trust(ssl.loadX509(Resource.class.getResourceAsStream("ressrv.crt")));
} catch(java.security.cert.CertificateException e) {
throw(new Error("Invalid built-in certificate", e));
} catch(IOException e) {
throw(new Error(e));
}
ssl.ignoreName();
}
public HttpSource(URL baseurl) {
this.baseurl = baseurl;
}
private URL encodeurl(URL raw) throws IOException {
/* This is "kinda" ugly. It is, actually, how the Java
* documentation recommend that it be done, though... */
try {
return(new URL(new URI(raw.getProtocol(), raw.getHost(), raw.getPath(), raw.getRef()).toASCIIString()));
} catch(URISyntaxException e) {
throw(new IOException(e));
}
}
public InputStream get(String name) throws IOException {
URL resurl = encodeurl(new URL(baseurl, name + ".res"));
URLConnection c;
int tries = 0;
while(true) {
try {
if(resurl.getProtocol().equals("https"))
c = ssl.connect(resurl);
else
c = resurl.openConnection();
c.addRequestProperty("User-Agent", "Haven/1.0");
return(c.getInputStream());
} catch(ConnectException e) {
if(++tries >= 5)
throw(new IOException("Connection failed five times", e));
}
}
}
public String toString() {
return("HTTP res source (" + baseurl + ")");
}
}
private static class Loader implements Runnable {
private ResSource src;
private Loader next = null;
private Queue<Resource> queue = new PrioQueue<Resource>();
private transient Thread th = null;
public Loader(ResSource src) {
this.src = src;
}
public void chain(Loader next) {
this.next = next;
}
public void load(Resource res) {
synchronized(queue) {
queue.add(res);
queue.notifyAll();
}
synchronized(Loader.this) {
if(th == null) {
th = new HackThread(loadergroup, Loader.this, "Haven resource loader");
th.setDaemon(true);
th.start();
}
}
}
public void run() {
try {
while(true) {
Resource cur;
synchronized(queue) {
while((cur = queue.poll()) == null)
queue.wait();
}
synchronized(cur) {
handle(cur);
}
cur = null;
}
} catch(InterruptedException e) {
} finally {
synchronized(Loader.this) {
/* Yes, I know there's a race condition. */
th = null;
}
}
}
private void handle(Resource res) {
InputStream in = null;
try {
res.source = src;
try {
try {
in = src.get(res.name);
res.load(in);
res.error = null;
res.loading = false;
res.notifyAll();
return;
} catch(IOException e) {
throw(new LoadException(e, res));
}
} catch(RuntimeException e) {
LoadException error;
if(e instanceof LoadException)
error = (LoadException)e;
else
error = new LoadException(e, res);
error.src = src;
error.prev = res.error;
res.error = error;
if(next == null) {
res.loading = false;
res.notifyAll();
} else {
next.load(res);
}
}
} finally {
try {
if(in != null)
in.close();
} catch(IOException e) {}
}
}
}
public static class LoadException extends RuntimeException {
public Resource res;
public ResSource src;
public LoadException prev;
public LoadException(String msg, Resource res) {
super(msg);
this.res = res;
}
public LoadException(String msg, Throwable cause, Resource res) {
super(msg, cause);
this.res = res;
}
public LoadException(Throwable cause, Resource res) {
super("Load error in resource " + res.toString() + ", from " + res.source, cause);
this.res = res;
}
}
public static class Loading extends haven.Loading {
public final Resource res;
public Loading(Resource res) {
this.res = res;
}
public String toString() {
return("#<Resource " + res.name + ">");
}
public boolean canwait() {return(true);}
public void waitfor() throws InterruptedException {
res.loadwaitint();
}
}
public static Coord cdec(byte[] buf, int off) {
return(new Coord(Utils.int16d(buf, off), Utils.int16d(buf, off + 2)));
}
public abstract class Layer implements Serializable {
public abstract void init();
public Resource getres() {
return(Resource.this);
}
}
public interface LayerFactory<T extends Layer> {
public T cons(Resource res, byte[] buf);
}
public static class LayerConstructor<T extends Layer> implements LayerFactory<T> {
public final Class<T> cl;
private final Constructor<T> cons;
public LayerConstructor(Class<T> cl) {
this.cl = cl;
try {
this.cons = cl.getConstructor(Resource.class, byte[].class);
} catch(NoSuchMethodException e) {
throw(new RuntimeException("No proper constructor found for layer type " + cl.getName(), e));
}
}
public T cons(Resource res, byte[] buf) {
try {
return(cons.newInstance(res, buf));
} catch(InstantiationException e) {
throw(new LoadException(e, res));
} catch(IllegalAccessException e) {
throw(new LoadException(e, res));
} catch(InvocationTargetException e) {
Throwable c = e.getCause();
if(c instanceof RuntimeException)
throw((RuntimeException)c);
else
throw(new LoadException(e, res));
}
}
}
public static void addltype(String name, LayerFactory<?> cons) {
ltypes.put(name, cons);
}
public static <T extends Layer> void addltype(String name, Class<T> cl) {
addltype(name, new LayerConstructor<T>(cl));
}
@dolda.jglob.Discoverable
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface LayerName {
public String value();
}
static {
for(Class<?> cl : dolda.jglob.Loader.get(LayerName.class).classes()) {
String nm = cl.getAnnotation(LayerName.class).value();
if(LayerFactory.class.isAssignableFrom(cl)) {
try {
addltype(nm, cl.asSubclass(LayerFactory.class).newInstance());
} catch(InstantiationException e) {
throw(new Error(e));
} catch(IllegalAccessException e) {
throw(new Error(e));
}
} else if(Layer.class.isAssignableFrom(cl)) {
addltype(nm, cl.asSubclass(Layer.class));
} else {
throw(new Error("Illegal resource layer class: " + cl));
}
}
}
public interface IDLayer<T> {
public T layerid();
}
@LayerName("image")
public class Image extends Layer implements Comparable<Image>, IDLayer<Integer> {
public transient BufferedImage img;
transient private Tex tex;
public final int z, subz;
public final boolean nooff;
public final int id;
private int gay = -1;
public Coord sz;
public Coord o;
public Image(byte[] buf) {
z = Utils.int16d(buf, 0);
subz = Utils.int16d(buf, 2);
/* Obsolete flag 1: Layered */
nooff = (buf[4] & 2) != 0;
id = Utils.int16d(buf, 5);
o = cdec(buf, 7);
try {
img = ImageIO.read(new ByteArrayInputStream(buf, 11, buf.length - 11));
} catch(IOException e) {
throw(new LoadException(e, Resource.this));
}
if(img == null)
throw(new LoadException("Invalid image data in " + name, Resource.this));
sz = Utils.imgsz(img);
}
public synchronized Tex tex() {
if(tex != null)
return(tex);
tex = new TexI(img) {
public String toString() {
return("TexI(" + Resource.this.name + ", " + id + ")");
}
};
return(tex);
}
private boolean detectgay() {
for(int y = 0; y < sz.y; y++) {
for(int x = 0; x < sz.x; x++) {
if((img.getRGB(x, y) & 0x00ffffff) == 0x00ff0080)
return(true);
}
}
return(false);
}
public boolean gayp() {
if(gay == -1)
gay = detectgay()?1:0;
return(gay == 1);
}
public int compareTo(Image other) {
return(z - other.z);
}
public Integer layerid() {
return(id);
}
public void init() {}
}
@LayerName("tooltip")
public class Tooltip extends Layer {
public final String t;
public Tooltip(byte[] buf) {
try {
t = new String(buf, "UTF-8");
} catch(UnsupportedEncodingException e) {
throw(new LoadException(e, Resource.this));
}
}
public void init() {}
}
@LayerName("tile")
public class Tile extends Layer {
transient BufferedImage img;
transient private Tex tex;
public final int id;
public final int w;
public final char t;
public Tile(byte[] buf) {
t = (char)Utils.ub(buf[0]);
id = Utils.ub(buf[1]);
w = Utils.uint16d(buf, 2);
try {
img = ImageIO.read(new ByteArrayInputStream(buf, 4, buf.length - 4));
} catch(IOException e) {
throw(new LoadException(e, Resource.this));
}
if(img == null)
throw(new LoadException("Invalid image data in " + name, Resource.this));
}
public synchronized Tex tex() {
if(tex == null)
tex = new TexI(img);
return(tex);
}
public void init() {}
}
@LayerName("neg")
public class Neg extends Layer {
public Coord cc;
public Coord[][] ep;
public Neg(byte[] buf) {
int off;
cc = cdec(buf, 0);
ep = new Coord[8][0];
int en = buf[16];
off = 17;
for(int i = 0; i < en; i++) {
int epid = buf[off];
int cn = Utils.uint16d(buf, off + 1);
off += 3;
ep[epid] = new Coord[cn];
for(int o = 0; o < cn; o++) {
ep[epid][o] = cdec(buf, off);
off += 4;
}
}
}
public void init() {}
}
@LayerName("anim")
public class Anim extends Layer {
private int[] ids;
public int id, d;
public Image[][] f;
public Anim(byte[] buf) {
id = Utils.int16d(buf, 0);
d = Utils.uint16d(buf, 2);
ids = new int[Utils.uint16d(buf, 4)];
if(buf.length - 6 != ids.length * 2)
throw(new LoadException("Invalid anim descriptor in " + name, Resource.this));
for(int i = 0; i < ids.length; i++)
ids[i] = Utils.int16d(buf, 6 + (i * 2));
}
public void init() {
f = new Image[ids.length][];
Image[] typeinfo = new Image[0];
for(int i = 0; i < ids.length; i++) {
LinkedList<Image> buf = new LinkedList<Image>();
for(Image img : layers(Image.class, false)) {
if(img.id == ids[i])
buf.add(img);
}
f[i] = buf.toArray(typeinfo);
}
}
}
@LayerName("tileset2")
public class Tileset extends Layer {
private String tn = "gnd";
public Object[] ta = new Object[0];
private transient Tiler.Factory tfac;
public WeightList<Resource> flavobjs = new WeightList<Resource>();
public WeightList<Tile> ground;
public WeightList<Tile>[] ctrans, btrans;
public int flavprob;
private Tileset() {
}
public Tileset(byte[] bbuf) {
Message buf = new Message(0, bbuf);
while(!buf.eom()) {
int p = buf.uint8();
switch(p) {
case 0:
tn = buf.string();
ta = buf.list();
break;
case 1:
int flnum = buf.uint16();
flavprob = buf.uint16();
for(int i = 0; i < flnum; i++) {
String fln = buf.string();
int flv = buf.uint16();
int flw = buf.uint8();
try {
flavobjs.add(load(fln, flv), flw);
} catch(RuntimeException e) {
throw(new LoadException("Illegal resource dependency", e, Resource.this));
}
}
break;
default:
throw(new LoadException("Invalid tileset part " + p + " in " + name, Resource.this));
}
}
}
public Tiler.Factory tfac() {
synchronized(this) {
if(tfac == null) {
CodeEntry ent = layer(CodeEntry.class);
if(ent != null) {
tfac = ent.get(Tiler.Factory.class);
} else {
if((tfac = Tiler.byname(tn)) == null)
throw(new RuntimeException("Invalid tiler name in " + Resource.this.name + ": " + tn));
}
}
return(tfac);
}
}
private void packtiles(Collection<Tile> tiles, Coord tsz) {
if(tiles.size() < 1)
return;
int min = -1, minw = -1, minh = -1, mine = -1;
final int nt = tiles.size();
for(int i = 1; i <= nt; i++) {
int w = Tex.nextp2(tsz.x * i);
int h;
if((nt % i) == 0)
h = nt / i;
else
h = (nt / i) + 1;
h = Tex.nextp2(tsz.y * h);
int a = w * h;
int e = (w < h)?h:w;
if((min == -1) || (a < min) || ((a == min) && (e < mine))) {
min = a;
minw = w;
minh = h;
mine = e;
}
}
final Tile[] order = new Tile[nt];
final Coord[] place = new Coord[nt];
Tex packbuf = new TexL(new Coord(minw, minh)) {
{
mipmap(Mipmapper.avg);
minfilter(javax.media.opengl.GL2.GL_NEAREST_MIPMAP_LINEAR);
centroid = true;
}
protected BufferedImage fill() {
BufferedImage buf = TexI.mkbuf(dim);
Graphics g = buf.createGraphics();
for(int i = 0; i < nt; i++)
g.drawImage(order[i].img, place[i].x, place[i].y, null);
g.dispose();
return(buf);
}
public String toString() {
return("TileTex(" + Resource.this.name + ")");
}
};
int x = 0, y = 0, n = 0;
for(Tile t : tiles) {
if(y >= minh)
throw(new LoadException("Could not pack tiles into calculated minimum texture", Resource.this));
order[n] = t;
place[n] = new Coord(x, y);
t.tex = new TexSI(packbuf, place[n], tsz);
n++;
if((x += tsz.x) > (minw - tsz.x)) {
x = 0;
y += tsz.y;
}
}
}
@SuppressWarnings("unchecked")
public void init() {
WeightList<Tile> ground = new WeightList<Tile>();
WeightList<Tile>[] ctrans = new WeightList[15];
WeightList<Tile>[] btrans = new WeightList[15];
for(int i = 0; i < 15; i++) {
ctrans[i] = new WeightList<Tile>();
btrans[i] = new WeightList<Tile>();
}
int cn = 0, bn = 0;
Collection<Tile> tiles = new LinkedList<Tile>();
Coord tsz = null;
for(Tile t : layers(Tile.class, false)) {
if(t.t == 'g') {
ground.add(t, t.w);
} else if(t.t == 'b') {
btrans[t.id - 1].add(t, t.w);
bn++;
} else if(t.t == 'c') {
ctrans[t.id - 1].add(t, t.w);
cn++;
}
tiles.add(t);
if(tsz == null) {
tsz = Utils.imgsz(t.img);
} else {
if(!Utils.imgsz(t.img).equals(tsz)) {
throw(new LoadException("Different tile sizes within set", Resource.this));
}
}
}
if(ground.size() > 0)
this.ground = ground;
if(cn > 0)
this.ctrans = ctrans;
if(bn > 0)
this.btrans = btrans;
packtiles(tiles, tsz);
}
}
/* Only for backwards compatibility */
@LayerName("tileset")
public static class OrigTileset implements LayerFactory<Tileset> {
public Tileset cons(Resource res, byte[] buf) {
Tileset ret = res.new Tileset();
int[] off = new int[1];
off[0] = 0;
int fl = Utils.ub(buf[off[0]++]);
int flnum = Utils.uint16d(buf, off[0]);
off[0] += 2;
ret.flavprob = Utils.uint16d(buf, off[0]);
off[0] += 2;
for(int i = 0; i < flnum; i++) {
String fln = Utils.strd(buf, off);
int flv = Utils.uint16d(buf, off[0]);
off[0] += 2;
int flw = Utils.ub(buf[off[0]++]);
try {
ret.flavobjs.add(load(fln, flv), flw);
} catch(RuntimeException e) {
throw(new LoadException("Illegal resource dependency", e, res));
}
}
return(ret);
}
}
@LayerName("pagina")
public class Pagina extends Layer {
public final String text;
public Pagina(byte[] buf) {
try {
text = new String(buf, "UTF-8");
} catch(UnsupportedEncodingException e) {
throw(new LoadException(e, Resource.this));
}
}
public void init() {}
}
@LayerName("action")
public class AButton extends Layer {
public final String name;
public final Resource parent;
public final char hk;
public final String[] ad;
public AButton(byte[] buf) {
int[] off = new int[1];
off[0] = 0;
String pr = Utils.strd(buf, off);
int pver = Utils.uint16d(buf, off[0]);
off[0] += 2;
if(pr.length() == 0) {
parent = null;
} else {
try {
parent = load(pr, pver);
} catch(RuntimeException e) {
throw(new LoadException("Illegal resource dependency", e, Resource.this));
}
}
name = Utils.strd(buf, off);
Utils.strd(buf, off); /* Prerequisite skill */
hk = (char)Utils.uint16d(buf, off[0]);
off[0] += 2;
ad = new String[Utils.uint16d(buf, off[0])];
off[0] += 2;
for(int i = 0; i < ad.length; i++)
ad[i] = Utils.strd(buf, off);
}
public void init() {}
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface PublishedCode {
String name();
Class<? extends Instancer> instancer() default Instancer.class;
public interface Instancer {
public Object make(Class<?> cl) throws InstantiationException, IllegalAccessException;
}
}
@LayerName("code")
public class Code extends Layer {
public final String name;
transient public final byte[] data;
public Code(byte[] buf) {
int[] off = new int[1];
off[0] = 0;
name = Utils.strd(buf, off);
data = new byte[buf.length - off[0]];
System.arraycopy(buf, off[0], data, 0, data.length);
}
public void init() {}
}
public class ResClassLoader extends ClassLoader {
public ResClassLoader(ClassLoader parent) {
super(parent);
}
public Resource getres() {
return(Resource.this);
}
public String toString() {
return("cl:" + Resource.this.toString());
}
};
public static Resource classres(final Class<?> cl) {
return(java.security.AccessController.doPrivileged(new java.security.PrivilegedAction<Resource>() {
public Resource run() {
ClassLoader l = cl.getClassLoader();
if(l instanceof ResClassLoader)
return(((ResClassLoader)l).getres());
throw(new RuntimeException("Cannot fetch resource of non-resloaded class " + cl));
}
}));
}
public <T> T getcode(Class<T> cl, boolean fail) {
CodeEntry e = layer(CodeEntry.class);
if(e == null) {
if(fail)
throw(new RuntimeException("Tried to fetch non-present res-loaded class " + cl.getName() + " from " + Resource.this.name));
return(null);
}
return(e.get(cl, fail));
}
public static class LibClassLoader extends ClassLoader {
private final ClassLoader[] classpath;
public LibClassLoader(ClassLoader parent, Collection<ClassLoader> classpath) {
super(parent);
this.classpath = classpath.toArray(new ClassLoader[0]);
}
public Class<?> findClass(String name) throws ClassNotFoundException {
for(ClassLoader lib : classpath) {
try {
return(lib.loadClass(name));
} catch(ClassNotFoundException e) {}
}
throw(new ClassNotFoundException("Could not find " + name + " in any of " + Arrays.asList(classpath).toString()));
}
}
@LayerName("codeentry")
public class CodeEntry extends Layer {
private String clnm;
private Map<String, Code> clmap = new TreeMap<String, Code>();
private Map<String, String> pe = new TreeMap<String, String>();
private Collection<Resource> classpath = new LinkedList<Resource>();
transient private ClassLoader loader;
transient private Map<String, Class<?>> lpe = null;
transient private Map<Class<?>, Object> ipe = new HashMap<Class<?>, Object>();
public CodeEntry(byte[] buf) {
int[] off = new int[1];
off[0] = 0;
while(off[0] < buf.length) {
int t = buf[off[0]++];
if(t == 1) {
while(true) {
String en = Utils.strd(buf, off);
String cn = Utils.strd(buf, off);
if(en.length() == 0)
break;
pe.put(en, cn);
}
} else if(t == 2) {
while(true) {
String ln = Utils.strd(buf, off);
if(ln.length() == 0)
break;
int ver = Utils.uint16d(buf, off[0]); off[0] += 2;
classpath.add(Resource.load(ln, ver));
}
} else {
throw(new LoadException("Unknown codeentry data type: " + t, Resource.this));
}
}
}
public void init() {
for(Code c : layers(Code.class, false))
clmap.put(c.name, c);
}
public ClassLoader loader(final boolean wait) {
synchronized(CodeEntry.this) {
if(this.loader == null) {
this.loader = java.security.AccessController.doPrivileged(new java.security.PrivilegedAction<ClassLoader>() {
public ClassLoader run() {
ClassLoader parent = Resource.class.getClassLoader();
if(classpath.size() > 0) {
Collection<ClassLoader> loaders = new LinkedList<ClassLoader>();
for(Resource res : classpath) {
if(wait)
res.loadwait();
loaders.add(res.layer(CodeEntry.class).loader(wait));
}
parent = new LibClassLoader(parent, loaders);
}
return(new ResClassLoader(parent) {
public Class<?> findClass(String name) throws ClassNotFoundException {
Code c = clmap.get(name);
if(c == null)
throw(new ClassNotFoundException("Could not find class " + name + " in resource (" + Resource.this + ")"));
return(defineClass(name, c.data, 0, c.data.length));
}
});
}
});
}
}
return(this.loader);
}
private void load() {
synchronized(CodeEntry.class) {
if(lpe != null)
return;
ClassLoader loader = loader(false);
lpe = new TreeMap<String, Class<?>>();
try {
for(Map.Entry<String, String> e : pe.entrySet()) {
String name = e.getKey();
String clnm = e.getValue();
Class<?> cl = loader.loadClass(clnm);
lpe.put(name, cl);
}
} catch(ClassNotFoundException e) {
throw(new LoadException(e, Resource.this));
}
}
}
public <T> Class<? extends T> getcl(Class<T> cl, boolean fail) {
load();
PublishedCode entry = cl.getAnnotation(PublishedCode.class);
if(entry == null)
throw(new RuntimeException("Tried to fetch non-published res-loaded class " + cl.getName() + " from " + Resource.this.name));
Class<?> acl;
synchronized(lpe) {
if((acl = lpe.get(entry.name())) == null) {
if(fail)
throw(new RuntimeException("Tried to fetch non-present res-loaded class " + cl.getName() + " from " + Resource.this.name));
return(null);
}
}
return(acl.asSubclass(cl));
}
public <T> Class<? extends T> getcl(Class<T> cl) {
return(getcl(cl, true));
}
public <T> T get(Class<T> cl, boolean fail) {
load();
PublishedCode entry = cl.getAnnotation(PublishedCode.class);
if(entry == null)
throw(new RuntimeException("Tried to fetch non-published res-loaded class " + cl.getName() + " from " + Resource.this.name));
Class<?> acl;
synchronized(lpe) {
if((acl = lpe.get(entry.name())) == null) {
if(fail)
throw(new RuntimeException("Tried to fetch non-present res-loaded class " + cl.getName() + " from " + Resource.this.name));
return(null);
}
}
try {
synchronized(ipe) {
Object pinst;
if((pinst = ipe.get(acl)) != null) {
return(cl.cast(pinst));
} else {
T inst;
Object rinst;
if(entry.instancer() != PublishedCode.Instancer.class)
rinst = entry.instancer().newInstance().make(acl);
else
rinst = acl.newInstance();
try {
inst = cl.cast(rinst);
} catch(ClassCastException e) {
throw(new ClassCastException("Published class in " + Resource.this.name + " is not of type " + cl));
}
ipe.put(acl, inst);
return(inst);
}
}
} catch(InstantiationException e) {
throw(new RuntimeException(e));
} catch(IllegalAccessException e) {
throw(new RuntimeException(e));
}
}
public <T> T get(Class<T> cl) {
return(get(cl, true));
}
}
@LayerName("audio")
public class Audio extends Layer implements IDLayer<String> {
transient public byte[] coded;
public final String id;
public double bvol = 1.0;
public Audio(byte[] coded, String id) {
this.coded = coded;
this.id = id.intern();
}
public Audio(byte[] buf) {
this(buf, "cl");
}
public void init() {}
public InputStream pcmstream() {
try {
return(new dolda.xiphutil.VorbisStream(new ByteArrayInputStream(coded)).pcmstream());
} catch(IOException e) {
throw(new RuntimeException(e));
}
}
public String layerid() {
return(id);
}
}
@LayerName("audio2")
public static class Audio2 implements LayerFactory<Audio> {
public Audio cons(Resource res, byte[] buf) {
int[] off = {0};
int ver = buf[off[0]++];
if((ver == 1) || (ver == 2)) {
String id = Utils.strd(buf, off);
double bvol = 1.0;
if(ver == 2) {
bvol = Utils.uint16d(buf, off[0]) / 1000.0; off[0] += 2;
}
byte[] data = new byte[buf.length - off[0]];
System.arraycopy(buf, off[0], data, 0, buf.length - off[0]);
Audio ret = res.new Audio(data, id);
ret.bvol = bvol;
return(ret);
} else {
throw(new LoadException("Unknown audio layer version: " + ver, res));
}
}
}
@LayerName("midi")
public class Music extends Resource.Layer {
transient javax.sound.midi.Sequence seq;
public Music(byte[] buf) {
try {
seq = javax.sound.midi.MidiSystem.getSequence(new ByteArrayInputStream(buf));
} catch(javax.sound.midi.InvalidMidiDataException e) {
throw(new LoadException("Invalid MIDI data", Resource.this));
} catch(IOException e) {
throw(new LoadException(e, Resource.this));
}
}
public void init() {}
}
@LayerName("font")
public class Font extends Layer {
public transient final java.awt.Font font;
public Font(byte[] buf) {
int[] off = {0};
int ver = buf[off[0]++];
if(ver == 1) {
int type = buf[off[0]++];
if(type == 0) {
try {
this.font = java.awt.Font.createFont(java.awt.Font.TRUETYPE_FONT, new ByteArrayInputStream(buf, off[0], buf.length - off[0]));
} catch(Exception e) {
throw(new RuntimeException(e));
}
} else {
throw(new LoadException("Unknown font type: " + type, Resource.this));
}
} else {
throw(new LoadException("Unknown font layer version: " + ver, Resource.this));
}
}
public void init() {}
}
private void readall(InputStream in, byte[] buf) throws IOException {
int ret, off = 0;
while(off < buf.length) {
ret = in.read(buf, off, buf.length - off);
if(ret < 0)
throw(new LoadException("Incomplete resource at " + name, this));
off += ret;
}
}
public <L extends Layer> Collection<L> layers(final Class<L> cl, boolean th) {
if(loading && th)
throw(new Loading(this));
checkerr();
return(new AbstractCollection<L>() {
public int size() {
int s = 0;
for(L l : this)
s++;
return(s);
}
public Iterator<L> iterator() {
return(new Iterator<L>() {
Iterator<Layer> i = layers.iterator();
L c = n();
private L n() {
while(i.hasNext()) {
Layer l = i.next();
if(cl.isInstance(l))
return(cl.cast(l));
}
return(null);
}
public boolean hasNext() {
return(c != null);
}
public L next() {
L ret = c;
if(ret == null)
throw(new NoSuchElementException());
c = n();
return(ret);
}
public void remove() {
throw(new UnsupportedOperationException());
}
});
}
});
}
public <L extends Layer> Collection<L> layers(Class<L> cl) {
return(layers(cl, true));
}
public <L extends Layer> L layer(Class<L> cl, boolean th) {
if(loading && th)
throw(new Loading(this));
checkerr();
for(Layer l : layers) {
if(cl.isInstance(l))
return(cl.cast(l));
}
return(null);
}
public <L extends Layer> L layer(Class<L> cl) {
return(layer(cl, true));
}
public <I, L extends IDLayer<I>> L layer(Class<L> cl, I id) {
if(loading)
throw(new Loading(this));
checkerr();
for(Layer l : layers) {
if(cl.isInstance(l)) {
L ll = cl.cast(l);
if(ll.layerid().equals(id))
return(ll);
}
}
return(null);
}
public int compareTo(Resource other) {
checkerr();
int nc = name.compareTo(other.name);
if(nc != 0)
return(nc);
if(ver != other.ver)
return(ver - other.ver);
if(other != this)
throw(new RuntimeException("Resource identity crisis!"));
return(0);
}
public boolean equals(Object other) {
if(!(other instanceof Resource))
return(false);
Resource o = (Resource)other;
return(o.name.equals(this.name) && (o.ver == this.ver));
}
private void load(InputStream in) throws IOException {
String sig = "Haven Resource 1";
byte buf[] = new byte[sig.length()];
readall(in, buf);
if(!sig.equals(new String(buf)))
throw(new LoadException("Invalid res signature", this));
buf = new byte[2];
readall(in, buf);
int ver = Utils.uint16d(buf, 0);
List<Layer> layers = new LinkedList<Layer>();
if(this.ver == -1) {
this.ver = ver;
} else {
if(ver != this.ver)
throw(new LoadException("Wrong res version (" + ver + " != " + this.ver + ")", this));
}
outer: while(true) {
StringBuilder tbuf = new StringBuilder();
while(true) {
byte bb;
int ib;
if((ib = in.read()) == -1) {
if(tbuf.length() == 0)
break outer;
throw(new LoadException("Incomplete resource at " + name, this));
}
bb = (byte)ib;
if(bb == 0)
break;
tbuf.append((char)bb);
}
buf = new byte[4];
readall(in, buf);
int len = Utils.int32d(buf, 0);
buf = new byte[len];
readall(in, buf);
LayerFactory<?> lc = ltypes.get(tbuf.toString());
if(lc == null)
continue;
layers.add(lc.cons(this, buf));
}
this.layers = layers;
for(Layer l : layers)
l.init();
}
public Indir<Resource> indir() {
if(indir != null)
return(indir);
indir = new Indir<Resource>() {
public Resource res = Resource.this;
public Resource get() {
if(loading)
throw(new Loading(Resource.this));
return(Resource.this);
}
public void set(Resource r) {
throw(new RuntimeException());
}
public int compareTo(Indir<Resource> x) {
return(Resource.this.compareTo(this.getClass().cast(x).res));
}
};
return(indir);
}
public void checkerr() {
if(!loading && (error != null))
throw(new RuntimeException("Delayed error in resource " + name + " (v" + ver + "), from " + source, error));
}
public int priority() {
return(prio);
}
public static BufferedImage loadimg(String name) {
Resource res = load(name);
res.loadwait();
return(res.layer(imgc).img);
}
public static Tex loadtex(String name) {
Resource res = load(name);
res.loadwait();
return(res.layer(imgc).tex());
}
public String toString() {
return(name + "(v" + ver + ")");
}
public static void loadlist(InputStream list, int prio) throws IOException {
BufferedReader in = new BufferedReader(new InputStreamReader(list, "us-ascii"));
String ln;
while((ln = in.readLine()) != null) {
int pos = ln.indexOf(':');
if(pos < 0)
continue;
String nm = ln.substring(0, pos);
int ver;
try {
ver = Integer.parseInt(ln.substring(pos + 1));
} catch(NumberFormatException e) {
continue;
}
try {
load(nm, ver, prio);
} catch(RuntimeException e) {
}
}
in.close();
}
public static void dumplist(Collection<Resource> list, Writer dest) {
PrintWriter out = new PrintWriter(dest);
List<Resource> sorted = new ArrayList<Resource>(list);
Collections.sort(sorted);
for(Resource res : sorted) {
if(res.loading)
continue;
out.println(res.name + ":" + res.ver);
}
}
public static void updateloadlist(File file) throws Exception {
BufferedReader r = new BufferedReader(new FileReader(file));
Map<String, Integer> orig = new HashMap<String, Integer>();
String ln;
while((ln = r.readLine()) != null) {
int pos = ln.indexOf(':');
if(pos < 0) {
System.err.println("Weird line: " + ln);
continue;
}
String nm = ln.substring(0, pos);
int ver = Integer.parseInt(ln.substring(pos + 1));
orig.put(nm, ver);
}
r.close();
for(String nm : orig.keySet())
load(nm);
while(true) {
int d = qdepth();
if(d == 0)
break;
System.out.print("\033[1GLoading... " + d + "\033[K");
Thread.sleep(500);
}
System.out.println();
Collection<Resource> cur = new LinkedList<Resource>();
for(Map.Entry<String, Integer> e : orig.entrySet()) {
String nm = e.getKey();
int ver = e.getValue();
Resource res = load(nm);
res.loadwait();
res.checkerr();
if(res.ver != ver)
System.out.println(nm + ": " + ver + " -> " + res.ver);
cur.add(res);
}
Writer w = new OutputStreamWriter(new FileOutputStream(file), "UTF-8");
try {
dumplist(cur, w);
} finally {
w.close();
}
}
public static void main(String[] args) throws Exception {
String cmd = args[0].intern();
if(cmd == "update") {
updateloadlist(new File(args[1]));
}
}
}