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