package tc.oc.parse; import java.io.IOException; import java.nio.file.Path; import java.util.Optional; import java.util.function.Consumer; import java.util.logging.Level; import javax.inject.Inject; import javax.xml.parsers.DocumentBuilder; import com.google.inject.assistedinject.Assisted; import org.xml.sax.SAXException; import tc.oc.commons.bukkit.logging.MapdevLogger; import tc.oc.minecraft.scheduler.MainThreadExecutor; import tc.oc.file.PathWatcher; import tc.oc.file.PathWatcherHandle; import tc.oc.file.PathWatcherService; import tc.oc.parse.xml.DocumentParser; /** * Watches a single file that is parseable with a bound {@link DocumentParser<T>}. * * When the file changes, it is parsed and passed to the given {@link Consumer}. * If the file is removed, the consumer is called with an empty value. */ public class DocumentWatcher<T> implements PathWatcher { public interface Factory<T> { DocumentWatcher<T> create(Path path, Consumer<Optional<T>> callback); } private final DocumentBuilder builder; private final DocumentParser<T> parser; private final MapdevLogger mapdevLogger; private final Consumer<Optional<T>> callback; private final PathWatcherHandle handle; @Inject DocumentWatcher(@Assisted Path path, @Assisted Consumer<Optional<T>> callback, DocumentBuilder builder, DocumentParser<T> parser, MapdevLogger mapdevLogger, PathWatcherService watcherService, MainThreadExecutor executor) throws IOException { this.builder = builder; this.parser = parser; this.mapdevLogger = mapdevLogger; this.callback = callback; this.handle = watcherService.watch(path, executor, this); } public void cancel() { if(handle != null) { handle.cancel(); callback.accept(Optional.empty()); } } @Override public void fileCreated(Path path) { fileModified(path); } @Override public void fileModified(Path path) { final T document; try { document = parser.parse(builder.parse(path.toFile())); } catch(SAXException | IOException | ParseException e) { mapdevLogger.log(Level.SEVERE, "Failed to load " + path, e); return; } callback.accept(Optional.of(document)); } @Override public void fileDeleted(Path path) { callback.accept(Optional.empty()); } }