| Author | djarvis <email> |
|---|---|
| Date | 2016-12-17 23:10:42 GMT-0800 |
| Commit | 8d2ab3010db6d95af22e83096d21dbc2783edeb1 |
| Parent | 9cabd39 |
| import static com.scrivenvar.Constants.*; | ||
| import com.scrivenvar.service.Options; | ||
| +import com.scrivenvar.service.Snitch; | ||
| import com.scrivenvar.service.events.AlertService; | ||
| import com.scrivenvar.util.StageState; | ||
| stage.show(); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Stops the snitch service, if its running. | ||
| + */ | ||
| + @Override | ||
| + public void stop() { | ||
| + Services.load( Snitch.class ).stop(); | ||
| } | ||
| import com.scrivenvar.processors.VariableProcessor; | ||
| import com.scrivenvar.processors.XMLCaretInsertionProcessor; | ||
| +import com.scrivenvar.processors.XMLProcessor; | ||
| import com.scrivenvar.service.Options; | ||
| import com.scrivenvar.util.Action; | ||
| import javafx.collections.ListChangeListener.Change; | ||
| import javafx.collections.ObservableList; | ||
| -import javafx.event.Event; | ||
| +import static javafx.event.Event.fireEvent; | ||
| import javafx.scene.Node; | ||
| import javafx.scene.Scene; | ||
| import javafx.stage.Window; | ||
| import javafx.stage.WindowEvent; | ||
| +import static javafx.stage.WindowEvent.WINDOW_CLOSE_REQUEST; | ||
| /** | ||
| System.out.println( "Tab File: " + path ); | ||
| } | ||
| - | ||
| + | ||
| final HTMLPreviewPane preview = getPreviewPane(); | ||
| preview.setPath( tab.getPath() ); | ||
| final Processor<String> hpp = new HTMLPreviewProcessor( preview ); | ||
| final Processor<String> mcrp = new CaretReplacementProcessor( hpp ); | ||
| final Processor<String> mp = new MarkdownProcessor( mcrp ); | ||
| // final Processor<String> mcip = new MarkdownCaretInsertionProcessor( mp, tab.getCaretPosition() ); | ||
| - final Processor<String> xmlp = new XMLCaretInsertionProcessor( mp, tab.getCaretPosition() ); | ||
| - final Processor<String> vp = new VariableProcessor( xmlp, getResolvedMap() ); | ||
| + final Processor<String> xmlp = new XMLProcessor( mp, tab.getPath() ); | ||
| + final Processor<String> xcip = new XMLCaretInsertionProcessor( xmlp, tab.getCaretPosition() ); | ||
| + final Processor<String> vp = new VariableProcessor( xcip, getResolvedMap() ); | ||
| vp.processChain( tab.getEditorText() ); | ||
| private void fileExit() { | ||
| final Window window = getWindow(); | ||
| - Event.fireEvent( window, | ||
| - new WindowEvent( window, WindowEvent.WINDOW_CLOSE_REQUEST ) ); | ||
| + fireEvent( window, new WindowEvent( window, WINDOW_CLOSE_REQUEST ) ); | ||
| } | ||
| @Override | ||
| public String processLink( final String t ) { | ||
| - final int caretOffset = getCaretPosition(); | ||
| + final int caret = getCaretPosition(); | ||
| int insertOffset = -1; | ||
| if( t.length() > 0 ) { | ||
| try { | ||
| final VTDNav vn = getNavigator( t ); | ||
| - | ||
| final int tokens = vn.getTokenCount(); | ||
| int currTokenIndex = 0; | ||
| int prevTokenIndex = currTokenIndex; | ||
| - int currTokenOffset = 0; | ||
| - | ||
| - boolean found = false; | ||
| + int currOffset = 0; | ||
| // To find the insertion spot even faster, the algorithm could | ||
| // use a binary search or interpolation search algorithm. This | ||
| // would reduce the worst-case iterations to O(log n) from O(n). | ||
| - while( currTokenIndex < tokens && !found ) { | ||
| + while( currTokenIndex < tokens ) { | ||
| if( vn.getTokenType( currTokenIndex ) == TOKEN_CHARACTER_DATA ) { | ||
| - final int prevTokenOffset = currTokenOffset; | ||
| - currTokenOffset = vn.getTokenOffset( currTokenIndex ); | ||
| - | ||
| - if( currTokenOffset > caretOffset ) { | ||
| - found = true; | ||
| + final int prevOffset = currOffset; | ||
| + currOffset = vn.getTokenOffset( currTokenIndex ); | ||
| - final int prevTokenLength = vn.getTokenLength( prevTokenIndex ); | ||
| + if( currOffset > caret ) { | ||
| + final int prevLength = vn.getTokenLength( prevTokenIndex ); | ||
| // If the caret falls within the limits of the previous token, then | ||
| // insert the caret position marker at the caret offset. | ||
| - if( isBetween( caretOffset, prevTokenOffset, prevTokenOffset + prevTokenLength ) ) { | ||
| - insertOffset = caretOffset; | ||
| + if( isBetween( caret, prevOffset, prevOffset + prevLength ) ) { | ||
| + insertOffset = caret; | ||
| } else { | ||
| // The caret position is outside the previous token's text | ||
| // boundaries, but not inside the current text token. The | ||
| // caret should be positioned into the closer text token. | ||
| // For now, the cursor is positioned at the start of the | ||
| // current text token. | ||
| - insertOffset = currTokenOffset; | ||
| + insertOffset = currOffset; | ||
| } | ||
| - | ||
| - // Done. | ||
| - continue; | ||
| + | ||
| + break; | ||
| } | ||
| } catch( final Exception ex ) { | ||
| throw new RuntimeException( | ||
| - new ParseException( ex.getMessage(), caretOffset ) | ||
| + new ParseException( ex.getMessage(), caret ) | ||
| ); | ||
| } | ||
| * instance for scanning through the XML elements. | ||
| * | ||
| - * @param xml | ||
| + * @param xml The XML document to parse. | ||
| * | ||
| - * @return | ||
| + * @return A document navigator instance. | ||
| */ | ||
| private VTDNav getNavigator( final String xml ) throws VTDException { | ||
| final VTDGen vg = getParser(); | ||
| + // TODO: Use the document's encoding... | ||
| vg.setDoc( xml.getBytes() ); | ||
| vg.parse( true ); | ||
| package com.scrivenvar.processors; | ||
| +import com.scrivenvar.Services; | ||
| +import com.scrivenvar.service.Snitch; | ||
| import java.io.File; | ||
| +import java.io.Reader; | ||
| import java.io.StringReader; | ||
| import java.io.StringWriter; | ||
| import java.nio.file.Path; | ||
| import java.nio.file.Paths; | ||
| -import javax.xml.parsers.SAXParser; | ||
| -import javax.xml.parsers.SAXParserFactory; | ||
| +import java.text.ParseException; | ||
| +import javax.xml.stream.XMLEventReader; | ||
| +import javax.xml.stream.XMLInputFactory; | ||
| +import javax.xml.stream.XMLStreamException; | ||
| +import javax.xml.stream.events.ProcessingInstruction; | ||
| +import javax.xml.stream.events.XMLEvent; | ||
| import javax.xml.transform.Source; | ||
| import javax.xml.transform.Transformer; | ||
| +import javax.xml.transform.TransformerConfigurationException; | ||
| import javax.xml.transform.TransformerFactory; | ||
| import javax.xml.transform.stream.StreamResult; | ||
| import javax.xml.transform.stream.StreamSource; | ||
| +import net.sf.saxon.TransformerFactoryImpl; | ||
| import static net.sf.saxon.tree.util.ProcInstParser.getPseudoAttribute; | ||
| -import org.xml.sax.InputSource; | ||
| -import org.xml.sax.helpers.DefaultHandler; | ||
| /** | ||
| public class XMLProcessor extends AbstractProcessor<String> { | ||
| - private final ProcessingInstructionHandler handler = new ProcessingInstructionHandler(); | ||
| + private final Snitch snitch = Services.load( Snitch.class ); | ||
| + | ||
| + private XMLInputFactory xmlInputFactory; | ||
| + private TransformerFactory transformerFactory; | ||
| + | ||
| private String href; | ||
| private Path path; | ||
| } | ||
| + /** | ||
| + * Transforms the given XML text into another form (typically Markdown). | ||
| + * | ||
| + * @param text The text to transform, can be empty, cannot be null. | ||
| + * | ||
| + * @return The transformed text, or empty if text is empty. | ||
| + */ | ||
| @Override | ||
| - public String processLink( final String t ) { | ||
| - String result = t; | ||
| + public String processLink( final String text ) { | ||
| + try { | ||
| + return text.isEmpty() ? text : transform( text ); | ||
| + } catch( Exception e ) { | ||
| + throw new RuntimeException( e ); | ||
| + } | ||
| + } | ||
| - try( final StringReader sr = new StringReader( t ) ) { | ||
| - SAXParserFactory saxFactory = SAXParserFactory.newInstance(); | ||
| - SAXParser saxParser = saxFactory.newSAXParser(); | ||
| + /** | ||
| + * Performs an XSL transformation on the given XML text. The XML text must | ||
| + * have a processing instruction that points to the XSL template file to use | ||
| + * for the transformation. | ||
| + * | ||
| + * @param text The text to transform. | ||
| + * | ||
| + * @return The transformed text. | ||
| + */ | ||
| + private String transform( final String text ) throws Exception { | ||
| + // Extract the XML stylesheet processing instruction. | ||
| + final String template = getXsltFilename( text ); | ||
| - final InputSource is = new InputSource( sr ); | ||
| - saxParser.parse( is, getHandler() ); | ||
| + try( | ||
| + final StringWriter output = new StringWriter(); | ||
| + final StringReader input = new StringReader( text ) ) { | ||
| - } catch( Exception ex ) { | ||
| - System.out.println( ex.getMessage() ); | ||
| + final Source xml = new StreamSource( input ); | ||
| + final Path xsl = getXslPath( template ); | ||
| + final StreamResult sr = new StreamResult( output ); | ||
| + getTransformer( xsl ).transform( xml, sr ); | ||
| + | ||
| + return output.toString(); | ||
| } | ||
| + } | ||
| - try( | ||
| - final StringReader input = new StringReader( t ); | ||
| - final StringWriter output = new StringWriter(); ) { | ||
| - final Source source = new StreamSource( input ); | ||
| + /** | ||
| + * Returns an XSL transformer ready to transform an XML document using the | ||
| + * XSLT file specified by the given path. If the path is already known then | ||
| + * this will return the associated transformer. | ||
| + * | ||
| + * @param path The path to an XSLT file. | ||
| + * | ||
| + * @return A transformer that will transform XML documents using the given | ||
| + * XSLT file. | ||
| + * | ||
| + * @throws TransformerConfigurationException Could not instantiate the | ||
| + * transformer. | ||
| + */ | ||
| + private Transformer getTransformer( final Path path ) | ||
| + throws TransformerConfigurationException { | ||
| - final TransformerFactory factory = TransformerFactory.newInstance(); | ||
| - final Path xmlPath = getPath(); | ||
| - final File xmlDirectory = xmlPath.toFile().getParentFile(); | ||
| + final TransformerFactory factory = getTransformerFactory(); | ||
| + final Source xslt = new StreamSource( path.toFile() ); | ||
| + return factory.newTransformer( xslt ); | ||
| + } | ||
| - final Path xslPath = Paths.get( xmlDirectory.getPath(), getHref() ); | ||
| + private Path getXslPath( final String filename ) { | ||
| + final Path xmlPath = getPath(); | ||
| + final File xmlDirectory = xmlPath.toFile().getParentFile(); | ||
| - final Source xslt = new StreamSource( xslPath.toFile() ); | ||
| - final Transformer transformer = factory.newTransformer( xslt ); | ||
| + return Paths.get( xmlDirectory.getPath(), filename ); | ||
| + } | ||
| - final StreamResult sr = new StreamResult( output ); | ||
| + /** | ||
| + * Given XML text, this will use a StAX pull reader to obtain the XML | ||
| + * stylesheet processing instruction. This will throw a parse exception if the | ||
| + * href pseudo-attribute filename value cannot be found. | ||
| + * | ||
| + * @param xml The XML containing an xml-stylesheet processing instruction. | ||
| + * | ||
| + * @return The href pseudo-attribute value. | ||
| + * | ||
| + * @throws XMLStreamException Could not parse the XML file. | ||
| + * @throws ParseException Could not find a non-empty HREF attribute value. | ||
| + */ | ||
| + private String getXsltFilename( final String xml ) | ||
| + throws XMLStreamException, ParseException { | ||
| - transformer.transform( source, sr ); | ||
| + String result = ""; | ||
| - result = output.toString(); | ||
| + try( final StringReader sr = new StringReader( xml ) ) { | ||
| + boolean found = false; | ||
| + int count = 0; | ||
| + final XMLEventReader reader = createXMLEventReader( sr ); | ||
| - input.close(); | ||
| - output.close(); | ||
| - } catch( Exception e ) { | ||
| - System.out.println( e.getMessage() ); | ||
| + // If the processing instruction wasn't found in the first 10 lines, | ||
| + // fail fast. This should iterate twice through the loop. | ||
| + while( !found && reader.hasNext() && count++ < 10 ) { | ||
| + final XMLEvent event = reader.nextEvent(); | ||
| + | ||
| + if( event.isProcessingInstruction() ) { | ||
| + final ProcessingInstruction pi = (ProcessingInstruction)event; | ||
| + final String target = pi.getTarget(); | ||
| + | ||
| + if( "xml-stylesheet".equalsIgnoreCase( target ) ) { | ||
| + result = getPseudoAttribute( pi.getData(), "href" ); | ||
| + found = true; | ||
| + } | ||
| + } | ||
| + } | ||
| + | ||
| + sr.close(); | ||
| } | ||
| return result; | ||
| } | ||
| - private ProcessingInstructionHandler getHandler() { | ||
| - return this.handler; | ||
| + private XMLEventReader createXMLEventReader( final Reader reader ) | ||
| + throws XMLStreamException { | ||
| + return getXMLInputFactory().createXMLEventReader( reader ); | ||
| } | ||
| - private String getHref() { | ||
| - return this.href; | ||
| + private synchronized XMLInputFactory getXMLInputFactory() { | ||
| + if( this.xmlInputFactory == null ) { | ||
| + this.xmlInputFactory = createXMLInputFactory(); | ||
| + } | ||
| + | ||
| + return this.xmlInputFactory; | ||
| } | ||
| - private void setHref( final String href ) { | ||
| - this.href = href; | ||
| + private XMLInputFactory createXMLInputFactory() { | ||
| + return XMLInputFactory.newInstance(); | ||
| + } | ||
| + | ||
| + private synchronized TransformerFactory getTransformerFactory() { | ||
| + if( this.transformerFactory == null ) { | ||
| + this.transformerFactory = createTransformerFactory(); | ||
| + } | ||
| + | ||
| + return this.transformerFactory; | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns a high-performance XSLT 2 transformation engine. | ||
| + * | ||
| + * @return An XSL transforming engine. | ||
| + */ | ||
| + private TransformerFactory createTransformerFactory() { | ||
| + return new TransformerFactoryImpl(); | ||
| + //return TransformerFactory.newInstance(); | ||
| } | ||
| private void setPath( final Path path ) { | ||
| this.path = path; | ||
| } | ||
| private Path getPath() { | ||
| return this.path; | ||
| - } | ||
| - | ||
| - private class ProcessingInstructionHandler extends DefaultHandler { | ||
| - | ||
| - @Override | ||
| - public void processingInstruction( final String target, final String data ) { | ||
| - if( "xml-stylesheet".equalsIgnoreCase( target ) ) { | ||
| - setHref( getPseudoAttribute( data, "href" ) ); | ||
| - } | ||
| - } | ||
| } | ||
| } | ||
| +/* | ||
| + * Copyright 2016 White Magic Software, Ltd. | ||
| + * | ||
| + * All rights reserved. | ||
| + * | ||
| + * Redistribution and use in source and binary forms, with or without | ||
| + * modification, are permitted provided that the following conditions are met: | ||
| + * | ||
| + * o Redistributions of source code must retain the above copyright | ||
| + * notice, this list of conditions and the following disclaimer. | ||
| + * | ||
| + * o Redistributions in binary form must reproduce the above copyright | ||
| + * notice, this list of conditions and the following disclaimer in the | ||
| + * documentation and/or other materials provided with the distribution. | ||
| + * | ||
| + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||
| + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||
| + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||
| + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||
| + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||
| + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||
| + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||
| + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||
| + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
| + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
| + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
| + */ | ||
| +package com.scrivenvar.service; | ||
| + | ||
| +import java.io.IOException; | ||
| +import java.nio.file.Path; | ||
| + | ||
| +/** | ||
| + * Listens for changes to file system files and directories. | ||
| + * | ||
| + * @author White Magic Software, Ltd. | ||
| + */ | ||
| +public interface Snitch { | ||
| + | ||
| + /** | ||
| + * Listens for changes to the path. If the path specifies a file, then only | ||
| + * notifications pertaining to that file are sent. Otherwise, change events | ||
| + * for the directory that contains the file are sent. | ||
| + * | ||
| + * @param file Send notifications when this file changes. | ||
| + * | ||
| + * @throws IOException Couldn't create a watcher for the given file. | ||
| + */ | ||
| + public void listen( Path file ) throws IOException; | ||
| + | ||
| + /** | ||
| + * Removes the given file from the notifications list. | ||
| + * | ||
| + * @param file The file to stop monitoring for any changes. | ||
| + */ | ||
| + public void ignore( final Path file ); | ||
| + | ||
| + /** | ||
| + * Stop listening for events. | ||
| + */ | ||
| + public void stop(); | ||
| +} | ||
| +/* | ||
| + * Copyright 2016 White Magic Software, Ltd. | ||
| + * | ||
| + * All rights reserved. | ||
| + * | ||
| + * Redistribution and use in source and binary forms, with or without | ||
| + * modification, are permitted provided that the following conditions are met: | ||
| + * | ||
| + * o Redistributions of source code must retain the above copyright | ||
| + * notice, this list of conditions and the following disclaimer. | ||
| + * | ||
| + * o Redistributions in binary form must reproduce the above copyright | ||
| + * notice, this list of conditions and the following disclaimer in the | ||
| + * documentation and/or other materials provided with the distribution. | ||
| + * | ||
| + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||
| + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||
| + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||
| + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||
| + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||
| + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||
| + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||
| + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||
| + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
| + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
| + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
| + */ | ||
| +package com.scrivenvar.service.impl; | ||
| + | ||
| +import com.scrivenvar.service.Snitch; | ||
| +import java.io.IOException; | ||
| +import java.nio.file.FileSystem; | ||
| +import java.nio.file.FileSystems; | ||
| +import java.nio.file.Files; | ||
| +import java.nio.file.Path; | ||
| +import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY; | ||
| +import java.nio.file.WatchEvent; | ||
| +import java.nio.file.WatchKey; | ||
| +import java.nio.file.WatchService; | ||
| +import java.util.Collections; | ||
| +import java.util.HashMap; | ||
| +import java.util.HashSet; | ||
| +import java.util.Map; | ||
| +import java.util.Set; | ||
| + | ||
| +/** | ||
| + * Listens for file changes. | ||
| + * | ||
| + * @author White Magic Software, Ltd. | ||
| + */ | ||
| +public class DefaultSnitch implements Snitch, Runnable { | ||
| + | ||
| + /** | ||
| + * Service for listening to directories for modifications. | ||
| + */ | ||
| + private WatchService watchService; | ||
| + | ||
| + /** | ||
| + * Directories being monitored for changes. | ||
| + */ | ||
| + private Map<WatchKey, Path> keys; | ||
| + | ||
| + /** | ||
| + * Files that will kick off notification events if modified. | ||
| + */ | ||
| + private Set<Path> eavesdropped; | ||
| + | ||
| + /** | ||
| + * Set to true when running; set to false to stop listening. | ||
| + */ | ||
| + private boolean listening; | ||
| + | ||
| + public DefaultSnitch() { | ||
| + } | ||
| + | ||
| + @Override | ||
| + public void stop() { | ||
| + setListening( false ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Adds a listener to the list of files to watch for changes. | ||
| + * | ||
| + * @param file Path to a file to watch for changes. | ||
| + * | ||
| + * @throws IOException The file could not be monitored. | ||
| + */ | ||
| + @Override | ||
| + public void listen( final Path file ) throws IOException { | ||
| + // This will fail if the file is stored in the root folder. | ||
| + final Path path = Files.isDirectory( file ) ? file : file.getParent(); | ||
| + final WatchKey key = path.register( getWatchService(), ENTRY_MODIFY ); | ||
| + | ||
| + getWatchMap().put( key, path ); | ||
| + getEavesdropped().add( file ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Stop listening to the given file for change events. This fails silently. | ||
| + * | ||
| + * @param file The file to no longer monitor for changes. | ||
| + */ | ||
| + @Override | ||
| + public void ignore( final Path file ) { | ||
| + // Remove all occurrences. | ||
| + getWatchMap().values().removeAll( Collections.singleton( file ) ); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Loops until isRunning is set to false. | ||
| + */ | ||
| + @Override | ||
| + public void run() { | ||
| + setListening( true ); | ||
| + | ||
| + while( isListening() ) { | ||
| + try { | ||
| + final WatchKey key = getWatchService().take(); | ||
| + final Path path = get( key ); | ||
| + | ||
| + for( WatchEvent<?> event : key.pollEvents() ) { | ||
| + final Path changed = (Path)event.context(); | ||
| + | ||
| + for( final Path file : getEavesdropped() ) { | ||
| + System.out.println( "Changed: " + changed ); | ||
| + System.out.println( "Monitored: " + file ); | ||
| + } | ||
| + } | ||
| + | ||
| + if( !key.reset() ) { | ||
| + ignore( path ); | ||
| + } | ||
| + } catch( IOException | InterruptedException ex ) { | ||
| + // Stop eavesdropping. | ||
| + setListening( false ); | ||
| + } | ||
| + } | ||
| + } | ||
| + | ||
| + private Path get( final WatchKey key ) { | ||
| + return getWatchMap().get( key ); | ||
| + } | ||
| + | ||
| + private synchronized Map<WatchKey, Path> getWatchMap() { | ||
| + if( this.keys == null ) { | ||
| + this.keys = createWatchKeys(); | ||
| + } | ||
| + | ||
| + return this.keys; | ||
| + } | ||
| + | ||
| + protected Map<WatchKey, Path> createWatchKeys() { | ||
| + return new HashMap<>(); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Returns a list of files that, when changed, will kick off a notification. | ||
| + * | ||
| + * @return A non-null, possibly empty, list of files. | ||
| + */ | ||
| + private synchronized Set<Path> getEavesdropped() { | ||
| + if( this.eavesdropped == null ) { | ||
| + this.eavesdropped = createEavesdropped(); | ||
| + } | ||
| + | ||
| + return this.eavesdropped; | ||
| + } | ||
| + | ||
| + protected Set<Path> createEavesdropped() { | ||
| + return new HashSet<>(); | ||
| + } | ||
| + | ||
| + /** | ||
| + * The existing watch service, or a new instance if null. | ||
| + * | ||
| + * @return A valid WatchService instance, never null. | ||
| + * | ||
| + * @throws IOException Could not create a new watch service. | ||
| + */ | ||
| + private synchronized WatchService getWatchService() throws IOException { | ||
| + if( this.watchService == null ) { | ||
| + this.watchService = createWatchService(); | ||
| + } | ||
| + | ||
| + return this.watchService; | ||
| + } | ||
| + | ||
| + protected WatchService createWatchService() throws IOException { | ||
| + final FileSystem fileSystem = FileSystems.getDefault(); | ||
| + return fileSystem.newWatchService(); | ||
| + } | ||
| + | ||
| + /** | ||
| + * Answers whether the loop should continue executing. | ||
| + * | ||
| + * @return true The internal listening loop should continue listening for file | ||
| + * modification events. | ||
| + */ | ||
| + protected boolean isListening() { | ||
| + return this.listening; | ||
| + } | ||
| + | ||
| + /** | ||
| + * Requests the snitch to stop eavesdropping on file changes. | ||
| + * | ||
| + * @param listening Use true to indicate the service should stop running. | ||
| + */ | ||
| + private void setListening( final boolean listening ) { | ||
| + this.listening = listening; | ||
| + } | ||
| +} | ||
| - | ||
| +com.scrivenvar.service.impl.DefaultSnitch |
| Delta | 448 lines added, 75 lines removed, 373-line increase |
|---|