JJTreeGoalMojo.java

/*
 * Copyright (c) 2025-2026, Marc Mazas <mazas.marc@gmail.com>.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *     * Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 *     * 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.
 *     * Neither the names of the copyright holders nor the names of its
 *       contributors may be used to endorse or promote products derived from
 *       this software without specific prior written permission.
 *
 * 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 OWNER 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 org.javacc.mojo;

import java.io.File;
import java.util.List;

import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;

/**
 * The <b>jjtree</b> goal, for generating the tree files (but not the parser files) from a JJTree
 * grammar.<br>
 * It searches the source directory for all grammar files and run JJTree once for each file it
 * finds, managing the output directory from the user setting or the default setting, and adding it
 * to the project-wide compile source roots.<br>
 * It uses an intermediate output directory to help discard any generated file when the user wants
 * instead his customized version he has put in any compile source root.
 * <p>
 * Detailed information about the JJTree options can be found on the
 * <a href="https://javacc.github.io/javacc-8/">JavaCC website</a>.<br>
 * The code repositories can be found within <a href="https://github.com/javacc">JavaCC at
 * GitHub</a>.
 *
 * @since 3.8.0
 * @author Maͫzͣaͬsͨ
 */
@Mojo(name = "jjtree", defaultPhase = LifecyclePhase.GENERATE_SOURCES, threadSafe = true)
public class JJTreeGoalMojo extends AbstractPluginMojo {
  
  /** The bean handling JJTree options as command line arguments. */
  private final JJTreeArgumentsBean jjtab = new JJTreeArgumentsBean();
  
  /**
   * The list of single full command line arguments, which should be in the form accepted by
   * JJTree/JavaCC.
   *
   * <p>
   * Arguments' values for <code>-CODE_GENERATOR</code>, <code>-GRAMMAR_ENCODING</code> and
   * <code>-JJTREE_OUTPUT_DIRECTORY</code> are read and used by the plugin; their default values
   * (i.e. if these options are not present) are <code>"Java"</code> (as for JJTree/JavaCC), system
   * property <code>file.encoding</code> (as for JJTree/JavaCC) and <code>
   * ${project.build.directory}/generated-sources/jjtree</code> (as for usual maven convention -
   * JJTree default value is <code>"."</code>).<br>
   *
   * <p>
   * The list will be passed as it is to JJTree, no control nor modification is done by the plugin,
   * except for <code>-JJTREE_OUTPUT_DIRECTORY</code>, whose value will be changed to an
   * intermediate temporary directory <code>${project.build.directory}/jjtree-nnnnnn</code>. The
   * given output directory value will be used at the end to copy the generated files from the
   * intermediate directory to it.
   *
   * <p>
   * The list has no default value (except for the 3 above options) (but JJTree/JavaCC itself
   * supplies default values).<br>
   * Note that among those 3 default values, the grammar encoding one (platform file encoding) may
   * be not adequate as most of the times the project is under an IDE and globally set to a platform
   * independent encoding like 'UTF-8'.
   *
   * <p>
   * Example:<br>
   *
   * <pre>{@code
   * <jjtreeCmdLineArgs>
   *   <arg>-NODE_EXTENDS="MyNode"</arg>
   *   <arg>-MuLtI=true</arg>
   *   <arg>-code_generator:"Java"</arg>
   *   <arg>-Grammar_Encoding="UTF-8"</arg>
   *   <arg>-JJTREE_OUTPUT_DIRECTORY="$}{{@code
   * project.build.directory
   * }}{@code /gen-src/jjt"</arg>
   * </jjtreeCmdLineArgs>
   * }</pre>
   *
   * <br>
   * Note that the <code>jjtreeCmdLineArgs</code> parameter is of type {@code List<String>}, which
   * implies that the inner tags names can have any names and may be appear many times (like
   * <code>arg</code> above).<br>
   * Note also that if the project has a parent which also configures the plugin with parameters (to
   * factorize them for the children for example), the child parameters will complement / replace
   * the parent's ones for those that are absent / present in the parent; so
   * <ul>
   * <li>if one wants to get rid of (all) the parent's ones, he must use the <code>
   * combine.self="override"</code> attribute at the list level in the child, and</li>
   * <li>if one wants to replace some of the parent's ones and add new ones in the child it is
   * recommended to use distinct tag names in the parent, the same tag names in the child for those
   * that must be replaced and another tag name or other tag names for the new ones.</li>
   * </ul>
   * See also
   * <a href= "https://github.com/javacc/javacc-maven-plugin#processor-parameters">Processor
   * parameters</a>.
   */
  @Parameter(property = "javacc.jjtreeCmdLineArgs") //
  protected List<String> jjtreeCmdLineArgs;
  
  @Override
  protected void initProcessor() throws PluginException {
    if (includes == null) {
      includes = new String[] {
          "**/*.jjt"
      };
    }
    if (sourceDirectory == null) {
      sourceDirectory = new File(project.getBasedir(), JJTreeArgumentsBean.defSrcSubDir);
      getLog().debug("sourceDirectory initialized to '" + sourceDirectory + "'");
    }
    
    // mojo's log injected after the mojo is constructed, so wait until now to pass it to the bean
    jjtab.log = getLog();
    jjtab.setProcCmdLineArgs(jjtreeCmdLineArgs);
    jjtab.findSpecificOptions(project, "jjtreeCmdLineArgs");
  }
  
  @Override
  protected void processGrammar(final GrammarInfo grammarInfo) throws ProcessorException {
    final String ts = String.valueOf(System.currentTimeMillis()).substring(6);
    final File jjtree = new File(project.getBuild().getDirectory(), "jjtree-" + ts);
    final File[] dirs = new File[] {
        jjtree
    };
    runProcessorOnGrammar(grammarInfo, dirs, true);
  }
  
  @Override
  protected void runProcessor(final File gram, final File[] dirs) throws ProcessorException {
    final JJTreeProcessor jjp = new JJTreeProcessor(getLog());
    jjp.inputFile = gram;
    jjp.intermediateOutputDirectories = dirs;
    jjp.intermedOutDirOptions = new String[] {
        JJTreeArgumentsBean.argOutDir
    };
    jjp.cmdLineArgs = jjtreeCmdLineArgs;
    jjp.run();
  }
  
  @Override
  protected String getGrammarFileEncoding() {
    return (jjtab.grammarFileEncodingOptionValue == null)
        ? AbstractArgumentsBean.defaultGrammarFileEncoding
        : jjtab.grammarFileEncodingOptionValue;
  }
  
  @Override
  protected Language getLanguage() {
    return jjtab.languageOptionValue == null ? AbstractArgumentsBean.defaultLanguage
        : jjtab.languageOptionValue;
  }
  
  @Override
  protected File[] getProcessorOutputDirectories() {
    return jjtab.processorOutputDirectories;
  }
  
  @Override
  protected File[] getGoalOutputDirectories() {
    return jjtab.processorOutputDirectories;
  }
}