# ScanDriver.java

    /*
    ** Module   : ScanDriver.java
    ** Abstract : processes an entire set of files using the AstGenerator
    **
    ** Copyright (c) 2004-2025, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --JPRM-- ----------------------------Description-----------------------------
    ** 001 GES 20050126   @19448 First version, with support for scanning
    **                           a list of files based on user input. Each
    **                           file in the entire subdirectory tree that
    **                           matches the user's file spec will be
    **                           preprocessed (if a cached version of the
    **                           preprocessor output doesn't already exist)
    **                           and then parsed.  The parse output will
    **                           be stored in a filename with the same base
    **                           as the input file + ".parse".  This is just
    **                           a harness to automate scanning of sets of
    **                           files.
    ** 002 GES 20050218   @19859 Added a try/catch inside the main processing
    **                           loop. This allows for any exception on a
    **                           given source file to be caught and the
    **                           scan to continue. This way a long-running
    **                           scan won't abort in the middle requiring
    **                           a restart of the entire scan.
    ** 003 GES 20050221   @19909 Added an option to control whether the
    **                           driver creates lexer output, parser output
    **                           and/or a persistent AST.
    ** 004 GES 20050224   @19947 Moved parser output processing and AST
    **                           persistence processing into AstGenerator.
    **                           This class is now only a command line
    **                           driver to run the AstGenerator on a user
    **                           specified list of source files AND with
    **                           user-specified control over generator
    **                           options.
    ** 005 GES 20050228   @20105 At the end of scanning, trigger a registry
    **                           save to ensure that any changes are
    **                           persisted.  Added a mode to rebuild the
    **                           registry from persisted ASTs.
    ** 006 GES 20060126   @24084 Added options for no recursion, silent mode
    **                           and an explicit file list mode.
    ** 007 GES 20070905   @34950 Modified the signature for processing files
    **                           to enable the file list to be maintained
    **                           downstream. This avoids some duplicate
    **                           processing when a file shows up as an E4GL
    **                           (HTML file) and its generated target file is
    **                           also in the file list. The generated target
    **                           file name must be removed from the list but
    **                           the scan driver does not know enough to do
    **                           that task. So it is delegated to the AST
    **                           generator.
    ** 008 GES 20090429   @42064 Match package and class name changes.
    ** 009 GES 20090515   @42229 Moved to AstManager from AstRegistry/AstPersister.
    ** 010 ECF 20160302          Enabled multiple scanning threads. Files may be processed out of
    **                           order, but this is safe. Also added an option to abort immediately on
    **                           error, but the default behavior is to continue processing on error.
    **                           Note that many errors (especially those encountered by the
    **                           SymbolResolver) are caught at a lower level and are not rethrown to
    **                           this level; these of course will not abort the scan.
    ** 011 ECF 20170213          When scanning with 1 thread, print name of file being scanned before
    **                           processing it. This guarantees the filename appears before any
    **                           messages about its processing.
    ** 012 ECF 20170302          Fixed remaining single threaded ordering issue.
    ** 013 GES 20170708          Pass silent flag down to the AST generator.
    ** 014 OM  20220405          XmlFilePlugin can load resources from application's jar.
    **     CA  20220501          Each profile has the same structure as the main 'global' config.  Allow multiple
    **                           profiles to be ran at once, with the conversion switching the state between
    **                           profiles, when a resource (like a file or namespace) is being processed.  Only
    **                           front phase is supported at this time.
    **     GES 20220516          Avoid duplicate parsing caused by recursive parsing of classes.
    **     CA  20220727          Improved memory management for parsing; cleanup is done in two phases:
    **                           1. after each legacy class file has finished parsing, the SchemaDictionary will
    **                              keep only protected temp-tables (all private tables are removed, as these
    **                              can't be reached from sub-classes; all permanent tables are removed, as each
    **                              class has its own reference to the permanent tables).  Also, the class def
    **                              instance will reduce its own used memory, which is not required when parsing
    **                              sub-classes.
    **                           2. after parsing of the entire file set is finished, any SchemaDictionary or
    **                              other ASTs referenced by the ClassDefinition are released.
    ** 015 GBB 20230512          Logging methods replaced by CentralLogger/ConversionStatus.
    ** 016 CA  20230801          Changed logging level from FINE to SEVERE for the 'Failure in file' stacktraces.
    ** 017 CA  20241118          Mark already parsed .cls file's ASTs as 'original' in the registry.
    ** 018 ES  20250327          Added sources from conversion list to SymbolResolver.
    **     ES  20250328          Added in conversion list the referenced sources which are not present in the
    **                           list.
    */

    /*
    ** 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.uast;

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

    import com.goldencode.ast.*;
    import com.goldencode.p2j.cfg.*;
    import com.goldencode.p2j.convert.*;
    import com.goldencode.p2j.util.*;

    /**
     * Provides a simple harness to scan a user-defined list of files and drive
     * preprocessing and parsing of each file.  Uses {@link AstGenerator} for
     * the majority of its function.  Preprocessing output will be cached and
     * reused as long as the cache file is newer than the input file.  This
     * significantly reduces the runtime of a multi-file scan. All files are
     * parsed each time (there is no reuse of parser output) and the output
     * is saved into one of more possible outputs (based on user controlled
     * options):
     * <p>
     * <ul>
     *     <li> the preprocessor can be invoked or bypassed
     *     <li> the preprocessor output can be cached for future use
     *     <li> the output of the lexer in a file that matches the base
     *          filename of the input file plus an appended '.lexer'.
     *     <li> a human readable report with a filename that matches the base
     *          filename of the input file plus an appended '.parse'.
     *     <li> a persisted AST that can be reconstituted for later use without
     *          re-running the lexer and parser, with a filename that matches the
     *          base filename of the input file plus an appended '.ast'.
     * </ul>
     * <p>
     * This program provides a command line interface.  See {@link #main} for the
     * syntax.
     * <p>
     * The scan is performed across multiple threads to reduce execution time. The API
     * allows for a fixed number of threads to be specified. If 0 threads are specified,
     * the scan driver will use one thread per available processor, as reported by
     * <code>Runtime.getRuntime().availableProcessors()</code>.
     * <p>
     * <strong>Important Note Regarding Multi-threading</strong><br>
     * In order to preserve the order of filenames written to the console, the writing of these names
     * is deferred until all the processing of a file, and of all those files submitted for processing
     * before it, has finished. This preserves the order of output of those filenames. However, since
     * most errors and warnings are written to stderr directly from the code which handles them, error
     * and warning information will display to the console <em>before</em> the name of the affected
     * file.
     * <p>
     * When running with a thread pool of size 1, the above does not apply. In this case, we enter a
     * stricter mode which forces the filename to appear before any messages associated with it.
     * This mode is particularly useful in the early stages of a conversion project, when working
     * through unstable or unknown code which is likely to produce errors or warnings. Using a single
     * thread naturally preserves the original order of the filenames.
     * <p>
     * When running with a thread pool larger than 1, the filename will appear below any messages as
     * noted above, but not necessarily immediately below. Several filenames and/or messages may be
     * written before the filename associated with the messages. Furthermore, the messages themselves
     * are not guaranteed to be written completely to stderr from one thread before another thread
     * interrupts with messages of its own. Therefore, it is best to use multiple threads only once
     * a source code set has stabilized and no longer produces errors and warnings during the scan
     * phase.
     * <p>
     * TODO: to fix this, we must overhaul the way error handling is performed by the classes invoked
     * by this class.
     */
    public class ScanDriver
    {
       /** Logger. */
       private static final ConversionStatus LOG = ConversionStatus.get(ScanDriver.class);

       /** Keeps track of the class of the files referenced in a source from conversion list. */
       private static Set<File> additionalFileList = Collections.synchronizedSet(new HashSet<>());

       /** Keeps track of the class of the files referenced in a source from conversion list. */
       private static Set<File> removedConversionFiles = Collections.synchronizedSet(new HashSet<>());

       /** Iterator over the conversion list. */
       private static ListIterator<File> iter = null;

       static
       {
          try
          {
             String name = Configuration.getParameter("registry");
             AstManager.initialize(new XmlFilePlugin(name, Configuration.isImportMode()));
          }
          catch (AstException ae)
          {
             // TODO: log this? get ready for null pointer exceptions later
          }
       }

       /**
        * Add to additionalFileList and to conversion list iterator a class referenced in of
        * sources from conversion list.
        *
        * @param    file
        *           Referenced 4gl source.
        */
       public static void addSource(File file)
       {
          if (!additionalFileList.contains(file))
          {
             additionalFileList.add(file);
             synchronized (iter)
             {
                iter.add(file);
                iter.previous();
             }
          }
       }

       /**
        * Remove from the conversion a buildin 4GL file.
        *
        * @param    file
        *           Referenced 4gl source.
        */
       public static boolean removeSource(File file)
       {
          return removedConversionFiles.add(file);
       }

       /**
        * Drive the scanning process for each of the given files in the list,
        * with preprocessing activated and caching of all output (preprocessor,
        * lexer, parser), persistence of ASTs and no registry rebuild.
        *
        * @param    files
        *           The list of files.  Must not be <code>null</code>.
        * @param    silent
        *           <code>false</code> if screen (stdout) output should be done.
        *           If <code>true</code>, normal screen output is suppressed and
        *           the only output occurs on failures (this is written to
        *           stderr).
        *
        * @throws   Exception
        *           in the event <code>abortOnError</code> is true and a failure occurs.
        */
       public static void scan(FileList files, boolean silent)
       throws Exception
       {
          // scan with default thread pool
          scan(files, silent, false, 1);
       }

       /**
        * Drive the scanning process for each of the given files in the list,
        * with preprocessing activated and caching of all output (preprocessor,
        * lexer, parser), persistence of ASTs and no registry rebuild.
        *
        * @param    files
        *           The list of files.  Must not be <code>null</code>.
        * @param    silent
        *           <code>false</code> if screen (stdout) output should be done.
        *           If <code>true</code>, normal screen output is suppressed and
        *           the only output occurs on failures (this is written to
        *           stderr).
        * @param    abortOnError
        *           <code>true</code> to end the scanning run if an error is encountered,
        *           else <code>false</code>.
        * @param    threads
        *           Number of threads to use for the scan. A non-positive value will enable
        *           the default behavior of a fixed size thread pool with one thread per
        *           available CPU. A positive value will create a fixed size thread pool of
        *           the given size.
        *
        * @throws   Exception
        *           in the event <code>abortOnError</code> is true and a failure occurs.
        */
       public static void scan(FileList files, boolean silent, boolean abortOnError, int threads)
       throws Exception
       {
          scan(files, true, true, true, true, true, silent, abortOnError, threads);
       }

       /**
        * Drive the scanning process for each of the given files in the list,
        * honoring the caching and output options provided.  In every case, the
        * lexer and parser will be run.  However there is control provided over
        * whether or not the preprocessor runs.  There is also very granular
        * control provided over output files and screen output.
        *
        * @param    files
        *           The list of files.  Must not be <code>null</code>.
        * @param    preproc
        *           <code>true</code> if the preprocessor should be run.
        * @param    cache
        *           <code>true</code> if the preprocessor output should be saved persistently.
        * @param    lex
        *           <code>true</code> if the lexer output should be saved persistently.
        * @param    parse
        *           <code>true</code> if the parser output should be saved persistently.
        * @param    persist
        *           <code>true</code> if the resulting AST files should be saved persistently.
        * @param    silent
        *           <code>false</code> if screen (stdout) output should be done.
        *           If <code>true</code>, normal screen output is suppressed and
        *           the only output occurs on failures (this is written to stderr).
        * @param    abortOnError
        *           <code>true</code> to end the scanning run if an error is encountered,
        *           else <code>false</code>.
        * @param    threads
        *           Number of threads to use for the scan. A value of zero will enable the
        *           default behavior of a fixed size thread pool with one thread per available
        *           CPU. A positive value will create a fixed size thread pool of the given
        *           size.
        *
        * @throws   Exception
        *           in the event <code>abortOnError</code> is true and a failure occurs.
        */
       public static void scan(FileList files,
                               boolean  preproc,
                               boolean  cache,
                               boolean  lex,
                               boolean  parse,
                               boolean  persist,
                               boolean  silent,
                               boolean  abortOnError,
                               int      threads)
       throws Exception
       {
          // setup
          File[] list = files.list();

          Set<String> fileCvtList = new HashSet<>();

          for (int i = 0; i < list.length; i++)
          {
             try
             {
                fileCvtList.add(list[i].getCanonicalPath());
             }
             catch (IOException exc)
             {
                LOG.log(Level.SEVERE, "", exc);
             }
          }

          SymbolResolver.setCvtSources(fileCvtList);

          // reorganize files as necessary to minimize redundant HTML processing
          list = AstGenerator.prioritize(list);

          // create a thread-safe list of files
          List<File> safeList = Collections.synchronizedList(new ArrayList<>(Arrays.asList(list)));

          // use all available processors if a hard thread limit was not specified
          if (threads == 0)
          {
             threads = Runtime.getRuntime().availableProcessors();
          }

          boolean singleThreaded = (threads == 1);

          // create a thread pool with an unbounded queue to process individual files in parallel
          ExecutorService exec = singleThreaded ? null : Executors.newFixedThreadPool(threads);

          // an object which will be created to report the result of processing each file
          class Result
          {
             String fileName = null;
             Exception error = null;
          }

          // queue of future results
          Queue<Future<Result>> results = new LinkedList<>();

          // process the list of files
          iter = safeList.listIterator();
          for (int i = 0; iter.hasNext(); i++)
          {
             File file = iter.next();

             // bypass entries that have been removed
             if (file == null)
             {
                continue;
             }

             // create and configure an AstGenerator for each file to be processed
             AstGenerator gen = new AstGenerator();
             gen.setSilent(silent);
             gen.setPreprocess(preproc);
             gen.setCache(cache);
             gen.setDumpLexer(lex);
             gen.setDumpParser(parse);
             gen.setAstPersist(persist);
             Configuration config = Configuration.getInstance();

             // need an effectively final variable for the list index for use within the lambda below
             int index = i;

             // lambda expression which processes the file on a separate thread and reports a result
             Callable<Result> job = () ->
             {
                Result result = new Result();

                File f = safeList.get(index);
                if (f != null)
                {
                   String fileName = result.fileName = f.toString();

                   config.withFileProfile(fileName);

                   try
                   {
                      boolean exists = AstManager.get().isExistingAst(AstGenerator.getAstName(fileName));

                      if (!silent && singleThreaded)
                      {
                         System.out.printf("%s%s\n", fileName, exists ? " (already parsed)" : "");
                      }

                      // only parse if the file has not been recursively parsed already
                      if (!exists)
                      {
                         // process the file, note that for HTML files the file list
                         // may be edited to remove the output file name that the E4GL
                         // preprocessor generates, if it is separately listed in
                         // the list (this avoids duplicate processing of that file
                         // since it was already processed fully to an AST when it was
                         // listed as an HTML file, it doesn't need to be processed
                         // again)
                         gen.processFile(fileName, safeList, index);
                      }
                      else
                      {
                         gen.markOriginal(fileName);
                      }
                   }
                   catch(Exception exc)
                   {
                      // some failures will raise an exception that is caught here; in other cases
                      // (notably within the SymbolResolver), exceptions are caught and reported,
                      // but not rethrown

                      if (singleThreaded)
                      {
                         if (!silent)
                         {
                            System.out.println("Failure in file '" + fileName + "':");
                         }

                         LOG.log(Level.SEVERE, "", exc);

                         if (abortOnError)
                         {
                            throw exc;
                         }
                      }
                      else
                      {
                         result.error = exc;
                      }
                   }
                }

                return result;
             };

             // if single-threaded, execute the lambda inline
             if (singleThreaded)
             {
                job.call();
             }
             else
             // submit a job for execution and queue its future result in the order submitted
             {
                results.add(exec.submit(job));
             }
          }

          // all jobs are now submitted; time to process the queue of results

          // ensure output to stream is in the correct order, even if the files were processed
          // out of order; this can make the output seem stilted when finished jobs are stacked
          // up behind an unfinished job
          for (Future<Result> future : results)
          {
             // wait for the job to finish
             Result result = future.get();

             // skip any jobs that were nulled out after being submitted, due to a race condition
             if (result.fileName == null)
             {
                continue;
             }

             if (!silent)
             {
                System.out.println(result.fileName);
             }

             // report any error and optionally terminate
             if (result.error != null)
             {
                if (!silent)
                {
                   System.out.println("Failure in file '" + result.fileName + "':");
                }

                LOG.log(Level.SEVERE, "", result.error);

                if (abortOnError)
                {
                   throw result.error;
                }
             }
          }

          if (!singleThreaded)
          {
             // all files have been processed; shut down the thread pool
             exec.shutdown();
          }

          iter = null;

          if (additionalFileList != null && additionalFileList.size() > 0)
          {
             files.addAdditionalFiles(additionalFileList);

             additionalFileList = null;
          }

          if (removedConversionFiles != null)
          {
             files.removeFiles(removedConversionFiles);
             removedConversionFiles = null;
          }

          // if changes have been made to the registry (by an AstGenerator)
          // then we need to persist the registry (if no changes have been
          // made, then this is essentially a no-op)
          AstManager.get().save();

          SymbolResolver.parseFinished();
       }

       /**
        * 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.FINE, msg);
          }

          String[] staticText =
          {
             "Syntax:",
             "",
             "   java ScanDriver [-QCLPXSANR] <directory> \"<filespec>\"",
             "   java ScanDriver -F[QCLPXSA] <filelist>",
             "",
             "Where",
             "",
             "   options:",
             "      Q = run preprocessor;",
             "      C = cache preprocessor output;",
             "      L = cache lexer output;",
             "      P = cache parser output;",
             "      X = save ASTs persistently",
             "      F = explicit file list mode",
             "      S = silent mode",
             "      A = abort on error",
             "      N = no recursion in filespec mode",
             "      R = registry rebuild (from persistent ASTs)",
             "",
             "   directory  = a relative or absolute path (only used",
             "                in filespec mode)",
             "   filespec   = file filter specification of the filenames in the",
             "                given directory to scan, should be enclosed in",
             "                double quotes if any of the wildcard characters *",
             "                or ? are used (only used in filespec mode)",
             "   filelist   = arbitrary list of absolute and/or relative file",
             "                names to scan",
             "",
             "By default, options QCLPX are active.  If any options are",
             "explicitly specified, all non-specified options are forced off."
          };

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

          System.exit(rc);
       }

       /**
        * Provides a command line interface for an end user to drive the
        * scanning harness.
        * <p>
        * Syntax:
        * <pre>
        *   java ScanDriver [-QCLPXSANR] &lt;directory&gt; &lt;&quot;filespec&quot;&gt;
        *   java ScanDriver -F[QCLPXSA] &lt;&quot;filelist&quot;&gt;
        * </pre>
        * Where:
        * <ul>
        *   <li> [-options] can be a hyphen followed by any combination of the
        *        letters 'QCLPXR' where
        *   <ul>
        *      <li> Q = run preprocessor
        *      <li> C = cache preprocessor output
        *      <li> L = cache lexer output
        *      <li> P = cache parser output
        *      <li> X = save ASTs persistently
        *      <li> F = interpret remaining args as an explicit file list
        *      <li> S = silent mode
        *      <li> A = abort on error",
        *      <li> N = no recursion in filespec mode
        *      <li> R = registry rebuild (from persistent ASTs)
        *   </ul>
        *   <li> filespec mode
        *   <ul>
        *      <li> &lt;directory&gt; is the directory to search for files
        *      <li> &quot;filespec&quot; is the file specification that will be
        *           used to create a list of files to process
        *   </ul>
        *   <li> explicit file list mode
        *   <ul>
        *      <li> &lt;filelist&gt; is arbitrary list of absolute and/or
        *           relative file names to scan
        *   </ul>
        * </ul>
        * <p>
        * By default, options QCLPX are active.  If any options are explicitly
        * specified, all non-specified options are forced off.
        *
        * @param   args
        *          List of command line arguments.
        */
       public static void main(String[] args)
       {
          // option defaults
          boolean preproc      = true;
          boolean cache        = true;
          boolean lex          = true;
          boolean parse        = true;
          boolean persist      = true;
          boolean recursion    = true;
          boolean silent       = false;
          boolean abortOnError = false;
          boolean explicit     = false;
          int     idx          = 0;

          // requires (directory and filespec) OR (option -F and a filelist)
          if (args.length < 2)
          {
             syntax("Missing command line parameters.", -1);
          }

          // process options
          if (args[0].startsWith("-"))
          {
             idx = 1;
             String opts = args[0].toLowerCase();

             if (opts.indexOf('q') != -1) preproc = true;
             else preproc = false;

             if (opts.indexOf('c') != -1) cache = true;
             else cache = false;

             if (opts.indexOf('l') != -1) lex = true;
             else lex = false;

             if (opts.indexOf('p') != -1) parse = true;
             else parse = false;

             if (opts.indexOf('x') != -1) persist = true;
             else persist = false;

             if (opts.indexOf('n') != -1) recursion = false;
             else recursion = true;

             if (opts.indexOf('s') != -1) silent = true;
             else silent = false;

             if (opts.indexOf('a') != -1) abortOnError = true;
             else abortOnError = false;

             if (opts.indexOf('f') != -1) explicit = true;
             else explicit = false;
          }

          FileList files = null;

          // at this point idx points to the next arg after the options
          if (explicit)
          {
             int      len      = args.length;
             String[] filelist = new String[len - idx];

             for (int j = idx; j < len; j++)
             {
                filelist[j - 1] = args[j];
             }

             files = new ExplicitFileList(filelist);
          }
          else
          {
             // options with filespec mode requires 3 args in total
             if (idx == 1 && args.length < 3)
             {
                syntax("Missing command line parameters.", -2);
             }

             files = new FileSpecList(new File(args[idx]),
                                      args[idx+1],
                                      recursion);
          }

          // drive the scanning
          try
          {
             scan(files, preproc, cache, lex, parse, persist, silent, abortOnError, 0);
          }
          catch (Exception exc)
          {
             // non-zero return to indicate error
             System.exit(1);
          }
       }
    }