# E4GLPreprocessor.java

    /*
    ** Module   : E4GLPreprocessor.java
    ** Abstract : driver for E4GL preprocessing
    **
    ** Copyright (c) 2007-2023, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --JPRM-- ----------------------------Description-----------------------------
    ** 001 GES 20070822   @34966 Provides a driver for E4GL preprocessing.
    ** 002 IAS 20160331          Fixed setOptions() method
    ** 003 GBB 20230512          Logging methods replaced by CentralLogger/ConversionStatus.
    */
    /*
    ** This program is free software: you can redistribute it and/or modify
    ** it under the terms of the GNU Affero General Public License as
    ** published by the Free Software Foundation, either version 3 of the
    ** License, or (at your option) any later version.
    **
    ** This program is distributed in the hope that it will be useful,
    ** but WITHOUT ANY WARRANTY; without even the implied warranty of
    ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    ** GNU Affero General Public License for more details.
    **
    ** You may find a copy of the GNU Affero GPL version 3 at the following
    ** location: https://www.gnu.org/licenses/agpl-3.0.en.html
    **
    ** Additional terms under GNU Affero GPL version 3 section 7:
    **
    **   Under Section 7 of the GNU Affero GPL version 3, the following additional
    **   terms apply to the works covered under the License.  These additional terms
    **   are non-permissive additional terms allowed under Section 7 of the GNU
    **   Affero GPL version 3 and may not be removed by you.
    **
    **   0. Attribution Requirement.
    **
    **     You must preserve all legal notices or author attributions in the covered
    **     work or Appropriate Legal Notices displayed by works containing the covered
    **     work.  You may not remove from the covered work any author or developer
    **     credit already included within the covered work.
    **
    **   1. No License To Use Trademarks.
    **
    **     This license does not grant any license or rights to use the trademarks
    **     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
    **     of Golden Code Development Corporation. You are not authorized to use the
    **     name Golden Code, FWD, or the names of any author or contributor, for
    **     publicity purposes without written authorization.
    **
    **   2. No Misrepresentation of Affiliation.
    **
    **     You may not represent yourself as Golden Code Development Corporation or FWD.
    **
    **     You may not represent yourself for publicity purposes as associated with
    **     Golden Code Development Corporation, FWD, or any author or contributor to
    **     the covered work, without written authorization.
    **
    **   3. No Misrepresentation of Source or Origin.
    **
    **     You may not represent the covered work as solely your work.  All modified
    **     versions of the covered work must be marked in a reasonable way to make it
    **     clear that the modified work is not originating from Golden Code Development
    **     Corporation or FWD.  All modified versions must contain the notices of
    **     attribution required in this license.
    */

    package com.goldencode.p2j.e4gl;

    import com.goldencode.p2j.convert.*;

    import java.io.*;
    import java.util.logging.*;

    /**
     * Provides a driver for E4GL preprocessing. This can be used via the
     * command line (see {@link #main} or via a programming API (see
     * {@link #preprocess}).
     */
    public class E4GLPreprocessor
    implements E4GLConstants
    {
       /** Logger */
       private static final ConversionStatus LOG = ConversionStatus.get(E4GLPreprocessor.class);

       /** Stores options that control the behavior of the preprocessor. */
       private Options opts = null;

       /**
        * Create a preprocess instance that can be used to handle the E4GL
        * preprocessing for a single HTML file and convert that file into a
        * valid 4GL source file. That source file will require normal 4GL
        * preprocessing before it is compilable source code.
        *
        * @param    input
        *           Filename of the input HTML file.
        */
       public E4GLPreprocessor(String input)
       {
          opts = new Options();
          opts.setInput(input);
       }

       /**
        * Create a preprocess instance that can be used to handle the E4GL
        * preprocessing for a single HTML file and convert that file into a
        * valid 4GL source file. That source file will require normal 4GL
        * preprocessing before it is compilable source code.
        *
        * @param    input
        *           Filename of the input HTML file.
        * @param    mode
        *           Compatibility mode controlling the code generation (see
        *           {@link E4GLConstants}).
        */
       public E4GLPreprocessor(String input, int mode)
       {
          this(input);
          opts.setMode(mode);
       }

       /**
        * Create a preprocess instance that can be used to handle the E4GL
        * preprocessing for a single HTML file and convert that file into a
        * valid 4GL source file. That source file will require normal 4GL
        * preprocessing before it is compilable source code.
        *
        * @param    input
        *           Filename of the input HTML file.
        * @param    mode
        *           Compatibility mode controlling the code generation (see
        *           {@link E4GLConstants}).
        * @param    other
        *           Manually specified options string.
        */
       public E4GLPreprocessor(String input, int mode, String other)
       {
          this(input, mode);

          if (other != null)
             opts.processWSOptions(other, false);
       }

       /**
        * Decode a compatibility mode name into the corresponding mode constant.
        * The decode is done on a case-insensitive basis.
        * <p>
        * The following mappings are implemented:
        * <p>
        * <pre>
        * Name                     Mode Constant
        * -----------------------  -----------------
        * ws                       WEBSPEED
        * webspeed                 WEBSPEED
        * ws_strict                WEBSPEED_STRICT
        * webspeed_strict          WEBSPEED_STRICT
        * bd                       BLUE_DIAMOND
        * blue_diamond             BLUE_DIAMOND
        * </pre>
        *
        * @param    name
        *           The mode name.
        *
        * @return   The mode constant or <code>UNKNOWN_MODE</code> if there is
        *           any problem.
        */
       public static int decodeMode(String name)
       {
          int mode = UNKNOWN_MODE;

          name = name.toLowerCase();

          if ("ws".equals(name) || "webspeed".equals(name))
          {
             mode = WEBSPEED;
          }
          else if ("ws_strict".equals(name) || "webspeed_strict".equals(name))
          {
             mode = WEBSPEED_STRICT;
          }
          else if ("bd".equals(name) || "blue_diamond".equals(name))
          {
             mode = BLUE_DIAMOND;
          }

          return mode;
       }

       /**
        * Read the HTML input file and convert it into 4GL source code.
        *
        * @param    scan
        *           <code>true</code> to scan the input file to resolve all
        *           options before the preprocessing occurs. <code>false</code>
        *           will bypass the scan and use the options that are already
        *           known.
        * @param    out
        *           An explicit stream to use as the destination for the
        *           preprocessed output. <code>null</code> means that the
        *           preprocessor should create its own stream using the current
        *           output filename OR if that filename is <code>null</code>,
        *           then the preprocessor will calculate the output filename
        *           (see {@link #calculateOutputDest}) and then create a
        *           stream to use.
        */
       public void preprocess(boolean scan, PrintStream out)
       {
          E4GLParser parser = null;

          if (scan)
          {
             scanForWSOptions();
          }

          if (out == null)
          {
             if (opts.getOutput() == null)
             {
                calculateOutputDest();
             }

             out = getOutputDest();
          }

          try
          {
             parser = getParser();
             parser.preprocess(opts, out);
          }

          catch (Exception exc)
          {
             LOG.log(Level.SEVERE, "", exc);
          }

          finally
          {
             try
             {
                if (parser != null)
                   parser.closeInput();

                out.close();
             }

             catch (Exception exc)
             {
                // ignore I/O failures
             }
          }
       }

       /**
        * Create a new instance of the E4GL parser based on previously set
        * configuration. After the caller is finsihed using the parser, it is
        * important to use {@link E4GLParser#getInput} and to call
        * <code>close</code> on that stream. Neither the parser nor the lexer
        * (that is being used by the parser) close the input stream when
        * complete.  It is the caller's responsibility to do this!
        *
        * @return   A fully functional parser ready for processing.
        */
       public E4GLParser getParser()
       {
          E4GLParser parser = null;

          try
          {
             FileInputStream file  = new FileInputStream(opts.getInput());
             DataInputStream data  = new DataInputStream(file);
             E4GLLexer       lexer = new E4GLLexer(data);

             parser = new E4GLParser(lexer);
             parser.setInput(data);
          }

          catch(Exception excpt)
          {
             LOG.log(Level.WARNING,"", excpt);
          }

          return parser;
       }

       /**
        * Scan the input file to parse out any options that are encoded in
        * &lt;META&gt; or &lt;!--WSMETA--&gt; elements. These options will be
        * normalized, cleaned up and merged with any manually specified options.
        * The result can be used to direct the E4GL preprocessing run. Access
        * the options via {@link #getOptions}.
        */
       public void scanForWSOptions()
       {
          E4GLParser parser = null;

          try
          {
             parser = getParser();
             parser.scan_options(opts);
          }

          catch(Exception excpt)
          {
             LOG.log(Level.WARNING,"", excpt);
          }

          finally
          {
             if (parser != null)
                parser.closeInput();
          }
       }

       /**
        * Use the input filename and the known options to calculate the filename
        * for the output destination. It is critical that all options have been
        * determined/resolved before this method is called. To the extent that
        * the options are dependent upon encoded META or WSMETA elements in the
        * HTML input file, it is important to use {@link #scanForWSOptions}
        * before calling this method. The resulting filename will be stored in
        * the current options for downstream usage by the preprocessor.
        *
        * @return   The calculated filename.
        */
       public String calculateOutputDest()
       {
          StringBuilder sb = new StringBuilder();

          String input = opts.getInput();
          int    idx   = input.lastIndexOf(".");

          // remove the file extension if present
          sb.append((idx != -1) ? input.substring(0, idx) : input);

          // add our output extension based on our options
          sb.append(opts.getCompatibilityHelper().suffix());

          String dest = sb.toString();
          opts.setOutput(dest);

          return dest;
       }

       /**
        * Access the specified and/or calculated options for preprocessing the
        * current input file.
        *
        * @return   The options for this preprocessor instance.
        */
       public Options getOptions()
       {
          return opts;
       }

       /**
        * Replace the specified and/or calculated options for preprocessing the
        * current input file. WARNING: this will erase any state set by previous
        * calls to {@link #scanForWSOptions} and/or manually set options strings
        * in the preprocessor constructor.
        *
        * @param    options
        *           The options for this preprocessor instance.
        */
       public void setOptions(Options options)
       {
          this.opts = options;
       }

       /**
        * Access the specified input filename.
        *
        * @return   A filename.
        */
       public String getInput()
       {
          return opts.getInput();
       }

       /**
        * Set the specified input filename.
        *
        * @param    input
        *           A filename.
        */
       public void setInput(String input)
       {
          opts.setInput(input);
       }

       /**
        * Access the calculated output filename.
        *
        * @return   A filename.
        */
       public String getOutput()
       {
          return opts.getOutput();
       }

       /**
        * Set the specified output filename. WARNING: this will override any
        * previously calculated output filename.
        *
        * @param    output
        *           A filename.
        */
       public void setOutput(String output)
       {
          opts.setOutput(output);
       }

       /**
        * Gets the operating system name which must be used to determine how
        * escape characters are treated.
        *
        * @return   The operating system name.
        */
       public String getOpsys()
       {
          return opts.getOpsys();
       }

       /**
        * Sets the operating system name which must be used to determine how
        * escape characters are treated.
        *
        * @param    opsys
        *           The new operating system name.
        */
       public void setOpsys(String opsys)
       {
          opts.setOpsys(opsys);
       }

       /**
        * Get a stream that outputs to the file specified by the current output
        * filename (see {@link #getOutput}). The target file will be removed if
        * it exists.
        *
        * @return   A stream or <code>null</code> if any failure occurs.
        */
       public PrintStream getOutputDest()
       {
          PrintStream out = null;

          File dest = new File(opts.getOutput());

          try
          {
             // if this filename exists as a directory, we can't save results
             if (!dest.isDirectory())
             {
                // remove the target file if it exists.
                if (dest.exists())
                {
                   dest.delete();
                }

                out = new PrintStream(new FileOutputStream(dest));
             }
          }

          catch (Exception excpt)
          {
             LOG.log(Level.WARNING,"", excpt);
          }

          return out;
       }

       /**
        * Emit a syntax statement and optional error message to
        * <code>stderr</code>, then exit the process with the specified return
        * code. This method is called from {@link #main} when the user passes
        * command line parameters which are not viable.
        *
        * @param    msg
        *           Optional error message; may be <code>null</code>.
        * @param    rc
        *           Process return code used with <code>System.exit()</code>.
        */
       private static void syntax(String msg, int rc)
       {
          if (msg != null)
          {
             LOG.log(Level.SEVERE, msg);
          }

          String[] staticText =
          {
             "",
             "---------------------E4GL Preprocessor Driver---------------------",
             "",
             "Reads HTML files (with Embedded 4GL) and converts them into 4GL",
             "source code (procedure or include files).",
             "",
             "Syntax:",
             "",
             "   java E4GLPreprocessor [-options] <file>",
             "",
             "Where",
             "",
             "   options",
             "",
             "      C<mode>   = specify the compatibility mode where <mode>:",
             "                  - WS or WEBSPEED",
             "                  - WS_STRICT or WEBSPEED_STRICT",
             "                  - BD or BLUE_DIAMOND",
             "      O<wsopts> = manually add an options string to augment",
             "                  that found by scanning for 'wsoptions' in",
             "                  <META> or <!--WSMETA--> HTML elements",
             "      S         = scan for wsoptions and content-type meta-data,",
             "                  dump the results to STDOUT and then exit without",
             "                  any further processing",
             "",
             "   file    = absolute and/or relative filename of the file",
             "             to process",
             ""
          };

          for (int i = 0; i < staticText.length; i++)
          {
             LOG.log(Level.CONFIG, staticText[i]);
          }

          System.exit(rc);
       }

       /**
        * Command line driver for E4GL preprocessing. Reads HTML files
        * (containing Embedded 4GL) and converts them into 4GL source code
        * (procedure or include files).
        * <p>
        * Syntax:
        * <pre>
        * java E4GLPreprocessor [-options] &lt;file&gt;
        * </pre>
        * Where:
        * <ul>
        *   <li> Options
        *   <ul>
        *      <li> C&lt;mode&gt; to specify the compatibility mode where
        *           &lt;mode&gt;
        *      <ul>
        *         <li> WS or WEBSPEED
        *         <li> WS_STRICT or WEBSPEED_STRICT",
        *         <li> BD or BLUE_DIAMOND",
        *      </ul>
        *      <li> O&lt;wsopts&gt; = manually add an options string to augment
        *           that found by scanning for 'wsoptions' in &lt;META&gt; or
        *           &lt;!--WSMETA--&gt; HTML elements
        *      <li> S = scan for wsoptions and content-type meta-data,
        *           dump the results to STDOUT and then exit without any
        *           further processing
        *   </ul>
        *   <li> file = absolute and/or relative filename of the file to process
        * </ul>
        *
        * @param    args
        *           Command line parameters. Use matching double quotes to
        *           pass any argument that requires special characters or
        *           whitespace.
        */
       public static void main(String[] args)
       {
          // option defaults
          boolean scanonly  = false;
          String  wsoptions = null;
          int     mode      = DEFAULT_MODE;
          int     idx       = 0;

          if (args.length < 1)
          {
             syntax("Missing <file> parameter.", -1);
          }

          // process options
          while (idx < args.length && args[idx].startsWith("-"))
          {
             String  parm  = null;
             boolean known = false;

             char opt = args[idx].charAt(1);

             if (opt == 's' || opt == 'S')
             {
                scanonly = true;
                known = true;
             }

             // all other options need more than 2 chars
             if (!known)
             {
                if (args[idx].length() < 3)
                   syntax(String.format("Invalid option '%s'.", args[idx]), -2);
                else
                   parm = args[idx].substring(2);
             }

             if (opt == 'c' || opt == 'C')
             {
                known = true;
                mode = E4GLPreprocessor.decodeMode(parm);

                if (mode == UNKNOWN_MODE)
                {
                   syntax("Unknown compatibility mode.", -3);
                }
             }

             if (opt == 'o' || opt == 'O')
             {
                known = true;
                char first = parm.charAt(0);
                char last  = parm.charAt(parm.length() - 1);

                // strip off any surrounding quotes
                if (first == '\"' || first == '\'')
                   parm = parm.substring(1);
                if (last == '\"' || last == '\'')
                   parm = parm.substring(0,parm.length() - 1);

                wsoptions = parm;
             }

             if (!known)
                syntax(String.format("Unknown option '%s'.", args[idx]), -4);

             idx++;
          }

          // there must be at least 1 arg left
          if (idx >= args.length)
          {
             syntax("Missing <file> parameter.", -5);
          }

          // drive processing
          try
          {
             E4GLPreprocessor preproc = new E4GLPreprocessor(args[idx],
                                                             mode,
                                                             wsoptions);

             if (scanonly)
             {
                preproc.scanForWSOptions();
                System.out.println(preproc.getOptions().toString());
             }
             else
             {
                preproc.preprocess(true, null);
             }
          }

          catch(Exception excpt)
          {
             LOG.log(Level.SEVERE, "", excpt);
          }
       }
    }