SupportLevelDocumentationGenerator.java

/*
** Module   : SupportLevelDocumentationGenerator.java
** Abstract : Generates textile wiki syntax to describe the support levels of 4GL features.
**
** Copyright (c) 2021-2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- --------------------------------------Description----------------------------------------
** 001 GES 20210214 First version.  Handles OO built-in classes/enums/interfaces, methods, events and
**                  properties.
**     TJD 20220504 Java 11 compatibility minor changes
** 002 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.uast;

import java.lang.reflect.*;
import java.util.*;
import java.util.logging.*;

import com.goldencode.p2j.convert.*;
import org.reflections.*;

import com.goldencode.p2j.oo.lang.*;
import com.goldencode.p2j.report.*;
import com.goldencode.p2j.report.SupportLevelHelper.Descriptor;
import com.goldencode.p2j.util.*;

/**
 * Generate textile wiki syntax to describe the support levels of 4GL features.
 */
public class SupportLevelDocumentationGenerator
implements ReportConstants
{
   /** Logger. */
   private static final ConversionStatus LOG = ConversionStatus.get(SupportLevelDocumentationGenerator.class);
   
   /** Wrapper type package qualifier. */
   private static final String WRAPPER_TYPE_PREFIX = "com.goldencode.p2j.util.";
   
   /** Length of the wrapper type package qualifier. */
   private static final int WRAPPER_PREFIX_SIZE = WRAPPER_TYPE_PREFIX.length(); 
   
   /** Object wrapper type prefix. */
   private static final String OBJECT_PREFIX = "object<? extends ";
   
   /** Length of the object prefix. */
   private static final int OBJECT_PREFIX_SIZE = OBJECT_PREFIX.length(); 
   
   /** Object wrapper type prefix. */
   private static final String ALT_OBJECT_PREFIX = "object<";
   
   /** Length of the object prefix. */
   private static final int ALT_OBJECT_PREFIX_SIZE = ALT_OBJECT_PREFIX.length();
   
   /**
    * Calculate the legacy type from the Java type.
    * 
    * @param    jtype
    *           The Java data type.
    *           
    * @return   The legacy 4GL type.
    */
   public static String reverseType(String jtype, Map<String, String> map)
   {
      boolean wrapper  = false;
      String  ptype    = jtype;
      int     brackets = jtype.indexOf('[');

      // remove the square brackets
      if (brackets != -1)
      {
         jtype = jtype.substring(0, brackets);
      }
      
      if (jtype.startsWith(WRAPPER_TYPE_PREFIX))
      {
         wrapper = true;
         ptype = jtype.substring(WRAPPER_PREFIX_SIZE);
      }
      
      if (ptype.startsWith("object"))
      {
         if (ptype.startsWith(OBJECT_PREFIX))
         {
            ptype = ptype.substring(OBJECT_PREFIX_SIZE, ptype.length() - 1);
         }
               
         if (ptype.startsWith(ALT_OBJECT_PREFIX))
         {
            ptype = ptype.substring(ALT_OBJECT_PREFIX_SIZE, ptype.length() - 1);
         }
            
         if (map.containsKey(ptype))
         {
            ptype = map.get(ptype);
         }
      }
      else if (wrapper)
      {
         // non-object wrapper, nothing else to do
      }
      else if ("java.lang.Object".equals(ptype))
      {
         ptype = "POLYMORPHIC";
      }
      else if ("void".equals(ptype))
      {
         // nothing to do, we're good to go
      }
      else
      {
         LOG.log(Level.SEVERE, String.format("Cannot decode legacy type from %s!", jtype));
      }
      
      return ptype;
   }
   
   /**
    * Describe the list of 4GL parameters as a text in the form "mode data_type(, mode datatype)*".
    * 
    * @param    parms
    *           The parameter list.
    *           
    * @return   The formatted descriptor.
    */
   public static String render(LegacyParameter[] parms)
   {
      String result = "";
      
      for (LegacyParameter p : parms)
      {
         String item = p.mode().toLowerCase() + " " + readType(p);
         result += result.isEmpty() ? item : ", " + item;
      }
      
      return result;
   }
   
   /**
    * Describe the list of Java parameters as a text in the form "data_type(, datatype)*".
    * 
    * @param    parms
    *           The parameter list.
    *           
    * @return   The formatted descriptor.
    */
   public static String render(Type[] parms)
   {
      String result = "";
      
      for (Type p : parms)
      {
         result += result.isEmpty() ? p.getTypeName() : ", " + p.getTypeName();
      }
      
      return result;
   }
   
   /**
    * Describe the given data type, objects are reported as their qualified type names.
    * 
    * @param    p
    *           The parameter to inspect.
    *           
    * @return   The text name for the type.
    */
   public static String readType(LegacyParameter p)
   {
      String type = p.type().toLowerCase();
      return "object".equals(type) ? p.qualified() : type;
   }
   
   /**
    * Generate the textile syntax for a table cell contents which describes a support level with the
    * proper coloring.
    * 
    * @param    lvl
    *           The support level to describe.
    *           
    * @return   The wiki syntax for the given support level.
    */
   public static String formatSupportLvl(SupportLevelHelper.Descriptor lvl)
   {
      return String.format("{background:%s;color:%s}. %s",
                           Descriptor.asRGBConstant(lvl.bg),
                           Descriptor.asRGBConstant(lvl.fg), 
                           lvl.name);
   }
   
   /**
    * Create a mapping of fully qualified Java class names to fully qualified legacy class names.
    * 
    * @param    legacyClasses
    *           All classes with legacy annotations.
    *           
    * @return   The mapping.
    */
   public static Map<String, String> initNameLookup(Set<Class<? extends _BaseObject_>> legacyClasses)
   {
      Map<String, String> nameLookup = new HashMap<>();
      
      for (Class<? extends _BaseObject_> cls : legacyClasses)
      {
         LegacyResource lr = cls.getAnnotation(LegacyResource.class);
         
         if (lr == null)
         {
            LOG.log(Level.WARNING, "class " + cls + " doesn't have a LegacyResource annotation!");
         }
         else
         {
            String clsName = cls.getName();
            
            if (cls == BaseObject.class)
            {
               clsName = _BaseObject_.class.getName();
            }
            
            // System.err.printf("MAP %s to %s\n", clsName, lr.resource());
            nameLookup.put(clsName, lr.resource());
         }
      }

      return nameLookup;
   }

   /**
    * Command line interface to execute the document generation.  WARNINGS and ERRORS are written to
    * STDERR and the textile wiki content is written to STDOUT.  Redirect these accordingly to capture
    * the output.  No parameters are needed on the command line.
    * 
    * @param    args
    *           Ignored.
    */
   public static void main(String[] args)
   {
      Reflections reflections = new Reflections("com.goldencode.p2j.oo");
      Set<Class<? extends _BaseObject_>> legacyClasses = reflections.getSubTypesOf(_BaseObject_.class);
      Map<String, String>                nameLookup    = initNameLookup(legacyClasses);
      
      System.out.println("|_\\2. Resource Type |_. Legacy Class |_. Java Class |_. Conversion Support |_. Runtime Support |");
      
      for (Class<? extends _BaseObject_> cls : legacyClasses)
      {
         LegacyResourceSupport lrs = cls.getAnnotation(LegacyResourceSupport.class);
         
         if (lrs == null)
         {
            LOG.log(Level.WARNING, "class " + cls + " doesn't have a LegacyResourceSupport annotation!");
         }
         else
         {
            String clsName = cls.getName();
            
            if (cls == BaseObject.class)
            {
               clsName = _BaseObject_.class.getName();
            }
            
            boolean iface = cls.isInterface();
            boolean lenum = com.goldencode.p2j.oo.lang.LegacyEnum.class.isAssignableFrom(cls);
                        
            String ooType = iface ? "INTERFACE" : (lenum ? "ENUM" : "CLASS");
            String legacyName = nameLookup.get(clsName);
            
            int lastDot = legacyName.lastIndexOf('.');
            String baseLegacyClass = lastDot == -1 ? legacyName : legacyName.substring(lastDot + 1);
            
            int lvl = lrs.supportLvl();
            SupportLevelHelper.Descriptor cvtLvl = SupportLevelHelper.describe(lvl & CVT_LVL_MASK);
            SupportLevelHelper.Descriptor rtLvl  = SupportLevelHelper.describe(lvl & RT_LVL_MASK);
            
            System.out.printf("|\\2. %s | %s | %s |%s |%s |\n", ooType, legacyName, clsName, formatSupportLvl(cvtLvl), formatSupportLvl(rtLvl));
            
            // print enum members
            if (lenum)
            {
               for (Field f : cls.getDeclaredFields())
               {
                  LegacySignature vs = f.getAnnotation(LegacySignature.class);
                  
                  if (vs != null)
                  {
                     if (vs.type() == InternalEntry.Type.VARIABLE)
                     {
                        System.out.printf("|{background:lightslategrey;color:lightslategrey}. ..... | MEMBER | %s | %s |{background:#009900;color:#FFFFFF}. Full |{background:#009900;color:#FFFFFF}. Full |\n", vs.name(), f.getName());
                     }
                  }
               }
            }            
            
            // check the method names against NameConverter
            for (Method m : cls.getDeclaredMethods())
            {
               LegacySignature       ls   = m.getAnnotation(LegacySignature.class);
               LegacyResourceSupport supp = m.getAnnotation(LegacyResourceSupport.class);
               
               if (ls != null)
               {
                  if (supp == null)
                  {
                     LOG.log(Level.WARNING,
                             "method " + m.getName() + " of class " + cls + 
                                " doesn't have a LegacyResourceSupport annotation!");
                  }
                  else
                  {
                     lvl      = supp.supportLvl();
                     cvtLvl   = SupportLevelHelper.describe(lvl & CVT_LVL_MASK);
                     rtLvl    = SupportLevelHelper.describe(lvl & RT_LVL_MASK);
                     
                     String jretType = m.getGenericReturnType().getTypeName();
                     String retType  = reverseType(jretType, nameLookup);
                     
                     LegacyParameter[] lparms = ls.parameters();
                     Type[]            jparms = m.getGenericParameterTypes();
                     
                     String typeDescr = ls.type().toString();
                     int    extent    = ls.extent();
                     
                     String extentDescr = "";
                     
                     if (extent != -2)
                     {
                        extentDescr = (extent == -1) ?  "[]" : String.format("[%d]", extent);
                     }
                     
                     switch (ls.type())
                     {
                        case METHOD:
                           System.out.printf("|{background:lightslategrey;color:lightslategrey}. ..... | %s | %s %s(%s) | %s %s(%s) |%s |%s |\n", typeDescr, retType, ls.name(), render(lparms), jretType, m.getName(), render(jparms), formatSupportLvl(cvtLvl), formatSupportLvl(rtLvl));
                           break;
                        case GETTER:
                           System.out.printf("|{background:lightslategrey;color:lightslategrey}. ..... | PROPERTY GETTER | %s : %s%s | %s %s(%s) |%s |%s |\n", ls.name(), retType, extentDescr, jretType, m.getName(), render(jparms), formatSupportLvl(cvtLvl), formatSupportLvl(rtLvl));
                           break;
                        case SETTER:
                           retType = reverseType(jparms[0].getTypeName(), nameLookup);
                           System.out.printf("|{background:lightslategrey;color:lightslategrey}. ..... | PROPERTY SETTER | %s : %s%s | %s %s(%s) |%s |%s |\n", ls.name(), retType, extentDescr, jretType, m.getName(), render(jparms), formatSupportLvl(cvtLvl), formatSupportLvl(rtLvl));
                           break;
                        case RESIZE:
                           System.out.printf("|{background:lightslategrey;color:lightslategrey}. ..... | PROPERTY | @EXTENT(%s) = <int64>@ | %s %s(%s) |%s |%s |\n", ls.name(), jretType, m.getName(), render(jparms), formatSupportLvl(cvtLvl), formatSupportLvl(rtLvl));
                           break;
                        case LENGTH:
                           System.out.printf("|{background:lightslategrey;color:lightslategrey}. ..... | PROPERTY | @integer EXTENT(%s)@ function | %s %s(%s) |%s |%s |\n", ls.name(), jretType, m.getName(), render(jparms), formatSupportLvl(cvtLvl), formatSupportLvl(rtLvl));
                           break;
                        case CONSTRUCTOR:
                        case DESTRUCTOR:
                           System.out.printf("|{background:lightslategrey;color:lightslategrey}. ..... | %s | %s(%s) | %s(%s) |%s |%s |\n", typeDescr, baseLegacyClass, render(lparms), m.getName(), render(jparms), formatSupportLvl(cvtLvl), formatSupportLvl(rtLvl));
                           break;
                        case PUBLISH:
                        case SUBSCRIBE:
                        case UNSUBSCRIBE:
                           typeDescr = "EVENT " + typeDescr;
                           System.out.printf("|{background:lightslategrey;color:lightslategrey}. ..... | %s | %s %s(%s) | %s %s(%s) |%s |%s |\n", typeDescr, retType, ls.name(), render(lparms), jretType, m.getName(), render(jparms), formatSupportLvl(cvtLvl), formatSupportLvl(rtLvl));
                           break;
                        default:
                           LOG.log(Level.SEVERE,
                                   String.format("   UNKNOWN %s %s %s(%s) CVT %s RT %s (Java %s %s(%s))",
                                                 typeDescr,
                                                 retType,
                                                 ls.name(),
                                                 render(lparms),
                                                 cvtLvl.name,
                                                 rtLvl.name,
                                                 jretType,
                                                 m.getName(),
                                                 render(jparms)));
                     }
                  }
               }
            }
         }
      }
   }
}