Java tutorial
/* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.asciidoctor.maven; import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.Scanner; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.commons.io.FileUtils; import org.apache.commons.io.filefilter.NameFileFilter; import org.apache.commons.io.filefilter.RegexFileFilter; import org.apache.commons.io.filefilter.TrueFileFilter; import org.apache.commons.io.monitor.FileAlterationListener; import org.apache.commons.io.monitor.FileAlterationListenerAdaptor; import org.apache.commons.io.monitor.FileAlterationMonitor; import org.apache.commons.io.monitor.FileAlterationObserver; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; import org.asciidoctor.Asciidoctor; @Mojo(name = "auto-refresh") public class AsciidoctorRefreshMojo extends AsciidoctorMojo { public static final String PREFIX = AsciidoctorMaven.PREFIX + "refresher."; @Parameter(property = PREFIX + "port", required = false) protected int port = 2000; @Parameter(property = PREFIX + "interval", required = false) protected int interval = 2000; // 2s private Future<Asciidoctor> asciidoctor = null; private Collection<FileAlterationMonitor> monitors = null; private final AtomicBoolean needsUpdate = new AtomicBoolean(false); private ScheduledExecutorService updaterScheduler = null; @Override public void execute() throws MojoExecutionException, MojoFailureException { // this is long because of JRuby startup createAsciidoctor(); startPolling(); startUpdater(); doWork(); stopUpdater(); stopMonitor(); } private void stopUpdater() { if (updaterScheduler != null) { updaterScheduler.shutdown(); } } private void startUpdater() { updaterScheduler = Executors.newScheduledThreadPool(1); // we prevent refreshing more often than all 200ms and we refresh at least once/s // NOTE1: it is intended to avoid too much time space between file polling and re-rendering // NOTE2: if nothing to refresh it does nothing so all is fine updaterScheduler.scheduleAtFixedRate(new Updater(needsUpdate, this), 0, Math.min(1000, Math.max(200, interval / 2)), TimeUnit.MILLISECONDS); } protected void doWork() throws MojoFailureException, MojoExecutionException { getLog().info("Rendered doc in " + executeAndReturnDuration() + "ms"); doWait(); } protected void doWait() { getLog().info("Type [exit|quit] to exit and [refresh] to force a manual re-rendering."); String line; final Scanner scanner = new Scanner(System.in); while ((line = scanner.nextLine()) != null) { line = line.trim(); if ("exit".equalsIgnoreCase(line) || "quit".equalsIgnoreCase(line)) { break; } if ("refresh".equalsIgnoreCase(line)) { doExecute(); } else { getLog().warn("'" + line + "' not understood, available commands are [quit, exit, refresh]."); } } } private void stopMonitor() throws MojoExecutionException { if (monitors != null) { for (final FileAlterationMonitor monitor : monitors) { try { monitor.stop(); } catch (Exception e) { throw new MojoExecutionException(e.getMessage(), e); } } } } protected synchronized void doExecute() { ensureOutputExists(); // delete only content files, resources are synchronized so normally up to date for (final File f : FileUtils.listFiles(outputDirectory, new RegexFileFilter(ASCIIDOC_REG_EXP_EXTENSION), TrueFileFilter.INSTANCE)) { FileUtils.deleteQuietly(f); } try { getLog().info("Re-rendered doc in " + executeAndReturnDuration() + "ms"); } catch (final MojoExecutionException e) { getLog().error(e); } catch (final MojoFailureException e) { getLog().error(e); } } protected long executeAndReturnDuration() throws MojoExecutionException, MojoFailureException { final long start = System.nanoTime(); super.execute(); final long end = System.nanoTime(); return TimeUnit.NANOSECONDS.toMillis(end - start); } private void startPolling() throws MojoExecutionException { monitors = new ArrayList<FileAlterationMonitor>(); { // content monitor final FileAlterationObserver observer; if (sourceDocumentName != null) { observer = new FileAlterationObserver(sourceDirectory, new NameFileFilter(sourceDocumentName)); } else if (sourceDirectory != null) { observer = new FileAlterationObserver(sourceDirectory, new RegexFileFilter(ASCIIDOC_REG_EXP_EXTENSION)); } else { monitors = null; // no need to start anything because there is no content return; } final FileAlterationMonitor monitor = new FileAlterationMonitor(interval); final FileAlterationListener listener = new FileAlterationListenerAdaptor() { @Override public void onFileCreate(final File file) { getLog().info("File " + file.getAbsolutePath() + " created."); needsUpdate.set(true); } @Override public void onFileChange(final File file) { getLog().info("File " + file.getAbsolutePath() + " updated."); needsUpdate.set(true); } @Override public void onFileDelete(final File file) { getLog().info("File " + file.getAbsolutePath() + " deleted."); needsUpdate.set(true); } }; observer.addListener(listener); monitor.addObserver(observer); monitors.add(monitor); } { // resources monitors if (synchronizations != null) { for (final Synchronization s : synchronizations) { final FileAlterationMonitor monitor = new FileAlterationMonitor(interval); final FileAlterationListener listener = new FileAlterationListenerAdaptor() { @Override public void onFileCreate(final File file) { getLog().info("File " + file.getAbsolutePath() + " created."); synchronize(s); needsUpdate.set(true); } @Override public void onFileChange(final File file) { getLog().info("File " + file.getAbsolutePath() + " updated."); synchronize(s); needsUpdate.set(true); } @Override public void onFileDelete(final File file) { getLog().info("File " + file.getAbsolutePath() + " deleted."); FileUtils.deleteQuietly(file); needsUpdate.set(true); } }; final File source = s.getSource(); final FileAlterationObserver observer; if (source.isDirectory()) { observer = new FileAlterationObserver(source); } else { observer = new FileAlterationObserver(source.getParentFile(), new NameFileFilter(source.getName())); } observer.addListener(listener); monitor.addObserver(observer); monitors.add(monitor); } } } for (final FileAlterationMonitor monitor : monitors) { try { monitor.start(); } catch (final Exception e) { throw new MojoExecutionException(e.getMessage(), e); } } } private void createAsciidoctor() { final ExecutorService es = Executors.newSingleThreadExecutor(); asciidoctor = es.submit(new Callable<Asciidoctor>() { @Override public Asciidoctor call() throws Exception { return Asciidoctor.Factory.create(); } }); es.shutdown(); } private static class Updater implements Runnable { private final AtomicBoolean run; private final AsciidoctorRefreshMojo mojo; private Updater(final AtomicBoolean run, final AsciidoctorRefreshMojo mojo) { this.run = run; this.mojo = mojo; } @Override public void run() { if (run.get()) { run.set(false); mojo.doExecute(); } } } }