diff --git a/src/java/com/threerings/resource/ResourceManager.java b/src/java/com/threerings/resource/ResourceManager.java
index 8ab2d41b..32fee95d 100644
--- a/src/java/com/threerings/resource/ResourceManager.java
+++ b/src/java/com/threerings/resource/ResourceManager.java
@@ -34,7 +34,9 @@ import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
@@ -47,10 +49,14 @@ import javax.imageio.ImageIO;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.MemoryCacheImageInputStream;
+import com.google.common.collect.Maps;
+
import com.samskivert.io.StreamUtil;
import com.samskivert.net.PathUtil;
+import com.samskivert.util.ObserverList;
import com.samskivert.util.ResultListener;
import com.samskivert.util.StringUtil;
+import com.samskivert.util.WeakObserverList;
import static com.threerings.resource.Log.log;
@@ -141,6 +147,21 @@ public class ResourceManager
protected InitObserver _obs;
}
+ /**
+ * Notifies observers of modifications to resources (as indicated by a change to their
+ * {@link File#lastModified} property).
+ */
+ public interface ModificationObserver
+ {
+ /**
+ * Notes that a resource has been modified.
+ *
+ * @param path the path of the resource.
+ * @param lastModified the resource's new timestamp.
+ */
+ public void resourceModified (String path, long lastModified);
+ }
+
/**
* Constructs a resource manager which will load resources via the classloader, prepending
* resourceRoot to their path.
@@ -702,6 +723,52 @@ public class ResourceManager
return _sets.get(name);
}
+ /**
+ * Adds a modification observer for the specified resource. Note that only a weak reference to
+ * the observer will be retained, and thus this will not prevent the observer from being
+ * garbage-collected.
+ */
+ public void addModificationObserver (String path, ModificationObserver obs)
+ {
+ ObservedResource resource = _observed.get(path);
+ if (resource == null) {
+ File file = getResourceFile(path);
+ if (file == null) {
+ return; // only resource files will ever be modified
+ }
+ _observed.put(path, resource = new ObservedResource(file));
+ }
+ resource.observers.add(obs);
+ }
+
+ /**
+ * Removes a modification observer from the list maintained for the specified resource.
+ */
+ public void removeModificationObserver (String path, ModificationObserver obs)
+ {
+ ObservedResource resource = _observed.get(path);
+ if (resource != null) {
+ resource.observers.remove(obs);
+ }
+ }
+
+ /**
+ * Checks all observed resources for changes to their {@link File#lastModified} properties,
+ * notifying their listeners if the files have been modified since the last call to this
+ * method.
+ */
+ public void checkForModifications ()
+ {
+ for (Iterator> it = _observed.entrySet().iterator();
+ it.hasNext(); ) {
+ Map.Entry entry = it.next();
+ ObservedResource resource = entry.getValue();
+ if (resource.checkForModification(entry.getKey())) {
+ it.remove();
+ }
+ }
+ }
+
/**
* Loads the configuration properties for our resource sets.
*/
@@ -914,6 +981,65 @@ public class ResourceManager
protected InitObserver _obs;
}
+ /** Contains the state of an observed file resource. */
+ protected static class ObservedResource
+ {
+ /** The observers listening for modifications to this resource. */
+ public WeakObserverList observers =
+ WeakObserverList.newFastUnsafe();
+
+ public ObservedResource (File file)
+ {
+ _file = file;
+ _lastModified = file.lastModified();
+ }
+
+ /**
+ * Checks for a modification to the observed resource, notifying the observers if
+ * one is detected.
+ *
+ * @param path the path of the resource (to forward to observers).
+ * @return true if the list of observers is empty and the resource should be
+ * removed from the observed list, false if it should remain in the list.
+ */
+ public boolean checkForModification (String path)
+ {
+ long newLastModified = _file.lastModified();
+ if (newLastModified > _lastModified) {
+ _resourceModifiedOp.init(path, _lastModified = newLastModified);
+ observers.apply(_resourceModifiedOp);
+ } else {
+ // TODO: uncomment when we get this method into samskivert
+ // observers.prune();
+ }
+ return observers.isEmpty();
+ }
+
+ protected File _file;
+ protected long _lastModified;
+ }
+
+ /** An observer op that calls {@link ModificationObserver#resourceModified}. */
+ protected static class ResourceModifiedOp
+ implements ObserverList.ObserverOp
+ {
+ public void init (String path, long lastModified)
+ {
+ _path = path;
+ _lastModified = lastModified;
+ }
+
+ // documentation inherited from interface ObserverOp
+ public boolean apply (ModificationObserver obs)
+ {
+ obs.resourceModified(_path, _lastModified);
+ return true;
+ }
+
+ protected String _path;
+ protected long _lastModified;
+ }
+
/** The classloader we use for classpath-based resource loading. */
protected ClassLoader _loader;
@@ -934,11 +1060,17 @@ public class ResourceManager
protected ResourceBundle[] _default = new ResourceBundle[0];
/** A table of our resource sets. */
- protected HashMap _sets = new HashMap();
+ protected HashMap _sets = Maps.newHashMap();
/** Locale to search for locale-specific resources, if any. */
protected String _localePrefix = null;
+ /** Maps resource paths to observed file resources. */
+ protected HashMap _observed = Maps.newHashMap();
+
+ /** A reusable instance of {@link ResourceModifiedOp}. */
+ protected static ResourceModifiedOp _resourceModifiedOp = new ResourceModifiedOp();
+
/** The prefix of configuration entries that describe a resource set. */
protected static final String RESOURCE_SET_PREFIX = "resource.set.";