All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.codehaus.mojo.javacc.JJDocMojo Maven / Gradle / Ivy

There is a newer version: 4.1.5
Show newest version
package org.codehaus.mojo.javacc;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file 
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

import java.io.File;
import java.util.Arrays;
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.Set;

import org.apache.maven.doxia.sink.Sink;
import org.apache.maven.doxia.siterenderer.Renderer;
import org.apache.maven.project.MavenProject;
import org.apache.maven.reporting.AbstractMavenReport;
import org.apache.maven.reporting.MavenReportException;

/**
 * JJDoc takes a JavaCC
 * parser specification and produces documentation for the BNF grammar. This
 * mojo will search the source directory for all *.jj files and run
 * JJDoc once for each file it finds. Each of these output files, along with an
 * index.html file will be placed in the site directory (
 * target/site/jjdoc), and a link will be created in the "Project
 * Reports" menu of the generated site.
 * 
 * @goal jjdoc
 * @execute phase=generate-sources
 * @since 2.3
 * @author Paul Gier
 * @version $Id: JJDocMojo.java 10603 2009-09-06 15:05:08Z bentmann $
 * @see JJDoc
 *      Documentation
 */
public class JJDocMojo extends AbstractMavenReport
{

  // ----------------------------------------------------------------------
  // Mojo Parameters
  // ----------------------------------------------------------------------

  /**
   * The current Maven project.
   * 
   * @parameter property=project
   * @required
   * @readonly
   */
  private MavenProject project;

  /**
   * The site renderer.
   * 
   * @component
   */
  private Renderer siteRenderer;

  /**
   * The directories where the JavaCC grammar files (*.jj) are
   * located. By default, the directories
   * ${basedir}/src/main/javacc,
   * ${project.build.directory}/generated-sources/jjtree and
   * ${project.build.directory}/generated-sources/jtb are scanned
   * for grammar files to document.
   * 
   * @parameter
   */
  private File [] sourceDirectories;

  /**
   * The default source directory for hand-crafted grammar files.
   * 
   * @parameter default-value="${basedir}/src/main/javacc"
   * @readonly
   */
  private File defaultGrammarDirectoryJavaCC;

  /**
   * The default source directory for grammar files generated by JJTree.
   * 
   * @parameter 
   *            default-value="${project.build.directory}/generated-sources/jjtree"
   * @readonly
   */
  private File defaultGrammarDirectoryJJTree;

  /**
   * The default source directory for grammar files generated by JTB.
   * 
   * @parameter default-value="${project.build.directory}/generated-sources/jtb"
   * @readonly
   */
  private File defaultGrammarDirectoryJTB;

  /**
   * The relative path of the JJDoc reports in the output directory. This path
   * will be appended to the output directory.
   * 
   * @parameter default-value="jjdoc";
   */
  private String jjdocDirectory;

  /**
   * The destination directory where JJDoc saves the generated documentation
   * files. Note that this parameter is only relevant if the goal is run from
   * the command line or from the default build lifecycle. If the goal is run
   * indirectly as part of a site generation, the output directory configured in
   * the Maven Site Plugin is used instead.
   * 
   * @parameter property=outputDirectory
   *            default-value="${project.reporting.outputDirectory}"
   */
  private File outputDirectory;

  /**
   * The file encoding to use for reading the grammar files.
   * 
   * @parameter property=grammarEncoding
   *            default-value="${project.build.sourceEncoding}"
   * @since 2.6
   */
  private String grammarEncoding;

  /**
   * The hypertext reference to an optional CSS file for the generated HTML
   * documents. If specified, this CSS file will be included via a
   * <link> element in the HTML documents. Otherwise, the
   * default style will be used.
   * 
   * @parameter property=cssHref
   * @since 2.5
   */
  private String cssHref;

  /**
   * A flag to specify the output format for the generated documentation. If set
   * to true, JJDoc will generate a plain text description of the
   * BNF. Some formatting is done via tab characters, but the intention is to
   * leave it as plain as possible. Specifying false causes JJDoc
   * to generate a hyperlinked HTML document unless the parameter {@link #bnf}
   * has been set to true. Default value is false.
   * 
   * @parameter property=text
   */
  private Boolean text;

  /**
   * A flag whether to generate a plain text document with the unformatted BNF.
   * Note that setting this option to true is only effective if the
   * parameter {@link #text} is false. Default value is
   * false.
   * 
   * @parameter property=bnf
   * @since 2.6
   */
  private Boolean bnf;

  /**
   * This option controls the structure of the generated HTML output. If set to
   * true, a single HTML table for the entire BNF is generated.
   * Setting it to false will produce one table for every
   * production in the grammar.
   * 
   * @parameter property=oneTable default-value=true
   */
  private boolean oneTable;

  /**
   * Get the maven project.
   * 
   * @see org.apache.maven.reporting.AbstractMavenReport#getProject()
   * @return The current Maven project.
   */
  @Override
  protected MavenProject getProject ()
  {
    return this.project;
  }

  /**
   * Get the site renderer.
   * 
   * @see org.apache.maven.reporting.AbstractMavenReport#getSiteRenderer()
   * @return The site renderer.
   */
  @Override
  protected Renderer getSiteRenderer ()
  {
    return this.siteRenderer;
  }

  /**
   * Get the output directory of the report if run directly from the command
   * line.
   * 
   * @see org.apache.maven.reporting.AbstractMavenReport#getOutputDirectory()
   * @return The report output directory.
   */
  @Override
  protected String getOutputDirectory ()
  {
    return this.outputDirectory.getAbsolutePath ();
  }

  /**
   * Get the output directory of the JJDoc files, i.e. the sub directory in the
   * report output directory as specified by the {@link #jjdocDirectory}
   * parameter.
   * 
   * @return The report output directory of the JJDoc files.
   */
  private File getJJDocOutputDirectory ()
  {
    return new File (getReportOutputDirectory (), this.jjdocDirectory);
  }

  /**
   * Get the source directories that should be scanned for grammar files.
   * 
   * @return The source directories that should be scanned for grammar files,
   *         never null.
   */
  private File [] getSourceDirectories ()
  {
    final Set  directories = new LinkedHashSet  ();
    if (this.sourceDirectories != null && this.sourceDirectories.length > 0)
    {
      directories.addAll (Arrays.asList (this.sourceDirectories));
    }
    else
    {
      if (this.defaultGrammarDirectoryJavaCC != null)
      {
        directories.add (this.defaultGrammarDirectoryJavaCC);
      }
      if (this.defaultGrammarDirectoryJJTree != null)
      {
        directories.add (this.defaultGrammarDirectoryJJTree);
      }
      if (this.defaultGrammarDirectoryJTB != null)
      {
        directories.add (this.defaultGrammarDirectoryJTB);
      }
    }
    return directories.toArray (new File [directories.size ()]);
  }

  // ----------------------------------------------------------------------
  // public methods
  // ----------------------------------------------------------------------

  /**
   * @see org.apache.maven.reporting.MavenReport#getName(java.util.Locale)
   * @param locale
   *        The locale to use for this report.
   * @return The name of this report.
   */
  public String getName (final Locale locale)
  {
    return getBundle (locale).getString ("report.jjdoc.name");
  }

  /**
   * @see org.apache.maven.reporting.MavenReport#getDescription(java.util.Locale)
   * @param locale
   *        The locale to use for this report.
   * @return The description of this report.
   */
  public String getDescription (final Locale locale)
  {
    return getBundle (locale).getString ("report.jjdoc.short.description");
  }

  /**
   * @see org.apache.maven.reporting.MavenReport#getOutputName()
   * @return The name of the main report file.
   */
  public String getOutputName ()
  {
    return this.jjdocDirectory + "/index";
  }

  /**
   * @see org.apache.maven.reporting.MavenReport#canGenerateReport()
   * @return true if the configured source directories are not
   *         empty, false otherwise.
   */
  @Override
  public boolean canGenerateReport ()
  {
    final File sourceDirs[] = getSourceDirectories ();
    for (final File sourceDir : sourceDirs)
    {
      final String [] files = sourceDir.list ();
      if (files != null && files.length > 0)
      {
        return true;
      }
    }
    return false;
  }

  /**
   * Run the actual report.
   * 
   * @param locale
   *        The locale to use for this report.
   * @throws MavenReportException
   *         If the report generation failed.
   */
  @Override
  public void executeReport (final Locale locale) throws MavenReportException
  {
    final Sink sink = getSink ();

    createReportHeader (getBundle (locale), sink);

    final File [] sourceDirs = getSourceDirectories ();
    for (final File sourceDir : sourceDirs)
    {
      final GrammarInfo [] grammarInfos = scanForGrammars (sourceDir);

      if (grammarInfos == null)
      {
        getLog ().debug ("Skipping non-existing source directory: " + sourceDir);
      }
      else
      {
        Arrays.sort (grammarInfos, GrammarInfoComparator.getInstance ());
        for (final GrammarInfo grammarInfo : grammarInfos)
        {
          final File grammarFile = grammarInfo.getGrammarFile ();

          String relativeOutputFileName = grammarInfo.getRelativeGrammarFile ();
          relativeOutputFileName = relativeOutputFileName.replaceAll ("(?i)\\.(jj|jjt|jtb)$", getOutputFileExtension ());

          final File jjdocOutputFile = new File (getJJDocOutputDirectory (), relativeOutputFileName);

          final JJDoc jjdoc = newJJDoc ();
          jjdoc.setInputFile (grammarFile);
          jjdoc.setOutputFile (jjdocOutputFile);
          try
          {
            jjdoc.run ();
          }
          catch (final Exception e)
          {
            throw new MavenReportException ("Failed to create BNF documentation: " + grammarFile, e);
          }

          createReportLink (sink, sourceDir, grammarFile, relativeOutputFileName);
        }
      }
    }

    createReportFooter (sink);
    sink.flush ();
    sink.close ();
  }

  /**
   * The JJDoc output file will have a .html or .txt
   * extension depending on the value of the parameters {@link #text} and
   * {@link #bnf}.
   * 
   * @return The file extension (including the leading period) to be used for
   *         the JJDoc output files.
   */
  private String getOutputFileExtension ()
  {
    if (Boolean.TRUE.equals (this.text) || Boolean.TRUE.equals (this.bnf))
    {
      return ".txt";
    }
    else
    {
      return ".html";
    }
  }

  /**
   * Create the header and title for the HTML report page.
   * 
   * @param bundle
   *        The resource bundle with the text.
   * @param sink
   *        The sink for writing to the main report file.
   */
  private void createReportHeader (final ResourceBundle bundle, final Sink sink)
  {
    sink.head ();
    sink.title ();
    sink.text (bundle.getString ("report.jjdoc.title"));
    sink.title_ ();
    sink.head_ ();

    sink.body ();

    sink.section1 ();
    sink.sectionTitle1 ();
    sink.text (bundle.getString ("report.jjdoc.title"));
    sink.sectionTitle1_ ();
    sink.text (bundle.getString ("report.jjdoc.description"));
    sink.section1_ ();

    sink.lineBreak ();
    sink.table ();
    sink.tableRow ();
    sink.tableHeaderCell ();
    sink.text (bundle.getString ("report.jjdoc.table.heading"));
    sink.tableHeaderCell_ ();
    sink.tableRow_ ();
  }

  /**
   * Create a table row containing a link to the JJDoc report for a grammar
   * file.
   * 
   * @param sink
   *        The sink to write the report
   * @param sourceDirectory
   *        The source directory of the grammar file.
   * @param grammarFile
   *        The JavaCC grammar file.
   * @param linkPath
   *        The path to the JJDoc output.
   */
  private void createReportLink (final Sink sink, final File sourceDirectory, final File grammarFile, String linkPath)
  {
    sink.tableRow ();
    sink.tableCell ();
    if (linkPath.startsWith ("/"))
    {
      linkPath = linkPath.substring (1);
    }
    sink.link (linkPath);
    String grammarFileRelativePath = sourceDirectory.toURI ().relativize (grammarFile.toURI ()).toString ();
    if (grammarFileRelativePath.startsWith ("/"))
    {
      grammarFileRelativePath = grammarFileRelativePath.substring (1);
    }
    sink.text (grammarFileRelativePath);
    sink.link_ ();
    sink.tableCell_ ();
    sink.tableRow_ ();
  }

  /**
   * Create the HTML footer for the report page.
   * 
   * @param sink
   *        The sink to write the HTML report page.
   */
  private void createReportFooter (final Sink sink)
  {
    sink.table_ ();
    sink.body_ ();
  }

  /**
   * Creates a new facade to invoke JJDoc. Most options for the invocation are
   * derived from the current values of the corresponding mojo parameters. The
   * caller is responsible to set the input file and output file on the returned
   * facade.
   * 
   * @return The facade for the tool invocation, never null.
   */
  private JJDoc newJJDoc ()
  {
    final JJDoc jjdoc = new JJDoc ();
    jjdoc.setLog (getLog ());
    jjdoc.setGrammarEncoding (this.grammarEncoding);
    jjdoc.setCssHref (this.cssHref);
    jjdoc.setText (this.text);
    jjdoc.setBnf (this.bnf);
    jjdoc.setOneTable (Boolean.valueOf (this.oneTable));
    return jjdoc;
  }

  /**
   * Searches the specified source directory to find grammar files that can be
   * documented.
   * 
   * @param sourceDirectory
   *        The source directory to scan for grammar files.
   * @return An array of grammar infos describing the found grammar files or
   *         null if the source directory does not exist.
   * @throws MavenReportException
   *         If there is a problem while scanning for .jj files.
   */
  private GrammarInfo [] scanForGrammars (final File sourceDirectory) throws MavenReportException
  {
    if (!sourceDirectory.isDirectory ())
    {
      return null;
    }

    GrammarInfo [] grammarInfos;

    getLog ().debug ("Scanning for grammars: " + sourceDirectory);
    try
    {
      final String [] includes = { "**/*.jj", "**/*.JJ", "**/*.jjt", "**/*.JJT", "**/*.jtb", "**/*.JTB" };
      final GrammarDirectoryScanner scanner = new GrammarDirectoryScanner ();
      scanner.setSourceDirectory (sourceDirectory);
      scanner.setIncludes (includes);
      scanner.scan ();
      grammarInfos = scanner.getIncludedGrammars ();
    }
    catch (final Exception e)
    {
      throw new MavenReportException ("Failed to scan for grammars: " + sourceDirectory, e);
    }
    getLog ().debug ("Found grammars: " + Arrays.asList (grammarInfos));

    return grammarInfos;
  }

  /**
   * Get the resource bundle for the report text.
   * 
   * @param locale
   *        The locale to use for this report.
   * @return The resource bundle.
   */
  private ResourceBundle getBundle (final Locale locale)
  {
    return ResourceBundle.getBundle ("jjdoc-report", locale, getClass ().getClassLoader ());
  }

  /**
   * Compares grammar infos using their relative grammar file paths as the sort
   * key.
   */
  private static class GrammarInfoComparator implements Comparator
  {

    /**
     * The singleton instance of this comparator.
     */
    private static final GrammarInfoComparator INSTANCE = new GrammarInfoComparator ();

    /**
     * Gets the singleton instance of this class.
     * 
     * @return The singleton instance of this class.
     */
    public static GrammarInfoComparator getInstance ()
    {
      return INSTANCE;
    }

    /**
     * Compares the path of two grammar files lexicographically.
     * 
     * @param o1
     *        The first grammar info.
     * @param o2
     *        The second grammar info.
     * @return A negative integer if the first grammar is considered "smaller",
     *         a positive integer if it is considered "greater" and zero
     *         otherwise.
     */
    public int compare (final Object o1, final Object o2)
    {
      int rel;

      final GrammarInfo info1 = (GrammarInfo) o1;
      final String [] paths1 = info1.getRelativeGrammarFile ().split ("\\" + File.separatorChar);

      final GrammarInfo info2 = (GrammarInfo) o2;
      final String [] paths2 = info2.getRelativeGrammarFile ().split ("\\" + File.separatorChar);

      final int dirs = Math.min (paths1.length, paths2.length) - 1;
      for (int i = 0; i < dirs; i++)
      {
        rel = paths1[i].compareToIgnoreCase (paths2[i]);
        if (rel != 0)
        {
          return rel;
        }
      }

      rel = paths1.length - paths2.length;
      if (rel != 0)
      {
        return rel;
      }

      return paths1[paths1.length - 1].compareToIgnoreCase (paths2[paths1.length - 1]);
    }

  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy