From 42d9abd3500c9b2963ef9f04364fa660e28caab9 Mon Sep 17 00:00:00 2001 From: applenick Date: Sun, 17 Jul 2022 00:14:29 -0700 Subject: [PATCH] Handle detecting changes to include files Signed-off-by: applenick --- .../java/tc/oc/pgm/api/map/MapLibrary.java | 8 +++++ .../java/tc/oc/pgm/api/map/MapSource.java | 11 +++++++ .../oc/pgm/api/map/includes/MapInclude.java | 7 ++++ .../api/map/includes/MapIncludeProcessor.java | 17 +++++----- .../api/map/includes/StoredMapInclude.java | 22 +++++++++++++ .../java/tc/oc/pgm/map/MapFactoryImpl.java | 13 +++++--- .../java/tc/oc/pgm/map/MapLibraryImpl.java | 5 +++ .../oc/pgm/map/includes/MapIncludeImpl.java | 9 ++++++ .../map/includes/MapIncludeProcessorImpl.java | 24 ++++++++------ .../map/includes/StoredMapIncludeImpl.java | 25 +++++++++++++++ .../map/source/SystemMapSourceFactory.java | 32 ++++++++++++++++++- 11 files changed, 148 insertions(+), 25 deletions(-) create mode 100644 core/src/main/java/tc/oc/pgm/api/map/includes/StoredMapInclude.java create mode 100644 core/src/main/java/tc/oc/pgm/map/includes/StoredMapIncludeImpl.java diff --git a/core/src/main/java/tc/oc/pgm/api/map/MapLibrary.java b/core/src/main/java/tc/oc/pgm/api/map/MapLibrary.java index c985038b35..5161d8742b 100644 --- a/core/src/main/java/tc/oc/pgm/api/map/MapLibrary.java +++ b/core/src/main/java/tc/oc/pgm/api/map/MapLibrary.java @@ -3,6 +3,7 @@ import java.util.Iterator; import java.util.concurrent.CompletableFuture; import javax.annotation.Nullable; +import tc.oc.pgm.api.map.includes.MapIncludeProcessor; /** A library of {@link MapInfo}s and {@link MapContext}s. */ public interface MapLibrary { @@ -45,4 +46,11 @@ public interface MapLibrary { * @return A {@link MapContext}. */ CompletableFuture loadExistingMap(String id); + + /** + * Get the {@link MapIncludeProcessor}. + * + * @return A {@link MapIncludeProcessor} + */ + MapIncludeProcessor getIncludeProcessor(); } diff --git a/core/src/main/java/tc/oc/pgm/api/map/MapSource.java b/core/src/main/java/tc/oc/pgm/api/map/MapSource.java index f65c4fd22c..a3e583e791 100644 --- a/core/src/main/java/tc/oc/pgm/api/map/MapSource.java +++ b/core/src/main/java/tc/oc/pgm/api/map/MapSource.java @@ -3,6 +3,7 @@ import java.io.File; import java.io.InputStream; import tc.oc.pgm.api.map.exception.MapMissingException; +import tc.oc.pgm.api.map.includes.StoredMapInclude; /** A source where {@link MapInfo} documents and files are downloaded. */ public interface MapSource { @@ -38,4 +39,14 @@ public interface MapSource { * @throws MapMissingException If the document can no longer be found. */ boolean checkForUpdates() throws MapMissingException; + + /** + * Adds a {@link StoredMapInclude} which holds information about a {@link MapInclude} + * + * @param include The {@link StoredMapInclude} + */ + void addMapInclude(StoredMapInclude include); + + /** Remove all associated {@link StoredMapInclude}, used when reloading document. */ + void clearIncludes(); } diff --git a/core/src/main/java/tc/oc/pgm/api/map/includes/MapInclude.java b/core/src/main/java/tc/oc/pgm/api/map/includes/MapInclude.java index c9cbcbd928..4447f71285 100644 --- a/core/src/main/java/tc/oc/pgm/api/map/includes/MapInclude.java +++ b/core/src/main/java/tc/oc/pgm/api/map/includes/MapInclude.java @@ -13,6 +13,13 @@ public interface MapInclude { */ String getId(); + /** + * Get the system file time from when this MapInclude file was last modified. + * + * @return Time of last file modification + */ + long getLastModified(); + /** * Get a collection of {@link Content} which can be merged into an existing {@link Document} * diff --git a/core/src/main/java/tc/oc/pgm/api/map/includes/MapIncludeProcessor.java b/core/src/main/java/tc/oc/pgm/api/map/includes/MapIncludeProcessor.java index 9955526691..58d5658154 100644 --- a/core/src/main/java/tc/oc/pgm/api/map/includes/MapIncludeProcessor.java +++ b/core/src/main/java/tc/oc/pgm/api/map/includes/MapIncludeProcessor.java @@ -1,7 +1,6 @@ package tc.oc.pgm.api.map.includes; import java.util.Collection; -import javax.annotation.Nullable; import org.jdom2.Document; import tc.oc.pgm.api.Config; import tc.oc.pgm.util.xml.InvalidXMLException; @@ -9,14 +8,6 @@ /** A processor to determine which {@link MapInclude}s should be included when loading a map * */ public interface MapIncludeProcessor { - /** - * Get a {@link MapInclude} which should be included on all maps or null if none. - * - * @return a {@link MapInclude} - */ - @Nullable - MapInclude getGlobalInclude(); - /** * Process the given {@link Document} and return a collection of {@link MapInclude}s. * @@ -26,6 +17,14 @@ public interface MapIncludeProcessor { */ Collection getMapIncludes(Document document) throws InvalidXMLException; + /** + * Get a {@link MapInclude} by its id + * + * @param includeId ID of the map include + * @return A {@link MapInclude} + */ + MapInclude getMapIncludeById(String includeId); + /** * Reload the processor to fetch new map includes or reload existing ones. * diff --git a/core/src/main/java/tc/oc/pgm/api/map/includes/StoredMapInclude.java b/core/src/main/java/tc/oc/pgm/api/map/includes/StoredMapInclude.java new file mode 100644 index 0000000000..d020446059 --- /dev/null +++ b/core/src/main/java/tc/oc/pgm/api/map/includes/StoredMapInclude.java @@ -0,0 +1,22 @@ +package tc.oc.pgm.api.map.includes; + +/** + * A snapshot of {@link MapInclude} info, used to determine when include files have been updated. * + */ +public interface StoredMapInclude { + + /** + * Gets the unique include id, used to reference a {@link MapInclude}. + * + * @return A unique include id + */ + String getIncludeId(); + + /** + * Gets whether the associated {@link MapInclude} files have changed since last loading. + * + * @param time The current system time + * @return True if given time is newer than last modified time + */ + boolean hasBeenModified(long time); +} diff --git a/core/src/main/java/tc/oc/pgm/map/MapFactoryImpl.java b/core/src/main/java/tc/oc/pgm/map/MapFactoryImpl.java index 6ba13b7bd4..e34d17ab9b 100644 --- a/core/src/main/java/tc/oc/pgm/map/MapFactoryImpl.java +++ b/core/src/main/java/tc/oc/pgm/map/MapFactoryImpl.java @@ -31,6 +31,7 @@ import tc.oc.pgm.kits.FeatureKitParser; import tc.oc.pgm.kits.KitParser; import tc.oc.pgm.kits.LegacyKitParser; +import tc.oc.pgm.map.includes.StoredMapIncludeImpl; import tc.oc.pgm.regions.FeatureRegionParser; import tc.oc.pgm.regions.LegacyRegionParser; import tc.oc.pgm.regions.RegionParser; @@ -76,26 +77,28 @@ protected MapModule createModule(MapModuleFactory factory) throws ModuleLoadExce } } + private void storeInclude(MapInclude include) { + this.source.addMapInclude(new StoredMapIncludeImpl(include.getId(), include.getLastModified())); + } + private void preLoad() throws IOException, JDOMException, InvalidXMLException, MapMissingException { if (document != null && !source.checkForUpdates()) { return; // If a document is present and there are no updates, skip loading again } + source.clearIncludes(); + try (final InputStream stream = source.getDocument()) { document = DOCUMENT_FACTORY.get().build(stream); document.setBaseURI(source.getId()); } - // Add global include file to the document if present - if (includes.getGlobalInclude() != null) { - document.getRootElement().addContent(0, includes.getGlobalInclude().getContent()); - } - // Check for any included map sources, appending them to the document if present Collection mapIncludes = includes.getMapIncludes(document); for (MapInclude include : mapIncludes) { document.getRootElement().addContent(0, include.getContent()); + storeInclude(include); } info = new MapInfoImpl(document.getRootElement()); diff --git a/core/src/main/java/tc/oc/pgm/map/MapLibraryImpl.java b/core/src/main/java/tc/oc/pgm/map/MapLibraryImpl.java index 0e7eb53027..6df17ed67a 100644 --- a/core/src/main/java/tc/oc/pgm/map/MapLibraryImpl.java +++ b/core/src/main/java/tc/oc/pgm/map/MapLibraryImpl.java @@ -83,6 +83,11 @@ public long getSize() { return maps.size(); } + @Override + public MapIncludeProcessor getIncludeProcessor() { + return includes; + } + private void logMapError(MapException err) { logger.log(Level.WARNING, err.getMessage(), err); } diff --git a/core/src/main/java/tc/oc/pgm/map/includes/MapIncludeImpl.java b/core/src/main/java/tc/oc/pgm/map/includes/MapIncludeImpl.java index 9277520bbf..7896f56dc4 100644 --- a/core/src/main/java/tc/oc/pgm/map/includes/MapIncludeImpl.java +++ b/core/src/main/java/tc/oc/pgm/map/includes/MapIncludeImpl.java @@ -6,6 +6,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.Collection; +import java.util.concurrent.atomic.AtomicLong; import org.jdom2.Content; import org.jdom2.Document; import org.jdom2.JDOMException; @@ -16,6 +17,7 @@ public class MapIncludeImpl implements MapInclude { private final String id; private final Document source; + private final AtomicLong lastModified; public MapIncludeImpl(File file) throws MapMissingException, JDOMException, IOException { try { @@ -24,6 +26,8 @@ public MapIncludeImpl(File file) throws MapMissingException, JDOMException, IOEx this.source = MapIncludeProcessorImpl.DOCUMENT_FACTORY.get().build(fileStream); } catch (FileNotFoundException e) { throw new MapMissingException(file.getPath(), "Unable to read map include document", e); + } finally { + lastModified = new AtomicLong(file.lastModified()); } } @@ -42,4 +46,9 @@ public boolean equals(Object other) { if (other == null || !(other instanceof MapInclude)) return false; return ((MapInclude) other).getId().equalsIgnoreCase(getId()); } + + @Override + public long getLastModified() { + return lastModified.get(); + } } diff --git a/core/src/main/java/tc/oc/pgm/map/includes/MapIncludeProcessorImpl.java b/core/src/main/java/tc/oc/pgm/map/includes/MapIncludeProcessorImpl.java index 9ff3f80c12..6cd973a634 100644 --- a/core/src/main/java/tc/oc/pgm/map/includes/MapIncludeProcessorImpl.java +++ b/core/src/main/java/tc/oc/pgm/map/includes/MapIncludeProcessorImpl.java @@ -7,7 +7,6 @@ import java.util.List; import java.util.Set; import java.util.logging.Logger; -import javax.annotation.Nullable; import org.jdom2.Document; import org.jdom2.Element; import org.jdom2.JDOMException; @@ -38,22 +37,27 @@ public MapIncludeProcessorImpl(Logger logger) { this.includes = Sets.newHashSet(); } - @Nullable - private MapInclude getIncludeById(String id) { - return includes.stream() - .filter(include -> include.getId().equalsIgnoreCase(id)) - .findAny() - .orElse(null); + public MapInclude getGlobalInclude() { + return getMapIncludeById("global"); } @Override - public MapInclude getGlobalInclude() { - return getIncludeById("global"); + public MapInclude getMapIncludeById(String includeId) { + return includes.stream() + .filter(include -> include.getId().equalsIgnoreCase(includeId)) + .findAny() + .orElse(null); } @Override public Collection getMapIncludes(Document document) throws InvalidXMLException { Set mapIncludes = Sets.newHashSet(); + + // Always add global include if present + if (getGlobalInclude() != null) { + mapIncludes.add(getGlobalInclude()); + } + List elements = document.getRootElement().getChildren("include"); for (Element element : elements) { @@ -69,7 +73,7 @@ public Collection getMapIncludes(Document document) throws InvalidXM } String id = XMLUtils.getRequiredAttribute(element, "id").getValue(); - MapInclude include = getIncludeById(id); + MapInclude include = getMapIncludeById(id); if (include == null) throw new InvalidXMLException( "The provided include id '" + id + "' could not be found!", element); diff --git a/core/src/main/java/tc/oc/pgm/map/includes/StoredMapIncludeImpl.java b/core/src/main/java/tc/oc/pgm/map/includes/StoredMapIncludeImpl.java new file mode 100644 index 0000000000..42a824e460 --- /dev/null +++ b/core/src/main/java/tc/oc/pgm/map/includes/StoredMapIncludeImpl.java @@ -0,0 +1,25 @@ +package tc.oc.pgm.map.includes; + +import java.util.concurrent.atomic.AtomicLong; +import tc.oc.pgm.api.map.includes.StoredMapInclude; + +public class StoredMapIncludeImpl implements StoredMapInclude { + + private final String includeId; + private final AtomicLong lastModified; + + public StoredMapIncludeImpl(String includeId, long lastModified) { + this.includeId = includeId; + this.lastModified = new AtomicLong(lastModified); + } + + @Override + public String getIncludeId() { + return includeId; + } + + @Override + public boolean hasBeenModified(long time) { + return time > lastModified.get(); + } +} diff --git a/core/src/main/java/tc/oc/pgm/map/source/SystemMapSourceFactory.java b/core/src/main/java/tc/oc/pgm/map/source/SystemMapSourceFactory.java index 755ac89b01..18182139c0 100644 --- a/core/src/main/java/tc/oc/pgm/map/source/SystemMapSourceFactory.java +++ b/core/src/main/java/tc/oc/pgm/map/source/SystemMapSourceFactory.java @@ -2,6 +2,7 @@ import static com.google.common.base.Preconditions.checkNotNull; +import com.google.common.collect.Sets; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -10,11 +11,16 @@ import java.nio.file.FileVisitOption; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Set; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Stream; import org.apache.commons.lang3.builder.ToStringBuilder; +import tc.oc.pgm.api.PGM; import tc.oc.pgm.api.map.MapSource; import tc.oc.pgm.api.map.exception.MapMissingException; +import tc.oc.pgm.api.map.includes.MapInclude; +import tc.oc.pgm.api.map.includes.MapIncludeProcessor; +import tc.oc.pgm.api.map.includes.StoredMapInclude; import tc.oc.pgm.util.FileUtils; public class SystemMapSourceFactory extends PathMapSourceFactory { @@ -44,10 +50,14 @@ protected static class SystemMapSource implements MapSource { private final String dir; private final AtomicLong modified; + private final Set storedIncludes; + private final MapIncludeProcessor includes; private SystemMapSource(String dir) { this.dir = checkNotNull(dir); this.modified = new AtomicLong(-1); + this.storedIncludes = Sets.newHashSet(); + this.includes = PGM.get().getMapLibrary().getIncludeProcessor(); } private File getDirectory() throws MapMissingException { @@ -118,7 +128,7 @@ public boolean checkForUpdates() throws MapMissingException { final File file = getFile(); final long mod = modified.get(); - return mod > 0 && file.lastModified() > mod; + return (mod > 0 && file.lastModified() > mod) || checkForIncludeUpdates(); } @Override @@ -139,5 +149,25 @@ public String toString() { .append("modified", modified.get()) .build(); } + + @Override + public void addMapInclude(StoredMapInclude include) { + this.storedIncludes.add(include); + } + + @Override + public void clearIncludes() { + this.storedIncludes.clear(); + } + + private boolean checkForIncludeUpdates() { + for (StoredMapInclude stored : storedIncludes) { + MapInclude include = includes.getMapIncludeById(stored.getIncludeId()); + if (stored.hasBeenModified(include.getLastModified())) { + return true; + } + } + return false; + } } }