org.kathrynhuxtable.maven.plugins.docbkxwrapper.Merger.java Source code

Java tutorial

Introduction

Here is the source code for org.kathrynhuxtable.maven.plugins.docbkxwrapper.Merger.java

Source

/*
 * Copyright (c) 2010 Kathryn Huxtable.
 *
 * This file is part of the Image Generator Maven plugin.
 *
 * 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.
 *
 * $Id$
 */
package org.kathrynhuxtable.maven.plugins.docbkxwrapper;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;

import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.doxia.site.decoration.DecorationModel;
import org.apache.maven.doxia.tools.SiteTool;
import org.apache.maven.doxia.tools.SiteToolException;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.codehaus.plexus.i18n.DefaultI18N;
import org.codehaus.plexus.i18n.I18N;
import org.codehaus.plexus.personality.plexus.lifecycle.phase.InitializationException;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.PathTool;
import org.codehaus.plexus.util.StringUtils;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.xpath.XPath;

/**
 * Goal runs Velocity on the files in the specified directory.
 */
public class Merger {

    private MavenProject project;
    private I18N i18n;
    private VelocityEngine ve;
    private Template template;
    private AttributeMap attributes;
    private DecorationModel decorationModel;

    /**
     * DOCUMENT ME!
     *
     * @param  templateFile    TODO
     * @param  localRepository DOCUMENT ME!
     * @param  repositories    DOCUMENT ME!
     * @param  i18n            DOCUMENT ME!
     * @param  inputEncoding   DOCUMENT ME!
     * @param  outputEncoding  DOCUMENT ME!
     * @param  siteDirectory   TODO
     * @param  project         DOCUMENT ME!
     * @param  siteTool        DOCUMENT ME!
     * @param  reactorProjects DOCUMENT ME!
     *
     * @throws MojoExecutionException DOCUMENT ME!
     *
     * @see    org.apache.maven.plugin.AbstractMojo#execute()
     */
    public void initialize(File templateFile, ArtifactRepository localRepository, List<?> repositories, I18N i18n,
            String inputEncoding, String outputEncoding, File siteDirectory, MavenProject project,
            SiteTool siteTool, List<?> reactorProjects) throws MojoExecutionException {
        this.i18n = i18n;
        this.project = project;
        ve = initializeVelocityEngine();
        template = getVelocityTemplate(ve, templateFile);

        initializeI18N(i18n);

        attributes = initializeAttributes(inputEncoding, outputEncoding, project);

        try {
            decorationModel = siteTool.getDecorationModel(project, reactorProjects, localRepository, repositories,
                    getRelativeFilePath(project.getBasedir(), siteDirectory), Locale.getDefault(), inputEncoding,
                    outputEncoding);
        } catch (SiteToolException e) {
            throw new MojoExecutionException("SiteToolException: " + e.getMessage(), e);
        }
    }

    /**
     * DOCUMENT ME!
     *
     * @param  i18n
     *
     * @throws MojoExecutionException
     */
    private void initializeI18N(I18N i18n) throws MojoExecutionException {
        try {
            ((DefaultI18N) i18n).initialize();
        } catch (InitializationException e) {
            e.printStackTrace();
            throw new MojoExecutionException("Unable to initialize I18N object", e);
        }
    }

    /**
     * Initialize the velocity engine.
     *
     * @return the velocity engine.
     *
     * @throws MojoExecutionException
     */
    private VelocityEngine initializeVelocityEngine() throws MojoExecutionException {
        VelocityEngine ve = new VelocityEngine();

        try {
            ve.addProperty("resource.loader", "file, class");
            ve.addProperty("class.resource.loader.description", "Velocity Classpath Resource Loader");
            ve.addProperty("class.resource.loader.class",
                    "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
            ve.init();
        } catch (Exception e) {
            e.printStackTrace();
            throw new MojoExecutionException("Unable to initialize Velocity engine", e);
        }

        return ve;
    }

    /**
     * Get the velocity template.
     *
     * @param  ve
     * @param  templateFile TODO
     *
     * @return the velocity template.
     *
     * @throws MojoExecutionException
     */
    private Template getVelocityTemplate(VelocityEngine ve, File templateFile) throws MojoExecutionException {
        Template template = null;

        try {
            template = ve.getTemplate(getRelativeFilePath(project.getBasedir(), templateFile));
        } catch (ResourceNotFoundException e) {
            e.printStackTrace();
            throw new MojoExecutionException("Unable to locate template " + templateFile, e);
        } catch (ParseErrorException e) {
            e.printStackTrace();
            throw new MojoExecutionException("Problem parsing the template", e);
        } catch (MethodInvocationException e) {
            e.printStackTrace();
            throw new MojoExecutionException("Something invoked in the template threw an exception", e);
        } catch (Exception e) {
            e.printStackTrace();
            throw new MojoExecutionException("Some random template parsing error occurred", e);
        }

        return template;
    }

    /**
     * DOCUMENT ME!
     *
     * @param  inputEncoding
     * @param  outputEncoding
     * @param  project
     *
     * @return DOCUMENT ME!
     */
    private AttributeMap initializeAttributes(String inputEncoding, String outputEncoding, MavenProject project) {
        AttributeMap attributes = new AttributeMap();

        if (attributes.get("project") == null) {
            attributes.put("project", project);
        }

        // Put any of the properties in directly into the Velocity attributes
        attributes.putAll(project.getProperties());

        if (attributes.get("inputEncoding") == null) {
            attributes.put("inputEncoding", inputEncoding);
        }

        if (attributes.get("outputEncoding") == null) {
            attributes.put("outputEncoding", outputEncoding);
        }

        Date date = new Date();

        attributes.put("currentDate", date);
        attributes.put("lastPublished", new SimpleDateFormat("dd MMM yyyy").format(date));

        return attributes;
    }

    /**
     * Merge a file with the velocity template, filtering it if necessary.
     *
     * @param  file            the file to merge
     * @param  doFiltering     DOCUMENT ME!
     * @param  filterExtension DOCUMENT ME!
     * @param  sourceDirectory DOCUMENT ME!
     * @param  targetFile      DOCUMENT ME!
     *
     * @throws MojoExecutionException
     */
    public void mergeFile(String file, boolean doFiltering, String filterExtension, File sourceDirectory,
            File targetFile) throws MojoExecutionException {
        File sourceFile = new File(sourceDirectory, file);

        FileWriter fileWriter = null;
        Reader fileReader = null;

        if (!targetFile.getParentFile().exists()) {
            targetFile.getParentFile().mkdirs();
        }

        VelocityContext context = createContext(sourceFile, sourceDirectory, doFiltering, filterExtension,
                decorationModel, attributes);

        try {
            StringWriter sw = new StringWriter();

            fileReader = new InputStreamReader(new FileInputStream(sourceFile), "UTF-8");
            // If file ends in filter extension, filter it through Velocity before merging with template.
            if (doFiltering) {
                if (!ve.evaluate(context, sw, "htmlfilter-site", fileReader)) {
                    throw new MojoExecutionException("Unable to evaluate html file " + sourceFile);
                }

                closeReader(fileReader);
                fileReader = new StringReader(sw.toString());
            }

            Document doc = parseXHTMLDocument(fileReader);

            addInfoFromDocument(context, decorationModel, doc, null);

            fileWriter = new FileWriter(targetFile);

            template.merge(context, fileWriter);
        } catch (Exception e) {
            e.printStackTrace();
            throw new MojoExecutionException("Unable to merge Velocity", e);
        } finally {
            closeReader(fileReader);
            closeWriter(fileWriter);
        }
    }

    /**
     * DOCUMENT ME!
     *
     * @param  filename        DOCUMENT ME!
     * @param  doFiltering     DOCUMENT ME!
     * @param  filterExtension DOCUMENT ME!
     *
     * @return DOCUMENT ME!
     */
    public String makeFinalFilename(String filename, boolean doFiltering, String filterExtension) {
        if (!doFiltering) {
            return filename;
        }

        return filename.substring(0, filename.length() - filterExtension.length()) + ".html";
    }

    /**
     * Add the information from the document to the Velocity context.
     *
     * @param context         the velocity context.
     * @param decorationModel the Doxia decoration model.
     * @param doc             the document.
     * @param createDate      the create date.
     */
    private void addInfoFromDocument(VelocityContext context, DecorationModel decorationModel, Document doc,
            Date createDate) {
        context.put("authors", getAuthors(doc));

        String title = "";

        if (decorationModel.getName() != null) {
            title = decorationModel.getName();
        } else if (project.getName() != null) {
            title = project.getName();
        }

        if (title.length() > 0) {
            title += " - ";
        }

        title += getTitle(doc);

        context.put("title", title);

        context.put("headContent", getHeadContent(doc));

        context.put("bodyContent", getBodyContent(doc));

        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");

        if (createDate != null) {
            context.put("dateCreation", sdf.format(createDate));
        }
    }

    /**
     * Close a Reader ignoring any exception.
     *
     * @param reader the reader to close.
     */
    private void closeReader(Reader reader) {
        if (reader != null) {
            try {
                reader.close();
            } catch (IOException e) {
            }
        }
    }

    /**
     * Close a Writer ignoring any exceptions.
     *
     * @param writer the Writer to close.
     */
    private void closeWriter(FileWriter writer) {
        if (writer != null) {
            try {
                writer.close();
            } catch (IOException e) {
            }
        }
    }

    /**
     * Get the relative file path for a file.
     *
     * @param  oldPath the base path.
     * @param  newPath the new path.
     *
     * @return the relative path to the newPath based on the oldPath.
     */
    private String getRelativeFilePath(File oldPath, File newPath) {
        List<String> names = new ArrayList<String>();

        oldPath = oldPath.getAbsoluteFile();
        newPath = newPath.getAbsoluteFile();

        while (newPath != null && !newPath.equals(oldPath)) {
            names.add(newPath.getName());
            newPath = newPath.getParentFile();
        }

        if (newPath == null) {
            return "";
        }

        StringBuilder result = new StringBuilder();

        for (int i = names.size() - 1; i >= 0; i--) {
            if (result.length() > 0) {
                result.append('/');
            }

            result.append(names.get(i));
        }

        return result.toString();
    }

    /**
     * Create the velocity context.
     *
     * @param  sourceFile      the source file.
     * @param  sourceDirectory TODO
     * @param  doFiltering     TODO
     * @param  filterExtension TODO
     * @param  decorationModel the Doxia decoration model.
     * @param  attributes      the attributes from the POM and such.
     *
     * @return the velocity context.
     */
    private VelocityContext createContext(File sourceFile, File sourceDirectory, boolean doFiltering,
            String filterExtension, DecorationModel decorationModel, AttributeMap attributes) {
        VelocityContext context = new VelocityContext();

        // ----------------------------------------------------------------------
        // Data objects
        // ----------------------------------------------------------------------

        String relativePath = PathTool.getRelativePath(sourceDirectory.getPath(), sourceFile.getPath());

        context.put("relativePath", relativePath);

        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");

        context.put("dateRevision", sdf.format(new Date()));

        context.put("decoration", decorationModel);

        context.put("currentDate", new Date());

        Locale locale = Locale.getDefault();

        context.put("dateFormat", DateFormat.getDateInstance(DateFormat.DEFAULT, locale));

        String currentFileName = sourceFile.getName();
        String alignedFileName = PathTool.calculateLink(getRelativeFilePath(sourceDirectory, sourceFile),
                relativePath);

        currentFileName = makeFinalFilename(currentFileName, doFiltering, filterExtension);
        alignedFileName = makeFinalFilename(alignedFileName, doFiltering, filterExtension);

        context.put("currentFileName", currentFileName);

        context.put("alignedFileName", alignedFileName);

        context.put("locale", locale);

        // Add global properties.
        if (attributes != null) {
            for (Object o : attributes.keySet()) {
                context.put((String) o, attributes.get(o));
            }
        }

        // ----------------------------------------------------------------------
        // Tools
        // ----------------------------------------------------------------------

        context.put("PathTool", new PathTool());

        context.put("FileUtils", new FileUtils());

        context.put("StringUtils", new StringUtils());

        context.put("i18n", i18n);

        return context;
    }

    /**
     * Parse a document from text in the reader.
     *
     * @param  reader the Reader from which to parse the document.
     *
     * @return the JDom document parsed from the XHTML file.
     *
     * @throws IOException   if the reader cannot be read.
     * @throws JDOMException if the reader cannot be parsed.
     */
    private Document parseXHTMLDocument(Reader reader) throws JDOMException, IOException {
        Document document = null;

        SAXBuilder builder = new SAXBuilder();

        builder.setEntityResolver(new DTDHandler());
        builder.setIgnoringElementContentWhitespace(false);
        builder.setIgnoringBoundaryWhitespace(false);
        document = builder.build(reader);

        return document;
    }

    /**
     * Get the document title.
     *
     * @param  document the document.
     *
     * @return the title.
     */
    private String getTitle(Document document) {
        Element element = null;

        element = selectSingleNode(document.getRootElement(), "/xhtml:html/xhtml:head/xhtml:title");
        if (element == null) {
            return null;
        }

        return element.getText();
    }

    /**
     * Get the document authors.
     *
     * @param  document the document.
     *
     * @return a list of authors.
     */
    private List<String> getAuthors(Document document) {
        List<Element> nl = getXPathList(document, "/xhtml:html/xhtml:head/xhtml:meta[@class='author']");
        List<String> list = new ArrayList<String>();

        for (Element elem : nl) {
            String author = selectSingleNode(elem, "xhtml:td[@class='author']").getText();

            list.add(author);
        }

        return list;
    }

    /**
     * Extract a list matching an XPath path. This surreptitiously adds the
     * XHTML namespace.
     *
     * @param  document the document.
     * @param  path     the path to select.
     *
     * @return the list of elements matching the path.
     */
    @SuppressWarnings("unchecked")
    protected List<Element> getXPathList(Document document, String path) {
        try {
            XPath xpath = XPath.newInstance(path);

            xpath.addNamespace("xhtml", "http://www.w3.org/1999/xhtml");
            List<Element> nl = (List<Element>) xpath.selectNodes(document);

            return nl;
        } catch (JDOMException e) {
            return new ArrayList<Element>();
        }
    }

    /**
     * Get the body content as a string.
     *
     * @param  document the document.
     *
     * @return the body content.
     */
    private String getBodyContent(Document document) {
        Element element = null;

        element = selectSingleNode(document.getRootElement(), "/xhtml:html/xhtml:body");
        if (element == null) {
            return null;
        }

        return getElementContentsAsText(element);
    }

    /**
     * Get the head content as a string.
     *
     * @param  document the document.
     *
     * @return the head content.
     */
    private String getHeadContent(Document document) {
        Element element = null;

        element = selectSingleNode(document.getRootElement(), "/xhtml:html/xhtml:head");
        if (element == null) {
            return null;
        }

        return getElementContentsAsText(element);
    }

    /**
     * Get the element contents as a String.
     *
     * @param  element the element.
     *
     * @return the element contents.
     */
    private String getElementContentsAsText(Element element) {
        StringBuilder text = new StringBuilder();
        HTMLOutputter writer = new HTMLOutputter(
                Format.getPrettyFormat().setTextMode(Format.TextMode.TRIM_FULL_WHITE).setExpandEmptyElements(true));
        List<Element> children = getChildren(element);

        if (children.size() == 0) {
            return element.getText();
        }

        for (Element child : children) {
            StringWriter sw = new StringWriter();

            try {
                writer.output(child, sw);
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

            text.append(sw);
        }

        return text.toString();
    }

    /**
     * Wrapper around Element.getChildren() to suppress the type warning.
     *
     * @param  element the element whose children to get.
     *
     * @return a List of Elements representing the children of the specified
     *         element.
     */
    @SuppressWarnings("unchecked")
    private List<Element> getChildren(Element element) {
        return (List<Element>) element.getChildren();
    }

    /**
     * Select a single node using XPath.
     *
     * @param  element the element.
     * @param  path    the path to select.
     *
     * @return the element selected.
     */
    private Element selectSingleNode(Element element, String path) {
        try {
            XPath xpath = XPath.newInstance(path);

            xpath.addNamespace("xhtml", "http://www.w3.org/1999/xhtml");
            return (Element) xpath.selectSingleNode(element);
        } catch (JDOMException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            return null;
        }
    }

    /**
     * Simplify references to the attribute hash map.
     */
    private static class AttributeMap extends HashMap<Object, Object> {
        private static final long serialVersionUID = 1787343499009497124L;
    }
}