org.asciidoctor.maven.AsciidoctorRefreshMojo.java Source code

Java tutorial

Introduction

Here is the source code for org.asciidoctor.maven.AsciidoctorRefreshMojo.java

Source

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