Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reintroduce include statements #1028

Merged
merged 5 commits into from
Sep 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions core/src/main/java/tc/oc/pgm/PGMConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import java.util.Map;
import java.util.TreeSet;
import java.util.logging.Level;
import javax.annotation.Nullable;
import net.kyori.adventure.text.Component;
import org.bukkit.ChatColor;
import org.bukkit.configuration.ConfigurationSection;
Expand Down Expand Up @@ -56,6 +57,7 @@ public final class PGMConfig implements Config {
// map.*
private final List<MapSourceFactory> mapSourceFactories;
private final String mapPoolFile;
private final String includesDirectory;

// countdown.*
private final Duration startTime;
Expand Down Expand Up @@ -159,6 +161,11 @@ public final class PGMConfig implements Config {
mapPoolFile == null || mapPoolFile.isEmpty()
? null
: new File(dataFolder, mapPoolFile).getAbsolutePath();
final String includesDirectory = config.getString("map.includes");
this.includesDirectory =
includesDirectory == null || includesDirectory.isEmpty()
? null
: new File(dataFolder, includesDirectory).getAbsolutePath();

this.startTime = parseDuration(config.getString("countdown.start", "30s"));
this.huddleTime = parseDuration(config.getString("countdown.huddle", "0s"));
Expand Down Expand Up @@ -466,6 +473,11 @@ public String getMapPoolFile() {
return mapPoolFile;
}

@Override
public @Nullable String getIncludesDirectory() {
return includesDirectory;
}

@Override
public Duration getStartTime() {
return startTime;
Expand Down
7 changes: 6 additions & 1 deletion core/src/main/java/tc/oc/pgm/PGMPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import tc.oc.pgm.api.map.MapOrder;
import tc.oc.pgm.api.map.exception.MapException;
import tc.oc.pgm.api.map.factory.MapSourceFactory;
import tc.oc.pgm.api.map.includes.MapIncludeProcessor;
import tc.oc.pgm.api.match.Match;
import tc.oc.pgm.api.match.MatchManager;
import tc.oc.pgm.api.module.Module;
Expand All @@ -57,6 +58,7 @@
import tc.oc.pgm.listeners.ServerPingDataListener;
import tc.oc.pgm.listeners.WorldProblemListener;
import tc.oc.pgm.map.MapLibraryImpl;
import tc.oc.pgm.map.includes.MapIncludeProcessorImpl;
import tc.oc.pgm.match.MatchManagerImpl;
import tc.oc.pgm.match.NoopVanishManager;
import tc.oc.pgm.namedecorations.ConfigDecorationProvider;
Expand Down Expand Up @@ -85,6 +87,7 @@ public class PGMPlugin extends JavaPlugin implements PGM, Listener {
private Logger gameLogger;
private Datastore datastore;
private MapLibrary mapLibrary;
private MapIncludeProcessor mapIncludeProcessor;
private List<MapSourceFactory> mapSourceFactories;
private MatchManager matchManager;
private MatchTabManager matchTabManager;
Expand Down Expand Up @@ -134,7 +137,8 @@ public void onEnable() {
asyncExecutorService = new BukkitExecutorService(this, true);

mapSourceFactories = new ArrayList<>();
mapLibrary = new MapLibraryImpl(gameLogger, mapSourceFactories);
mapIncludeProcessor = new MapIncludeProcessorImpl(gameLogger);
mapLibrary = new MapLibraryImpl(gameLogger, mapSourceFactories, mapIncludeProcessor);

saveDefaultConfig(); // Writes a config file, if one does not exist.
reloadConfig(); // Populates "this.config", if there is an error, will be null
Expand Down Expand Up @@ -261,6 +265,7 @@ public void reloadConfig() {

mapSourceFactories.clear();
mapSourceFactories.addAll(config.getMapSourceFactories());
mapIncludeProcessor.reload(config);

if (mapOrder != null) {
mapOrder.reload();
Expand Down
8 changes: 8 additions & 0 deletions core/src/main/java/tc/oc/pgm/api/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,14 @@ public interface Config {
@Nullable
String getMapPoolFile();

/**
* Gets a path to the includes directory.
*
* @return A path to the includes directory, or null for none.
*/
@Nullable
String getIncludesDirectory();

/**
* Gets a duration to wait before starting a match.
*
Expand Down
8 changes: 8 additions & 0 deletions core/src/main/java/tc/oc/pgm/api/map/MapLibrary.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -45,4 +46,11 @@ public interface MapLibrary {
* @return A {@link MapContext}.
*/
CompletableFuture<MapContext> loadExistingMap(String id);

/**
* Get the {@link MapIncludeProcessor}.
*
* @return A {@link MapIncludeProcessor}
*/
MapIncludeProcessor getIncludeProcessor();
}
11 changes: 11 additions & 0 deletions core/src/main/java/tc/oc/pgm/api/map/MapSource.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.MapInclude;

/** A source where {@link MapInfo} documents and files are downloaded. */
public interface MapSource {
Expand Down Expand Up @@ -38,4 +39,14 @@ public interface MapSource {
* @throws MapMissingException If the document can no longer be found.
*/
boolean checkForUpdates() throws MapMissingException;

/**
* Adds an associated {@link MapInclude}
*
* @param include The {@link MapInclude}
*/
void addMapInclude(MapInclude include);

/** Remove all associated {@link MapInclude}, used when reloading document. */
void clearIncludes();
}
37 changes: 37 additions & 0 deletions core/src/main/java/tc/oc/pgm/api/map/includes/MapInclude.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package tc.oc.pgm.api.map.includes;

import java.util.Collection;
import org.jdom2.Content;

/** Represents a snippet of XML that can be referenced for reuse * */
public interface MapInclude {

/**
* Get a unique id which identifies this MapInclude.
*
* @return A unique id
*/
String getId();

/**
* Get the system file time from when this MapInclude file was last modified.
*
* @return Time of last file modification
*/
long getLastModified();

/**
* 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);

/**
* Get a collection of {@link Content} which can be merged into an existing {@link Document}
*
* @return a collection of {@link Content}
*/
Collection<Content> getContent();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package tc.oc.pgm.api.map.includes;

import java.util.Collection;
import org.jdom2.Document;
import tc.oc.pgm.api.Config;
import tc.oc.pgm.util.xml.InvalidXMLException;

/** A processor to determine which {@link MapInclude}s should be included when loading a map * */
public interface MapIncludeProcessor {

/**
* Process the given {@link Document} and return a collection of {@link MapInclude}s.
*
* @param document A map document
* @return A collection of map includes, collection will be empty if none are found.
* @throws InvalidXMLException If the given document is not found or able to be parsed.
*/
Collection<MapInclude> 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.
*
* @param config A configuration file.
*/
void reload(Config config);
}
20 changes: 19 additions & 1 deletion core/src/main/java/tc/oc/pgm/map/MapFactoryImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.logging.Logger;
import org.jdom2.Document;
import org.jdom2.JDOMException;
Expand All @@ -19,6 +20,8 @@
import tc.oc.pgm.api.map.exception.MapMissingException;
import tc.oc.pgm.api.map.factory.MapFactory;
import tc.oc.pgm.api.map.factory.MapModuleFactory;
import tc.oc.pgm.api.map.includes.MapInclude;
import tc.oc.pgm.api.map.includes.MapIncludeProcessor;
import tc.oc.pgm.api.module.ModuleGraph;
import tc.oc.pgm.api.module.exception.ModuleLoadException;
import tc.oc.pgm.features.FeatureDefinitionContext;
Expand Down Expand Up @@ -49,17 +52,19 @@ public class MapFactoryImpl extends ModuleGraph<MapModule, MapModuleFactory<? ex

private final Logger logger;
private final MapSource source;
private final MapIncludeProcessor includes;
private Document document;
private MapInfo info;
private RegionParser regions;
private FilterParser filters;
private KitParser kits;
private FeatureDefinitionContext features;

public MapFactoryImpl(Logger logger, MapSource source) {
public MapFactoryImpl(Logger logger, MapSource source, MapIncludeProcessor includes) {
super(Modules.MAP); // Do not copy to avoid N copies of the factories
this.logger = ClassLogger.get(checkNotNull(logger), getClass(), checkNotNull(source).getId());
this.source = source;
this.includes = includes;
}

@Override
Expand All @@ -71,17 +76,30 @@ protected MapModule createModule(MapModuleFactory factory) throws ModuleLoadExce
}
}

private void storeInclude(MapInclude include) {
this.source.addMapInclude(include);
}

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());
}

// Check for any included map sources, appending them to the document if present
Collection<MapInclude> mapIncludes = includes.getMapIncludes(document);
for (MapInclude include : mapIncludes) {
document.getRootElement().addContent(0, include.getContent());
storeInclude(include);
}

info = new MapInfoImpl(document.getRootElement());
}

Expand Down
13 changes: 11 additions & 2 deletions core/src/main/java/tc/oc/pgm/map/MapLibraryImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import tc.oc.pgm.api.map.exception.MapMissingException;
import tc.oc.pgm.api.map.factory.MapFactory;
import tc.oc.pgm.api.map.factory.MapSourceFactory;
import tc.oc.pgm.api.map.includes.MapIncludeProcessor;
import tc.oc.pgm.util.StringUtils;
import tc.oc.pgm.util.UsernameResolver;

Expand All @@ -37,6 +38,7 @@ public class MapLibraryImpl implements MapLibrary {
private final List<MapSourceFactory> factories;
private final SortedMap<String, MapEntry> maps;
private final Set<MapSource> failed;
private final MapIncludeProcessor includes;

private static class MapEntry {
private final MapSource source;
Expand All @@ -50,11 +52,13 @@ private MapEntry(MapSource source, MapInfo info, MapContext context) {
}
}

public MapLibraryImpl(Logger logger, List<MapSourceFactory> factories) {
public MapLibraryImpl(
Logger logger, List<MapSourceFactory> factories, MapIncludeProcessor includes) {
this.logger = checkNotNull(logger); // Logger should be visible in-game
this.factories = Collections.synchronizedList(checkNotNull(factories));
this.maps = Collections.synchronizedSortedMap(new ConcurrentSkipListMap<>());
this.failed = Collections.synchronizedSet(new HashSet<>());
this.includes = includes;
}

@Override
Expand All @@ -79,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);
}
Expand Down Expand Up @@ -187,7 +196,7 @@ public CompletableFuture<MapContext> loadExistingMap(String id) {

private MapContext loadMap(MapSource source, @Nullable String mapId) throws MapException {
final MapContext context;
try (final MapFactory factory = new MapFactoryImpl(logger, source)) {
try (final MapFactory factory = new MapFactoryImpl(logger, source, includes)) {
context = factory.load();
} catch (MapMissingException e) {
failed.remove(source);
Expand Down
59 changes: 59 additions & 0 deletions core/src/main/java/tc/oc/pgm/map/includes/MapIncludeImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package tc.oc.pgm.map.includes;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
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;
import tc.oc.pgm.api.map.exception.MapMissingException;
import tc.oc.pgm.api.map.includes.MapInclude;

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 {
InputStream fileStream = new FileInputStream(file);
this.id = file.getName().replace(".xml", "");
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());
}
}

@Override
public String getId() {
return id;
}

@Override
public Collection<Content> getContent() {
return source.getRootElement().cloneContent();
}

@Override
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();
}

@Override
public boolean hasBeenModified(long time) {
return time > lastModified.get();
}
}
Loading