JTBJavaCCGoalMojo.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.Arrays;
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>jtb-javacc</b> goal, for generating the tree and the parser files from a JTB grammar.<br>
 * It searches the source directory for all grammar files and run JTB then JavaCC 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 for each processor 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 JTB and JavaCC options can be found on the
 * <a href="https://github.com/jtb-javacc/JTB/blob/master/doc/wiki/How_to_use.md">JTB wiki</a> and
 * 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/jtb-javacc">JTB at
 * GitHub</a> and <a href="https://github.com/javacc">JavaCC at GitHub</a>.
 * 
 * @since 3.8.0
 * @author Maͫzͣaͬsͨ
 */
@Mojo(name = "jtb-javacc", defaultPhase = LifecyclePhase.GENERATE_SOURCES, threadSafe = true)
public class JTBJavaCCGoalMojo extends AbstractPluginMojo {
  
  /** The bean handling JTB options as command line arguments. */
  private final JTBArgumentsBean jtbab = new JTBArgumentsBean();
  
  /** The bean handling JavaCC options as command line arguments. */
  private final JavaCCArgumentsBean jjab = new JavaCCArgumentsBean();
  
  /**
   * @see JTBGoalMojo#jtbCmdLineArgs
   */
  @Parameter(property = "javacc.jtbCmdLineArgs") //
  protected List<String> jtbCmdLineArgs;
  
  /**
   * @see JavaCCGoalMojo#javaccCmdLineArgs
   */
  @Parameter(property = "javacc.javaccCmdLineArgs") //
  protected List<String> javaccCmdLineArgs;
  
  @Override
  protected void initProcessor() throws PluginException {
    if (includes == null) {
      // note-jacoco: could add a test case without specific include, would be quite worthless
      includes = new String[] {
          "**/*.jtb"
      };
    }
    if (sourceDirectory == null) {
      sourceDirectory = new File(project.getBasedir() + "/src/main/jtb");
      getLog().debug("sourceDirectory initialized to '" + sourceDirectory + "'");
    }
    
    // jtb
    jtbab.log = getLog();
    jtbab.setProcCmdLineArgs(jtbCmdLineArgs);
    jtbab.findSpecificOptions(project, "jtbCmdLineArgs");
    
    // javacc
    jjab.log = getLog();
    jjab.setProcCmdLineArgs(javaccCmdLineArgs);
    jjab.findSpecificOptions(project, "javaccCmdLineArgs");
    
    // check for consistency
    checkConsistentSpecificOptions(jtbab, jjab, "jtb");
  }
  
  /** An index to memorize which processor is the current one in use. */
  int processorIndex;
  
  @Override
  protected void processGrammar(final GrammarInfo grammarInfo) throws ProcessorException {
    // JTB
    String ts = String.valueOf(System.currentTimeMillis()).substring(6);
    final File jtb = new File(project.getBuild().getDirectory(), "jtb-" + ts);
    final File jjtk = new File(project.getBuild().getDirectory(), "jjtk-" + ts);
    File[] dirs = new File[] {
        jtb, jjtk
    };
    processorIndex = 1;
    runProcessorOnGrammar(grammarInfo, dirs, true);
    // JavaCC
    final GrammarInfo jjGrammarInfo = grammarInfo.deriveJJ(getProcessorOutputDirectories()[0]);
    ts = String.valueOf(System.currentTimeMillis()).substring(6);
    final File javacc = new File(project.getBuild().getDirectory(), "javacc-" + ts);
    dirs = new File[] {
        javacc
    };
    processorIndex = 2;
    runProcessorOnGrammar(jjGrammarInfo, dirs, false);
  }
  
  @Override
  protected void runProcessor(final File gram, final File[] dirs) throws ProcessorException {
    if (processorIndex == 1) {
      final JTBProcessor jjp = new JTBProcessor(getLog());
      jjp.inputFile = gram;
      jjp.intermediateOutputDirectories = dirs;
      jjp.intermedOutDirOptions = new String[] {
          JTBArgumentsBean.argOutDir, JavaCCArgumentsBean.argOutDir
      };
      jjp.cmdLineArgs = jtbCmdLineArgs;
      jjp.run();
    } else {
      final JavaCCProcessor jjp = new JavaCCProcessor(getLog());
      jjp.inputFile = gram;
      jjp.intermediateOutputDirectories = dirs;
      jjp.intermedOutDirOptions = new String[] {
          JavaCCArgumentsBean.argOutDir
      };
      jjp.cmdLineArgs = javaccCmdLineArgs;
      jjp.run();
    }
  }
  
  /** The array of goal's output directories. */
  private File[] goalOutputDirectories = null;
  
  /**
   * Returns the option set in the <code>jtb</code> arguments, or if none the option set in the
   * <code>javacc</code> arguments, or if none the default value.
   */
  @Override
  protected String getGrammarFileEncoding() {
    final String jtbGFE = jtbab.grammarFileEncodingOptionValue;
    final String jjGFE = jjab.grammarFileEncodingOptionValue;
    if (jtbGFE != null) {
      return jtbGFE;
    }
    if (jjGFE != null) {
      return jjGFE;
    }
    return AbstractArgumentsBean.defaultGrammarFileEncoding;
  }
  
  /**
   * Returns the option set in the <code>jtb</code> arguments, or if none the option set in the
   * <code>javacc</code> arguments, or if none the default value.
   */
  @Override
  protected Language getLanguage() {
    final Language jtbLang = jtbab.languageOptionValue;
    final Language jjLang = jjab.languageOptionValue;
    if (jtbLang != null) {
      return jtbLang;
    }
    if (jjLang != null) {
      return jjLang;
    }
    return AbstractArgumentsBean.defaultLanguage;
  }
  
  @Override
  protected File[] getProcessorOutputDirectories() {
    if (processorIndex == 1) {
      return jtbab.processorOutputDirectories;
    } else {
      return jjab.processorOutputDirectories;
    }
  }
  
  @Override
  protected File[] getGoalOutputDirectories() {
    if (goalOutputDirectories == null) {
      goalOutputDirectories = Arrays.copyOf(jtbab.processorOutputDirectories,
          jtbab.processorOutputDirectories.length + jjab.processorOutputDirectories.length);
      System.arraycopy(jjab.processorOutputDirectories, 0, goalOutputDirectories,
          jtbab.processorOutputDirectories.length, jjab.processorOutputDirectories.length);
    }
    return goalOutputDirectories;
  }
}