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<>();
  }
}