# NameMappingWorker.java

    /*
    ** Module   : NameMappingWorker.java
    ** Abstract : maps Progress source names to Java class names
    **
    ** Copyright (c) 2006-2024, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --JPRM-- ----------------Description-----------------
    ** 001 GES 20060201   @24183 Created initial version. Builds up maps
    **                           which it uses to initialize and operate
    **                           the SourceNameMapper runtime class.
    ** 002 GES 20060313   @25047 Some helpers to facilitate incremental name
    **                           map processing (where only a subset of files
    **                           can be processed at a time but they can
    **                           still resolve classnames for files that were
    **                           previously converted.
    ** 003 GES 20060319   @25113 Added helpers for external procedure
    **                           signature processing.
    ** 004 GES 20101111          Pass conversion-time cfg values to convertName().
    ** 005 GES 20111213          resolveJavaClass returns null instead of throwing
    **                           exception if the class was not found (4GL allows
    **                           missing program name for RUN statements).
    ** 006 CA  20121220          Changes caused by SourceNameMapper API changes.
    ** 007 CS  20130304          Added classNameExists function which checks if a
    **                           class with the specified name exists in the given
    **                           package.
    ** 008 EVL 20130530          Removing propath member from class and overwriteMappingData() call.
    **                           The method resolveJavaClass() has also been removed because it is
    **                           never used anymore.
    ** 009 CA  20200412          Added incremental conversion support.
    ** 010 VVT 20240826          SourceNameMapper.convertJavaProg() renamed. See #8613-16.
    */
    /*
    ** 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.convert;

    import java.io.*;
    import java.util.*;
    import java.util.function.*;

    import org.w3c.dom.*;

    import com.goldencode.p2j.cfg.*;
    import com.goldencode.p2j.pattern.*;
    import com.goldencode.p2j.util.*;
    import com.goldencode.p2j.xml.*;
    import com.goldencode.util.*;

    /**
     * Gathers the mappings of Progress source names to Java class names and then
     * uses these maps to initialize and operate the <code>SourceNameMapper</code>
     * runtime class.  This allows <code>RUN FILENAME</code> constructs to be
     * mapped into the correct target classname.
     * <p>
     * This class only gathers the minimum data required to operate the
     * <code>convertName</code> method of the <code>SourceNameMapper</code>
     * class.  This minimizes the work and code needed.  In the future, it
     * would be easily possible to gather the method level data which would
     * allow additional Progress procedure/function to Java method mapping.
     */
    public class NameMappingWorker
    extends AbstractPatternWorker
    {
       /** Base path for all Java packages. */
       private String pkgroot = null;

       /** Base path for 4GL code. */
       private String proroot = null;

       /** Name element separator used for the Progress file name data. */
       private String fileSep = null;

       /** Path separator used for the Progress file name data. */
       private String pathSep = null;

       /** Case sensitivity flag for the Progress file name data. */
       private boolean caseSens;

       /** Flag to remember if we have initialized the source name mapper. */
       private boolean initialized = false;

       /**
        * The in-memory mappings for all programs (previously converted or not).  For incremental
        * conversion, this will load an existing name_map.xml file.
        */
       private Map<String, ProgramInfo> programs = new LinkedHashMap<>();

       /**
        * Default constructor which defines the symbol libraries to be registered.
        */
       public NameMappingWorker()
       {
          super();
          setLibrary(new Library());

          // query base config values
          pkgroot  = Configuration.getParameter("pkgroot", "");
          proroot  = Configuration.getParameter("basepath", "./");
          fileSep  = Configuration.getParameter("file-separator", "/");
          pathSep  = Configuration.getParameter("path-separator", ":");

          String cs = Configuration.getParameter("case-sensitive", "true");
          caseSens = cs.equalsIgnoreCase("true");
       }

       /**
        * Class whose instance is actually registered with the pattern engine's
        * symbol resolver as the primary library of name mapping user
        * functions.
        */
       public class Library
       {
          /**
           * Create a new converted program entry, with the given legacy and converted name.
           *
           * @param    pname
           *           The legacy name.
           * @param    jname
           *           The converted name.
           *
           * @return   A new {@link ProgramInfo} instance.
           */
          public ProgramInfo createProgramInfo(String pname, String jname)
          {
             ProgramInfo pinfo = new ProgramInfo();
             pinfo.attributes.put("pname", pname);
             pinfo.attributes.put("jname", jname);

             programs.put(pname, pinfo);

             return pinfo;
          }

          /**
           * Put an attribute with the given name and value for the specified method.
           *
           * @param    info
           *           The method information object.
           * @param    name
           *           The attribute name.
           * @param    value
           *           The attribute value.
           */
          public void putAttribute(MethodInfo info, String name, String value)
          {
             info.attributes.put(name, value);
          }

          /**
           * Add a new converted method entry, with the given legacy and converted name, to the given
           * program.
           *
           * @param    parent
           *           The method's external program owner.
           * @param    pname
           *           The legacy name.
           * @param    jname
           *           The converted name.
           *
           * @return   A new {@link MethodInfo} instance.
           */
          public MethodInfo addMethod(ProgramInfo parent, String pname, String jname)
          {
             MethodInfo minfo = new MethodInfo();
             parent.methods.add(minfo);

             minfo.attributes.put("pname", pname);
             minfo.attributes.put("jname", jname);

             return minfo;
          }

          /**
           * Add a new parameter to the given method (which can be an external program).
           *
           * @param    parent
           *           The parameter's method owner.
           * @param    pname
           *           The legacy name.
           * @param    jname
           *           The converted name.
           *
           * @return   A map to hold the parameter's attributes.
           */
          public Map<String, String> addParameter(MethodInfo parent, String pname, String jname)
          {
             Map<String, String> param = new LinkedHashMap<>();
             param.put("pname", pname);
             param.put("jname", jname);

             parent.parameters.add(param);

             return param;
          }

          /**
           * Add a new empty parameter to the given method (which can be an external program).
           *
           * @param    parent
           *           The parameter's method owner.
           *
           * @return   A map to hold the parameter's attributes.
           */
          public Map<String, String> addParameter(MethodInfo parent)
          {
             Map<String, String> param = new LinkedHashMap<>();

             parent.parameters.add(param);

             return param;
          }

          /**
           * Given a root node, store all the mappings from the {@link NameMappingWorker#programs} map.
           *
           * @param    root
           *           The root element.
           */
          public void storeMappings(XmlAst root)
          {
             BiConsumer<XmlAst, Map<String, String>> storeAttributes = (parent, m) ->
             {
                for (String name : m.keySet())
                {
                   String value = m.get(name);

                   XmlAst elAttr = (XmlAst) XmlPatternWorker.createAst(XmlTokenTypes.ATTRIBUTE_NODE,
                                                                       name,
                                                                       parent);
                   XmlPatternWorker.createAst(XmlTokenTypes.CONTENT, value, elAttr);
                }
             };

             BiConsumer<XmlAst, List<Map<String, String>>> storeParameters = (parent, l) ->
             {
                for (Map<String, String> params : l)
                {
                   XmlAst elParam = (XmlAst) XmlPatternWorker.createAst(XmlTokenTypes.ELEMENT_NODE,
                                                                        "parameter",
                                                                        parent);
                   storeAttributes.accept(elParam, params);
                }
             };

             for (String prog : programs.keySet())
             {
                ProgramInfo pinfo = programs.get(prog);

                XmlAst elClass = (XmlAst) XmlPatternWorker.createAst(XmlTokenTypes.ELEMENT_NODE,
                                                                     "class-mapping",
                                                                     root);
                storeAttributes.accept(elClass, pinfo.attributes);
                storeParameters.accept(elClass, pinfo.parameters);

                for (MethodInfo minfo : pinfo.methods)
                {
                   XmlAst elMthd = (XmlAst) XmlPatternWorker.createAst(XmlTokenTypes.ELEMENT_NODE,
                                                                       "method-mapping",
                                                                       elClass);

                   storeAttributes.accept(elMthd, minfo.attributes);
                   storeParameters.accept(elMthd, minfo.parameters);
                }
             }
          }

          /**
           * Given a filename with the XML structure with all the name mappings, load it in the
           * {@link NameMappingWorker#programs} map.
           *
           * @param    filename
           *           The file from which to read the name mappings.
           */
          public void restoreMappings(String filename)
          throws Exception
          {
             BiConsumer<Element, Map<String, String>> restoreAttributes = (el, m) ->
             {
                NamedNodeMap attrs = el.getAttributes();
                for (int j = 0; j < attrs.getLength(); j++)
                {
                   Node attr = attrs.item(j);
                   String attrName = attr.getNodeName();
                   String attrVal = attr.getNodeValue();

                   m.put(attrName, attrVal);
                }
             };

             BiConsumer<Element, List<Map<String, String>>> restoreParameters = (el, l) ->
             {
                NodeList cparams = el.getElementsByTagName("parameter");
                for (int j = 0; j < cparams.getLength(); j++)
                {
                   Element param = (Element) cparams.item(j);
                   // keep only parameters which belong to the given node
                   if (param.getParentNode() != el)
                   {
                      continue;
                   }

                   Map<String, String> pattrs = new LinkedHashMap<>();
                   restoreAttributes.accept(param, pattrs);

                   l.add(pattrs);
                }
             };

             FileInputStream fis = new FileInputStream(filename);
             try
             {
                Document dom = XmlHelper.parse(fis);

                // find the root node
                Element root = dom.getDocumentElement();
                NodeList cmaps = root.getElementsByTagName("class-mapping");
                for (int i = 0; i < cmaps.getLength(); i++)
                {
                   Element classMap = (Element) cmaps.item(i);

                   ProgramInfo pinfo = new ProgramInfo();
                   restoreAttributes.accept(classMap, pinfo.attributes);
                   restoreParameters.accept(classMap, pinfo.parameters);

                   programs.put(pinfo.attributes.get("pname"), pinfo);

                   // child method-mapping nodes
                   NodeList cmethods = classMap.getElementsByTagName("method-mapping");
                   for (int j = 0; j < cmethods.getLength(); j++)
                   {
                      Element method = (Element) cmethods.item(j);

                      MethodInfo minfo = new MethodInfo();
                      pinfo.methods.add(minfo);

                      restoreAttributes.accept(method, minfo.attributes);
                      restoreParameters.accept(method, minfo.parameters);
                   }
                }
             }
             finally
             {
                fis.close();
             }
          }

          /**
           * Utility function which checks if a class name is registered in the given package location
           * as a converted class.
           *
           * @param  pkg
           *         The package in which we want to check if there is any class by the given name.
           * @param  name
           *         The class name we want to check for existence.
           *
           * @return <code>true</code> if a class with that name exists in the given
           *         package or <code>false</code> otherwise.
           */
          public boolean classNameExists(String pkg, String name)
          {
             name = pkg + "." + name;
             return (SourceNameMapper.getLegacySourceName(pkgroot, name) != null);
          }
       }

       /**
        * A simple class which holds a list of <code>class-mapping</code> information, from a
        * name_map.xml file.
        * <p>
        * Each instance is implied to be the external program 'method'
        */
       public static class ProgramInfo
       extends MethodInfo
       {
          /** The list of method information. */
          private List<MethodInfo> methods = new ArrayList<>();
       }

       /**
        * A simple class which holds a list of <code>method-mapping</code> information, for a certain
        * program, as it appears in a name_map.xml file.
        */
       public static class MethodInfo
       {
          /** The method mapping attributes. */
          protected Map<String, String> attributes = new LinkedHashMap<>();

          /** The method's parameters (each with a map of XML attributes). */
          protected List<Map<String, String>> parameters = new ArrayList<>();
      }
    }