# CallGraphGenerator.java

    /*
    ** Module   : CallGraphGenerator.java
    ** Abstract : drives the pattern engine service to create a call graph for one or more root
    **            entry points
    **
    ** Copyright (c) 2005-2024, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --JPRM-- ----------------------------Description------------------------------
    ** 001 GES 20050314   @20313 Created initial version that provides support for command line
    **                           processing based on a root node list provided in an XML file or via
    **                           command line arguments.  This is an alternate, special-purpose
    **                           driver for the pattern engine.
    ** 002 GES 20050616   @21334 Updates to use a modified pattern engine interface that fully
    **                           enables using the same profile and pattern engine instance for
    **                           multiple invocations of the walk (one for each root node).
    **                           Previously, this was broken but now it works.
    ** 003 GES 20060130   @24137 Moved core logic into a callable method and added support for debug
    **                           level.  Simplified the command line.
    ** 004 GES 20090429   @42059 Match package and class name changes.
    ** 005 CA  20140313          Moved to a graph DB implementation.  When call-sites are found, uses
    **                           the external-program AST as source and the edge between the source
    **                           AST and the target is annotated with call-site info.
    ** 006 CA  20140408          Fixed filename matching for source-code originating from a different
    **                           OS than the one where conversion is ran.
    ** 007 CA  20140715          The schema files are loaded from the data/namespace folder, not from
    **                           the project basepath.
    ** 008 GES 20170424          Changes for call graph analysis v3.
    ** 009 CA  20180413          Made the GraphML output optional (via the '-g' argument).
    **     CA  20180503          Fixed graph access for update mode.
    ** 010 GBB 20230512          Logging methods replaced by CentralLogger/ConversionStatus.
    ** 011 TJD 20240221          Updated calls to Graph serialization into graphml
    ** 012 GBB 20240826          Moving FileSystemDaemon to osresource package.
    */

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

    import com.goldencode.ast.*;
    import com.goldencode.p2j.cfg.*;
    import com.goldencode.p2j.convert.*;
    import com.goldencode.p2j.pattern.*;
    import com.goldencode.p2j.util.osresource.*;
    import org.apache.tinkerpop.gremlin.structure.*;
    import org.apache.tinkerpop.gremlin.process.traversal.*;

    /**
     * Special-purpose driver for the pattern engine to create a new call graph for one or more root
     * entry points.  This provides a command line interface, see {@link #main} for the syntax.
     */
    public class CallGraphGenerator
    {
       /** Logger. */
       private static final ConversionStatus LOG = ConversionStatus.get(CallGraphGenerator.class);

       /**
        * Update the ambiguous external programs and the explicit list of external programs provided
        * as argument, by re-checking if the ambiguous call-sites can be disambiguated from the hints.
        *
        * @param    rootNodes
        *           List of Progress AST filenames to use as root nodes or
        *           <code>null</code> to use the projects defined root node list
        *           (see {@link RootNodeList}).
        * @param    debug
        *           Debug level.
        */
       public static void updateGraphs(String[] rootNodes, int debug)
       throws ConfigurationException,
              AstException
       {
          CallGraphWorker.getGraph();
          PatternEngine.setDebugLevel(debug);

          Iterator<Vertex> walker = CallGraphWorker.newUpdateCallGraphIterator(rootNodes, false);
          walkTargets(walker, "callgraph/generate_call_graph");

          walker = CallGraphWorker.newUpdateCallGraphIterator(rootNodes, false);
          walkTargets(walker, "callgraph/generate_call_graph_post");
       }

       /**
        * Implements the core of the call graph generation logic, a call graph
        * will be generated for each of the root nodes passed. For a root node
        * named <code>filename.p.ast</code>, the resulting call graph will be
        * named <code>filename.p.ast.graph</code>.
        *
        * @param    rootNodes
        *           List of Progress AST filenames to use as root nodes or
        *           <code>null</code> to use the projects defined root node list
        *           (see {@link RootNodeList}).
        * @param    debug
        *           Debug level.
        */
       public static void generateGraphs(String[] rootNodes, int debug)
       throws ConfigurationException,
              AstException,
              IOException
       {
          String folder = Configuration.getParameter("callgraph-db-folder",
                                                     CallGraphHelper.DEFAULT_CALLGRAPH_DB_FOLDER);
          File f = new File(folder);
          if (f.exists())
          {
             FileSystemDaemon.delete(f, true);
          }

          CallGraphWorker.getGraph();
          PatternEngine.setDebugLevel(debug);

          boolean projectRelative = false;

          if (rootNodes == null)
          {
             // read root node list from XML
             RootNodeList rnl = new RootNodeList();
             rootNodes = rnl.getList();

             projectRelative = true;
          }

          String basePath = Configuration.getParameter("basepath", ".");
          PatternEngine pe = new PatternEngine(basePath, "*.ast");

          // Initialize the graph by loading the entire code-set. For each added external program,
          // create its include graph, too. This is temporary until TRPL is changed so that is backed
          // by a graph DB: at that point, this step will no longer be necessary.
          System.out.println("Started loading the code-set.");
          pe.run("callgraph/load_code_set");
          pe.run("callgraph/load_code_set_post");
          System.out.println("Done loading the code-set.");

          // mark all the programs referenced as triggers by the schema; this is similar to how
          // RUN filename. is disambiguated
          pe = new PatternEngine("data/namespace", "*.schema");
          System.out.println("Started loading the schema-triggers.");
          pe.run("callgraph/load_schema_triggers");
          System.out.println("Done loading the schema-triggers.");

          Iterator<Vertex> walker = CallGraphWorker.newCallGraphIterator(rootNodes, projectRelative);
          walkTargets(walker, "callgraph/generate_call_graph");

          walker = CallGraphWorker.newCallGraphIterator(rootNodes, projectRelative);
          walkTargets(walker, "callgraph/generate_call_graph_post");
       }

       /**
        * Walk the targets specified by the given iterator, in batches, until the iterator no longer
        * provides any new targets.
        *
        * @param    walker
        *           The target provider.
        * @param    profile
        *           The profile to be applied against the targets.
        */
       private static void walkTargets(Iterator<Vertex> walker, String profile)
       throws AstException,
              ConfigurationException
       {
          PatternEngine pe = null;

          while (walker.hasNext())
          {
             pe = new PatternEngine();

             while (walker.hasNext())
             {
                Vertex what = walker.next();

                // this is restricted only to external programs for now, so is OK to search for a
                // filename
                String name = what.<String>property("os-filename").value();

                // gather all the AST files, to apply the rules in batches.
                pe.addTarget(name + ".ast");

                // TODO: when we an internal entry is targeted, we will need to walk the AST tree
                // rooted at the internal entry, not the entire external program.  for this case, it
                // is needed to 1. load the external AST into memory 2. position on the AST targeted
                // by the specified ID.
             }

             pe.run(profile);
          }
       }

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

          String[] staticText =
          {
             "Syntax:",
             "",
             "   java CallGraphGenerator [-g] [-Dn] [-u] [root_node_list]",
             "",
             "Where",
             "",
             "   options:",
             "      -g persist the graph in GraphML format, in file callgraph.graphml",
             "      -Dn  set the debug level to 'n' (0 = none, 1 = status,",
             "          2 = debug, 3 = trace)",
             "      -u sets the callgraph processing in update mode, where " +
             "         only the ambiguous nodes and their newly-resolved " +
             "         targets are processed.  The targets specified in the " +
             "         root_nodes_list are ensured to be updated and must " +
             "         already exist in the callgraph.",
             "",
             "   root_node_list = list of absolute and/or relative Progress",
             "                    AST file names to use as root nodes (e.g.",
             "                    filename.p.ast). These will be marked as" +
             "                    entry-points only if not in update mode.",
             "",
             "By default, the debug level is set to status (1) and the",
             "project's defined root node list is used."
          };

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

          System.exit(rc);
       }

       /**
        * Provides a command line interface for an end user to drive the pattern
        * engine for the purpose of building one or more call graphs.
        * <p>
        * Syntax:
        * <pre>
        *   java CallGraphGenerator [-Dn] [-u] [root_nodes...]
        * </pre>
        * Where:
        * <ul>
        *   <li> -g saves the graph in GraphML format</li>
        *   <li> -Dn sets the pattern engine debug level where <code>n</code> is
        *        the numeric setting of the level (default is 1):
        *      <ul>
        *         <li> none (0)
        *         <li> status (1)
        *         <li> debug (2)
        *         <li> trace (3)
        *      </ul>
        *   <li>-u sets the callgraph processing in update mode, where only the ambiguous nodes and
        *       their newly-resolved targets are processed.  The targets specified in the root_nodes
        *       list are ensured to be updated and must already exist in the callgraph.
        *
        *   <li> [root_nodes...] is one or more root node filenames to process
        *        (which must be valid relative or absolute names based on the
        *        current directory)
        * </ul>
        * <p>
        * Debug level is set to status (1) by default.
        * <p>
        * If no root nodes are listed on the command line, then the project's
        * root node list will be used ({@link RootNodeList}).
        *
        * @param   args
        *          List of command line arguments.
        */
       public static void main(String[] args)
       {
          String[] list  = null;
          int      debug = PatternEngine.MSG_STATUS;
          int      idx   = 0;
          boolean  update = false;

          // argument processing (all args are optional)
          if (args.length > 0)
          {
             if (args[idx].equalsIgnoreCase("-g"))
             {
                CallGraphWorker.getGraph().traversal().io("callgraph.graphml").with(IO.writer, IO.graphml).write().iterate();
                return;
             }

             // display help
             if (args[idx].equals("?") || args[idx].equals("-?"))
             {
                // this doesn't return
                syntax(null, 0);
             }

             // check for debug option
             if (args[idx].toLowerCase().startsWith("-d"))
             {
                char level = args[idx].charAt(2);

                if (Character.isDigit(level))
                {
                   debug = Character.digit(level, 10);

                   if (debug < PatternEngine.MSG_NONE ||
                       debug > PatternEngine.MSG_TRACE)
                   {
                      syntax("Invalid debug level.", -1);
                   }
                }
                else
                {
                   syntax("Debug level must be a numeric digit from " +
                          PatternEngine.MSG_NONE + " - " +
                          PatternEngine.MSG_TRACE + ".",
                          -2);
                }

                idx++;
             }

             // the idx value may have increased above
             if (args.length > idx && args[idx].toLowerCase().equals("-u"))
             {
                update = true;
                idx++;
             }

             // process an explicit list of root nodes
             if (args.length > idx)
             {
                List<String> strings = new ArrayList<>();

                // any other args are filenames
                for (int i = idx; i < args.length; i++)
                {
                   strings.add(args[i]);
                }

                list = strings.toArray(new String[0]);
             }
          }

          try
          {
             if (update)
             {
                updateGraphs(list, debug);
             }
             else
             {
                generateGraphs(list, debug);
             }
          }
          catch (Exception excpt)
          {
             LOG.log(Level.SEVERE, "", excpt);
          }
          finally
          {
             CallGraphHelper.shutdownGraph();
          }
       }
    }