ProjectAres/PGM/src/main/java/tc/oc/pgm/map/MapDefinition.java

143 lines
4.3 KiB
Java

package tc.oc.pgm.map;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Provider;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.hash.HashCode;
import com.google.common.hash.Hashing;
import com.google.common.io.Files;
import com.google.inject.Injector;
import tc.oc.commons.core.exception.ExceptionHandler;
import tc.oc.pgm.map.inject.MapInjectionScope;
import tc.oc.pgm.module.ModuleLoadException;
import tc.oc.pgm.xml.UnsupportedMapProtocolException;
import static tc.oc.commons.core.inject.Injection.unwrappingExceptions;
/**
* Base class for an XML configured map.
*
* This class is responsible for parsing an XML file into a
* {@link MapModuleContext}, detecting changes that require a reload,
* and creation of a child {@link Injector} with bindings specific to
* the map.
*
* This class is relatively decoupled from the rest of PGM, so that it can
* potentially be used to integrate PGM features into other applications,
* such as the lobby, or a non-match based game, e.g. an MMO.
*
* Anything related specifically to matches should be in {@link PGMMap}.
*/
public class MapDefinition {
@Inject private MapConfiguration configuration;
@Inject private Provider<MapModuleContext> contextProvider;
@Inject private ExceptionHandler exceptionHandler;
@Inject private MapInjectionScope mapInjectionScope;
private MapLogger logger;
@Inject void init(MapLogger.Factory loggerFactory) {
this.logger = loggerFactory.create(this);
}
private final MapFolder folder;
protected @Nullable MapModuleContext context;
protected MapDefinition(MapFolder folder) {
this.folder = folder;
}
public MapLogger getLogger() {
return logger;
}
public MapFolder getFolder() {
return folder;
}
public Optional<String> getThumbnailUri() {
return getFolder().getThumbnailUri();
}
public String getDottedPath() {
return Joiner.on(".").join(getFolder().getRelativePath());
}
public String getName() {
return getFolder().getRelativePath().toString();
}
public boolean isLoaded() {
return context != null;
}
public MapModuleContext getContext() {
if(context == null) {
throw new IllegalStateException("Map is not loaded: " + this);
}
return context;
}
public boolean shouldReload() {
if(context == null) return true;
if(!configuration.autoReload()) return false;
if(context.loadedFiles().isEmpty()) return configuration.reloadWhenError();
try {
for(Map.Entry<Path, HashCode> loaded : context.loadedFiles().entrySet()) {
HashCode latest = Files.hash(loaded.getKey().toFile(), Hashing.sha256());
if(!latest.equals(loaded.getValue())) return true;
}
return false;
} catch (IOException e) {
return true;
}
}
public boolean reload() throws MapNotFoundException {
List<? extends ModuleLoadException> errors;
try {
final MapModuleContext newContext = mapInjectionScope.withNewStore(this, () -> {
final MapModuleContext context = unwrappingExceptions(ModuleLoadException.class, contextProvider);
context.load();
return context;
});
if(!newContext.hasErrors()) {
this.context = newContext;
return true;
}
errors = newContext.getErrors();
} catch(MapNotFoundException e) {
throw e;
} catch(UnsupportedMapProtocolException e) {
logger.warning("Skipping map with unsupported proto " + e.getProto());
errors = ImmutableList.of();
} catch(ModuleLoadException e) {
errors = ImmutableList.of(e);
} catch(Throwable e) {
exceptionHandler.handleException(e);
errors = ImmutableList.of(new ModuleLoadException("Internal error", e));
}
for(ModuleLoadException error : errors) {
logger.log(new MapLogRecord(this, error));
}
return false;
}
}