package org.rexxla.bsf.engines.rexx;

/* Test command lines:

java org.rexxla.bsf.engines.rexx.ClassAdapter java.io.File Aloha getAbsoluteFile isFile deleteOnExit listFiles compareTo createTempFile
java org.rexxla.bsf.engines.rexx.ClassAdapter javax.swing.filechooser.FileFilter Aloha append oha nixiNAXI accept

java org.rexxla.bsf.engines.rexx.ClassAdapter java.awt.Frame Brumm getState getTitle setTitle "<init>" "java.awt.Container paint" "java.awt.Container <init>" "nixi.noxi.org oho"


java org.rexxla.bsf.engines.rexx.ClassAdapter TestAbstract Caesar

*/

/*
    2010-11-30, rgf:   - fixed bug in createConstructorsBase() which caused illegal class files to be created
    2010-05-31, rgf:   - use the ClassLoader of the class to proxy, otherwise dynamically created Java classes cannot be extended
    2014-08-30, rgf:   - subclasses are allowed to access protected methods in superclasses, adjust for this
    2014-08-31, rgf:   - if methods to proxy is null, then proxy all public and protected declared methods,
                         if methods is an empty array, then just proxy the constructors
    2016-11-01, rgf:   - do not use the ClassLoader from the target's class (e.g. javafx.* classes in Java 1.8/8
                         use their own class loaders which may confine them to see only javafx.*)
    2016-11-04, rgf:   - fixed bug that did not extend an abstract class, if not methods to proxy were supplied
                         (which will be the case when invoked by RexxProxy.newExtendedProxyInstance(...))

    2016-11-03, rgf:   - if no methods given, but an abstract class, then proxy all abstract (public or protected) methods only
    2016-11-05, rgf:   - basing on 1.6/6.0, get rid of generic warnings
    2016-11-07, rgf:   - remove reflective usage of Method.isSynthetic() as we can now rely it to be available
    2017-03-09, rgf:   - added option to implement any number of interface classes to the extended Java class
                         (cf. the changes to BSF.CLS' routine bsf.createProxyClass)
    2017-03-13, rgf:   - redo to allow full replacement of ASM, added functionality to create classes off interface(s) only
                       - changed "bss" to the self documenting name "bShowSource"
    2017-06-17, rgf:   - if the context class loader is null, make sure that the SimpleCompiler's parent class loader
                         gets set; this version uses the class loader for ClassAdapter.
    2018-02-19, rgf:   - when creating interface methods, make sure that only abstract interface methods get proxied;
                         starting with Java 1.8/8 there are concrete interface methods possible (static and default interface
                         methods)
    2019-08-13, rgf:   - corrected Javadoc warnings
    2019-08-21, rgf:   - renamed ProxiedJavaClassTool.java to ClassAdapter.java
    2019-08-24, rgf:   - there will always methods for public/protected abstract methods and constructors created
                         - if message name array is null or an empty Array, then
                           only abstract methods and constructors get created
                         - if message name array has a single entry "*", then
                           all declared public/protected methods get created
                         - else all array elements that refer to public/protected listed methods will be honored
    2019-08-25, rgf:    - supplying additional interface classes should be also honored for extending abstract classes
                        - fix bug that only honored one interface method (instead of honoring all)
                        - added ability to save the generated code
    2019-08-26, rgf:    - do not add BSFException to the throws clause of methods, rather
                          throw a RuntimeException with the BSFException as the cause when
                          a Rexx condition gets raised while invoking the proxy Rexx object
    2019-08-29, rgf:    - allow more control over generated class names consulting the new fields:
                                static public boolean bAddCRC32toNewName=false;
                                static public boolean bAddCRC32toGeneratedClassName=true;
                                static public boolean bAddHashToGeneratedClassName=false;
                                static public String extensionIndicator = "$RexxExtendClass$";
                                static public String packageName="org.rexxla.bsf.engines.rexx.onTheFly";

*/

import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Arrays;

import org.apache.bsf.BSFException;
import org.rexxla.bsf.engines.rexx.RexxProxy;
import org.rexxla.bsf.engines.rexx.RexxException;

import org.codehaus.janino.SimpleCompiler;

    // formatting Date()
import java.text.SimpleDateFormat;
import java.util.Date;

    // calculation CRC32 from byte array
import java.util.zip.CRC32;
import java.util.zip.Checksum;

import java.io.FileOutputStream;


/**
  * This class creates a proxied Java class on the fly, which has concrete implementations for all
 *  abstract (inherited or not) methods. Additionally it allows for creating proxy methods for
 *  concrete methods and matching <code>&quot;_forwardToSuper&quot;</code> methods to allow ooRexx to forward
 *  proxied Java method invocations to their original target on the Java side.
 *
 * <pre>------------------------ Apache Version 2.0 license -------------------------
 *    Copyright (C) 2010-2019 Rony G. Flatscher
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        <a href="http://www.apache.org/licenses/LICENSE-2.0">http://www.apache.org/licenses/LICENSE-2.0</a>
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 * ----------------------------------------------------------------------------- </pre>
 *
  * @version 1.04, 2010-03-27, 2010-04-14, 2010-04-17, 2010-04-18, 2010-11-30, 2011-05-31, 2014-08-30, 2016-11-01, 2016-11-04/5, 2017-03-13, 2019-08-24
  * @since 2010
  * @author Rony G. Flatscher (<a href="http://www.wu-wien.ac.at">WU-Wien/Wirtschaftsuniversit&auml;t Wien</a>, <a href="http://www.wu.ac.at">http://www.wu.ac.at</a>)
  */

public class ClassAdapter
{
    private final static String STR_REXX_PROXY="org.rexxla.bsf.engines.rexx.RexxProxy";
        // flags
    private final static int    METHOD_SIGNATURE=1;
    private final static int    THROWS_CLAUSE   =2;

        // switches
        // the bDebug switch should remain private and final: set to false should cause javac to remove any dependent code
    private final static boolean bDebug = false;    // true;

        // public to allow changes from anywhere
    public static boolean bTiming    = false; // true;
    public static boolean bShowSource= false; // true; // if set to .true, the generated source is printed to stderr

     /** Version information on this class. */
    static final public String version="104.20190829";

// 2019-08-15, rgf
        // add ability to save generated programs to file system
    /** Controls whether the generated file name should be led in with the
     *  extended class' simple name, defaults to <code>true</code>. */
    static public boolean bFileNameWithSimpleName=true;    // create with leading simple name of the class that gets extended

        // simple Date formatter
    /** Defines the format pattern (<code>"yyyyMMdd_HHmmss_SSS"</code>) that
     *  {@link #formatDateTime()} and {@link #formatDateTime(Date d)} use; this
     *  one is for showing date and time.
     */
    static SimpleDateFormat sdf_dt=new SimpleDateFormat("yyyyMMdd_HHmmss_SSS");

    /** Defines the format pattern (<code>"yyyyMMdd"</code>) that
     *  {@link #formatDateTime()} and {@link #formatDateTime(Date d)} use; this
     *  one is for showing date only.
     */
    static SimpleDateFormat sdf_d =new SimpleDateFormat("yyyyMMdd");

    /** Controls whether the generated file name includes a date and time
     *  portion, defaults to <code>true</code>. */
    static public boolean bFileNameWithDateTime  =true;    // create date time portion for file name

    /** Controls whether the generated file name's date and time portion
     *  should contain the date portion only, defaults to <code>false</code>. */
    static public boolean bFileNameWithDateOnly  =false;    // create date portion for file name


    // save generated program to file section -->
    /** Flag that determines whether the generated program should be stored in the file system. */
    static public boolean bSaveToFilesystem=false;          // do not save to file system


// ---------->
    /** Flag that determines whether the supplied <code>newName</code> should get the
     *  CRC32 value appended, defaults to <code>true</code>. The CRC32 value gets calculated
     *  from the string values of the arguments such that the same arguments are expected
     *  to create the same CRC32 value.
     */
    static public boolean bAddCRC32toNewName=false;

    /** Flag that determines whether the generated class name should get the CRC32
     *  value appended, defaults to <code>true</code>. */
    static public boolean bAddCRC32toGeneratedClassName=true;

    /** Flag that determines whether the generated class name should get the hash value of
     *  the class object appended, defaults to <code>false</code>. If name clashes occur
     *  in an application due to having duplicate CRC32 values generated, then setting
     *  this flag to <code>true</code> should allow for creating unique class names thereafter.
     */
    static public boolean bAddHashToGeneratedClassName=false;

    /** String that gets appended to the generated class name to ease locating such classes, defaults to
     *  <code>"_$RexxExtendClass$_"</code>.
     */
    static public String extensionIndicator = "$RexxExtendClass$";


    /** Package name that will replace the package name of the extended class, defaults
     *  to <code>"org.rexxla.bsf.engines.rexx.onTheFly"</code>.
     *        If the value is <code>null</code>, then the package name will not be replaced.
     *        If the value is the empty String (length of 0), then the package of the extended class
     *        will be removed.
     */
    static public String packageName="org.rexxla.bsf.engines.rexx.onTheFly";

/*
    / ** Setter method that allows the replacement package name to be set.
     *
     * @param newPackageName the name used for replacing the package name of the extended class.
     *        If the value is <code>null</code>, then the package name will not be replaced.
     *        If the value is the empty String (length of 0), then the package of the extended class
     *        will be removed.
     * /
    static public void setPackageName(String newPackageName)
    {
        packageName=newPackageName;
    }

    / ** Getter method that returns the the current replacement package name.
     *
     *  @return the current replacement package name
     * /
    static public String getPackageName()
    {
        return packageName;
    }
--- */


// <----------

    /** Path where a generated program should be stored to, defaults to the Java temporary directory
     *  <code>(System.getProperty("java.io.tmpdir"))</code>. */
    static public  String pathToSaveTo  =System.getProperty("java.io.tmpdir");   // use Java's tmp dirctory by default

    /** Stores the current platform's file separator character. */
    static private String fileSeparator=System.getProperty("file.separator");   // platform's file separator




    /** Allow using this class from the command line to create an extended Java class and displaying the generated source.
     *  Example: <br>
     *  <code>java java org.rexxla.bsf.engines.rexx.ClassAdapter javax.swing.AbstractAction ronysClass "*"</code>
     *
     *  @param args array of blank delimited Strings in the form of &quot;fullJavaClassName [newClassName [methodToProxy [...]]&quot;. If there is
     *         a single method to proxy with a value of &quot;*&quot; (asterisk), then all declared methods will get proxied.
     *
     * @throws BSFException if any execution error occurs
     */
    public static void main (String args[])
                    throws BSFException
    {
        if (args.length!=0)
        {
            // define defaults
            Class   javaClassToProxy   =null;
            String  newClzName         =null;
            String  []methodNames2proxy=null;

                // try to load class
            try
            {
                javaClassToProxy=ClassAdapter.class.getClassLoader().loadClass(args[0]);
            }
            catch (Exception e)
            {
                e.printStackTrace();
                // System.err.println(e+"\n---");
                System.err.println("--- The (premature) end. ---");
                System.exit(-1);
            }

            if (args.length>1)
            {
                newClzName=args[1];
            }

            if (args.length>2)
            {
                int nr=args.length-2;   // number of remaining arguments

                methodNames2proxy=new String[nr];
                for (int k=0; k<nr; k++)
                {
                    methodNames2proxy[k]=args[k+2];
                }
            }

            try
            {
                bTiming=true;       // show timing information
                bShowSource= true;  // show generated source code
                createProxiedJavaClass( javaClassToProxy, newClzName, methodNames2proxy, null);
            }
            catch (Exception e)
            {
                System.err.println(e+"\n---");
            }
        }
    }


    /* Do not allow argument less instances of this class. */
    private ClassAdapter ()
    {}

    /** Creates and returns a proxy class in which Java method invocations get forwarded to
     *  a RexxProxy.
     *
     *  @param  javaClassToProxy Class object to use for proxying
     *  @param  strNewClzName <code>null</code> or name for proxy class to create
     *  @param  methodNames2proxy an array denoting the protected or public methods to proxy. Please note:
     *          each proxied class will get all public/protected abstract methods and constructors
     *          created (proxied).<br/>
     *          Each method name element denotes a public or protected single method which may be optionally
     *          preceeded by a blank and preceeded by the name of some Java superclass to be used for looking
     *          up the method definitions to proxy.
     *
     *          The following special cases apply for this argument:
     *          <ul>
     *          <li>if this argument is <code>null</code>, then only the public/protected abstract methods and
     *              constructors get proxied.
     *          </li>
     *
     *          <li>if this argument has no elements (its <code>length</code>field is <code>0</code>), then
     *              only the public/protected abstract methods and constructors get proxied.
     *          </li>
     *
     *          <li>if this argument has a single element with the value <code>"*"</code> (asterisk), then
     *              all public/protected methods and constructors get proxied.
     *          </li>
     *          </ul>
     *
     *  @param   interfaceClasses optional, the extended class should in addition implement all abstract
     *           methods of these interface classes
     *
     *  @return the class object representing the extended class
     *
     * @throws BSFException if any execution error occurs
     */
    public static Class createProxiedJavaClass(final Class    javaClassToProxy,
                                               final String   strNewClzName,
                                               final String[] methodNames2proxy,
                                               final Class[]  interfaceClasses
                                               )
                                        throws BSFException
    {
        String newClzName=strNewClzName;

if (bDebug==true)
{
   System.err.println("... arrived in \"public static Class createProxiedJavaClass(...)\"");
   System.err.println("\targ #1, javaClassToProxy : ["+javaClassToProxy.toString()+"]");
   System.err.println("\targ #2, strNewClzName    : ["+strNewClzName                 +"]");
   System.err.println("\targ #3a, methodNames2proxy.length=["+methodNames2proxy.length+"]");
   System.err.println("\targ #3, methodNames2proxy: ["+(methodNames2proxy==null ? null : Arrays.toString(methodNames2proxy))+"]");
   System.err.println("\targ #4, interfaceClasses : ["+(interfaceClasses==null  ? null : Arrays.toString(interfaceClasses)) +"]");
}

        long crtSrcStartTime=0l, crtSrcEndTime=0l;
        if (bTiming==true) crtSrcStartTime=System.currentTimeMillis(); // System.nanoTime(); // currentTimeMillis();

            // process arguments
        if (javaClassToProxy==null)     // do not accept null !
        {
            throw new BSFException (BSFException.REASON_INVALID_ARGUMENT,
                     "ClassAdapter.createProxiedJavaClass(...): argument \"javaClassToProxy\" must not be null");
        }

        if (javaClassToProxy.isArray())     // do not accept Array classes
        {
            throw new BSFException (BSFException.REASON_INVALID_ARGUMENT,
                     "ClassAdapter.createProxiedJavaClass(...): argument \"javaClassToProxy\" must not be an array class");
        }

        int classModifiers=javaClassToProxy.getModifiers();

        if (!Modifier.isPublic(classModifiers))
        {
            throw new BSFException (BSFException.REASON_INVALID_ARGUMENT,
                     "ClassAdapter.createProxiedJavaClass(...): argument \"javaClassToProxy\", value=["+javaClassToProxy+"]: is not public");
        }

        if (Modifier.isFinal(classModifiers))  // only accept non-final classes
        {
            throw new BSFException (BSFException.REASON_INVALID_ARGUMENT,
                     "ClassAdapter.createProxiedJavaClass(...): argument \"javaClassToProxy\", value=["+javaClassToProxy+"]: must not be final");
        }

// ----------->
        String strCRC32            =null;
        String strClzSimpleName    =null;
        String strClzName          =null;
        String strMethodNames2proxy=null;
        String strInterfaceClasses =null;

            // calc CRC32 if any of the following flags is true
        if (bAddCRC32toGeneratedClassName || bSaveToFilesystem || bAddCRC32toNewName )
        {
                // String version of simple name only needed if saving to file and simple name desired
            if (bSaveToFilesystem && bFileNameWithSimpleName)
            {
                strClzSimpleName    =javaClassToProxy.getSimpleName();
            }

            strClzName          =javaClassToProxy.getName();
            strMethodNames2proxy=Arrays.toString(methodNames2proxy);
            strInterfaceClasses =Arrays.toString(interfaceClasses );
                // CRC32 value should be pretty good to create unique values
            strCRC32=calcCRC32(strClzName+"-"+newClzName+"-"+strMethodNames2proxy+"-"+strInterfaceClasses);
        }
// <-----------

        if (newClzName==null)   // no name for the proxied Java class supplied? Generate an artificial name
        {
            newClzName=(strClzName != null ? strClzName : javaClassToProxy.getName()) +
                        "_" + extensionIndicator ;  /* default: "$RexxExtendClass$"   */

            if (bAddCRC32toGeneratedClassName)  // CRC32 could make the name unique already and is repeatable
            {
                newClzName=newClzName+"_"+strCRC32;
            }

            if (bAddHashToGeneratedClassName)   // some random hashCode could make the name unique
            {
                newClzName=newClzName +"_"+(new Object()).hashCode();
            }
        }
        else if (bAddCRC32toNewName)    // shall we add CRC32 to the explictly passed-in new name for the class?
        {
            newClzName=strNewClzName+"_"+strCRC32;
        }

// --->
if (bDebug==true)
{
    System.err.println("\n--> newClzName=["+newClzName+"] (edited according to flags)");
    System.err.println("\tstrClzName                   =["+strClzName   +"]");
    System.err.println("\tstrNewClzName                =["+strNewClzName+"]");
    System.err.println("\tstrMethodNames2proxy         =["+strMethodNames2proxy         +"]");
    System.err.println("\tstrInterfaceClasses          =["+strInterfaceClasses          +"]");
    System.err.println();
    System.err.println("\tbAddCRC32toGeneratedClassName=["+bAddCRC32toGeneratedClassName+"]");
    System.err.println("\tbSaveToFilesystem            =["+bSaveToFilesystem            +"]");
    System.err.println("\tbAddHashToGeneratedClassName =["+bAddHashToGeneratedClassName +"]");
    System.err.println("\tbAddCRC32toNewName           =["+bAddCRC32toNewName           +"]");
    System.err.println("\tstrCRC32                     =["+strCRC32                     +"]");
}

// <---


        String [] methods2proxy=null;
        boolean isAbstractClz=Modifier.isAbstract(classModifiers);

        // proxy all public and protected declared methods if there is a single method name argument with a
        // single asterisk character (*)
        boolean bProxyAllDeclaredMethods=( methodNames2proxy!=null &&
                                           methodNames2proxy.length==1 && methodNames2proxy[0].equals("*") );

// System.err.println("ClassAdapter: bProxyAllDeclaredMethods=["+bProxyAllDeclaredMethods+"]");

        if (bProxyAllDeclaredMethods)
        {
            Method meths[]=javaClassToProxy.getDeclaredMethods();
            HashSet<String> methNames= new HashSet<String>();
            for (int i=0; i<meths.length; i++)
            {
                int mods=meths[i].getModifiers();
                if (Modifier.isPublic(mods) || Modifier.isProtected(mods))
                {
                    String methName=meths[i].getName();

                        // do not proxy methods toString(), equals(), hashCode() by default as this may break the JVM
                    if (methName.equals("toString") || methName.equals("equals") || methName.equals("hashCode") )
                    {
                        continue;
                    }
                    else if (Modifier.isAbstract(mods)==false)  // only proxy method if not abstract (abstract methods will get processed later)
                    {
                        methNames.add(meths[i].getName());
                    }
                }
            }
            // turn into String array (toArray will create and return an appropriately dimensiond String array object)
            methods2proxy= (String[]) methNames.toArray(new String[0]);
        }
        else    // use supplied array of method names to proxy
        {
            methods2proxy=methodNames2proxy;
        }


            // create Java program, use a StringBuffer
        StringBuffer sb=new StringBuffer();

        RunInfos ri=new RunInfos();
            // save mapping of class name to its class object for later use
        ri.uCN2clz.put(javaClassToProxy.getName().toUpperCase(), javaClassToProxy);


// ---------------------------------------------------------------------------
        // process package part of class:
        // if:   newClzName given, then do not change its package name
        // else:
        // ... if packageName is null, then leave the package name intact
        // ... if packageName is "", then remove package name altogether

if (bDebug==true) System.err.println("\treplacing/adding package name ... ]");

//        String packageName="org.rexxla.bsf.engines.rexx.onTheFly";
        String simpleNewClzName=newClzName;         // default to current fully defined class name
        String replacementPackageName=packageName;  // default to statically defined package name
            // extract simple class name
        int lpos=newClzName.lastIndexOf('.');
        if (lpos>=0)
        {
            if (strNewClzName!=null)    // use argument's package name later
            {
                // packageName=newClzName.substring(0,lpos);
               replacementPackageName=newClzName.substring(0,lpos);
            }
            else
            {
                if (packageName.length()==0 || packageName.contains(" "))   // empty string or blanks only
                {
                    replacementPackageName="";
                }
            }

if (bDebug==true) System.err.println("\treplacing package name ...   =["+newClzName.substring(0,lpos) +"]");
            // newClzName=newClzName.substring(lpos+1);    // remove package name
            simpleNewClzName=newClzName.substring(lpos+1);    // remove package name
        }
        else   // no package name
        {
            replacementPackageName=null;
        }

        String fullNewClzName=newClzName;
        // if (packageName!=null)  // only supply a package name, if original class has one as well, otherwise loadClass() may experience problems later
        if (replacementPackageName!=null)  // only supply a package name, if original class has one as well, otherwise loadClass() may experience problems later
        {
            // sb.append("package ").append(packageName).append("; \n");
            // fullNewClzName=packageName+"."+newClzName;
            if (replacementPackageName.length()>0)
            {
                sb.append("package ").append(replacementPackageName).append("; \n");
                // fullNewClzName=replacementPackageName+"."+newClzName;
                fullNewClzName=replacementPackageName+"."+simpleNewClzName;
            }
            else
            {
                fullNewClzName=newClzName;
            }
        }

if (bDebug==true)
{
    System.err.println("\tpackageName                  =["+packageName           +"]");
    System.err.println("\treplacementPackageName       =["+replacementPackageName+"]");
    System.err.println("\tsimpleNewClzName             =["+simpleNewClzName      +"]");
    System.err.println("\tEFFECTIVE fullNewClzName     =["+ fullNewClzName +"]");
}

// ---------------------------------------------------------------------------

        createProlog(sb, simpleNewClzName, javaClassToProxy, interfaceClasses, ri); // create prolog code

// ---------------------------------------------------------------------------

        createMethods(sb, simpleNewClzName,  javaClassToProxy, methods2proxy, ri);

// ---------------------------------------------------------------------------

        // if constructors were not created by demand of user, do not proxy them to the RexxProxy
        createConstructorsBase(sb, simpleNewClzName,  javaClassToProxy, false, ri); // create base constructors

// ---------------------------------------------------------------------------

            // create proxies for public constructors; if abstract class in hand, check whether superclass
            // is abstract as well and if so, proxy its public constructors and abstract methods too, such
            // that we can supply the RexxProxy-handler when creating an instance
        Class tmpClz=javaClassToProxy;
        do
        {
            if (bDebug==true) System.err.println("createProxiedJavaClass(): tmpClz=["+tmpClz+"]");

            // createConstructors(sb, simpleNewClzName,  javaClassToProxy, bProxyConstructors, isAbstractClz); // create addtional, constructors
            createConstructors(sb, simpleNewClzName,  tmpClz, false, ri); // create addtional, constructors

            // if an abstract class, check its superclass for abstractness; and if so
            // proxy its public constructors as well
            if (!Modifier.isAbstract(tmpClz.getModifiers()))
            {
                break;
            }

            sb.append("\n\t// --- creating methods for public and protected abstract methods in class ["+tmpClz.getName()+"], if not yet created \n");

            // create abstract methods
            Method []declMethods=null;
            if (ri.clz2declMethods.containsKey(tmpClz))     // declared methods for this class already available
            {
                declMethods=(Method []) ri.clz2declMethods.get(tmpClz);
            }
            else    // get declared methods, cache them, in case we need them for processing the method list later
            {
                declMethods=tmpClz.getDeclaredMethods();
                ri.clz2declMethods.put(tmpClz, declMethods);// save declared methods
            }

            for (int i=0; i<declMethods.length; i++)        // create implementations for abstract methods
            {
                Method m=declMethods[i];
                if (bDebug==true) System.err.println("createProxiedJavaClass(): declMethods, i=["+i+"], m=["+m.toString()+"]");

                int methModifiers=m.getModifiers();         // get Methods's modifiers
                // if ( Modifier.isAbstract(m.getModifiers())) // an abstract method in hand ?
                if ( Modifier.isAbstract(methModifiers))   // an abstract method in hand ?
                {
                    // only create proxy method if public or protected
                    if (Modifier.isPublic(methModifiers) || Modifier.isProtected(methModifiers) )
                    {
                        createMethod(sb, m, simpleNewClzName, ri);         // create method code, do not create forwardToSuper()
                    }
                }
            }

                // does the abstract class declare to implement interfaces? Might be, that
                // some of the interface methods are not implemented, implement then, if so
            for (Class tmpIClz : tmpClz.getInterfaces())    // iterate over declared interfaces and process them
            {
                createInterfaceMethods(sb, simpleNewClzName, tmpIClz, ri, tmpClz);
            }

            tmpClz=tmpClz.getSuperclass();
            if (tmpClz!=null && !Modifier.isAbstract(tmpClz.getModifiers()) )
            {
                break;
            }
        } while ( tmpClz!=null );


// ---------------------------------------------------------------------------

        // checking extending class with the supplied interface classes, create methods, if necessary
        if (bDebug) System.err.println("createProxiedJavaClass(): extending the concrete class with interfaceClasses=["+interfaceClasses+"], .length=["+(interfaceClasses!=null?interfaceClasses.length:null)+"]");

        if (interfaceClasses!=null)
        {
            for (Class tmpIClz : interfaceClasses)
            {
                createInterfaceMethods(sb, simpleNewClzName, tmpIClz, ri, javaClassToProxy);
            }
        }

// ---------------------------------------------------------------------------

        createEpilog(sb);                               // create epilog code (supply closing curly bracket)

// ---------------------------------------------------------------------------

        if (bShowSource)    // show the source before compiling
        {
            System.err.println("/// ClassAdapter.createProxiedJavaClass(...) ---> begin of generated code for class: ["+simpleNewClzName+"], now: ["+fullNewClzName+"]\n"+sb.toString()+"\n<--- end of generated code \\\\\\");
        }

        if (bTiming==true) crtSrcEndTime=System.currentTimeMillis(); // System.nanoTime(); // System.currentTimeMillis();


        String generatedCode=null;
            // writing generated code to file desired?
        if (bSaveToFilesystem)      // o.k. programmer wants to save to the file system
        {
/*
            String strClzSimpleName     =javaClassToProxy.getSimpleName();
            String strClzName           =javaClassToProxy.getName();
            String strMethodNames2proxy =Arrays.toString(methodNames2proxy);
            String strInterfaceClasses  =Arrays.toString(interfaceClasses );
*/

            // String fileName=createFileName(javaClassToProxy, simpleNewClzName, methodNames2proxy, interfaceClasses);
            String fileName=createFileName(strClzSimpleName, strClzName, simpleNewClzName, strMethodNames2proxy, strInterfaceClasses, strCRC32);

            sb.append("\n\n").append("// ClassAdapter:");
            sb.append("\n//\targument # 1: ").append(javaClassToProxy);
            sb.append("\n//\targument # 2: ").append(newClzName).append(" | simpleNewClzName: ").append(simpleNewClzName);
            sb.append("\n//\targument # 3: ").append(strMethodNames2proxy);
            sb.append("\n//\targument # 4: ").append(strInterfaceClasses);
            sb.append("\n\n// storing in a file named: ").append(fileName);
            sb.append("\n");    // without this Janino 3.0.15 throws up

            generatedCode=sb.toString();

            writeToFile(fileName,generatedCode);
        }
        else
        {
            generatedCode=sb.toString();
        }


        SimpleCompiler sc=new SimpleCompiler();
        Class clz=null;
        long startTime=0l, endTime=0l, endOfLoadTime=0l;

        try {
            if (bTiming==true)  startTime=System.currentTimeMillis(); // System.nanoTime(); // System.currentTimeMillis();

            // 2017-06-17, rgf: if no context class loader make sure SimpleCompiler gets a parent class loader
            if (Thread.currentThread().getContextClassLoader()==null)
            {
                sc.setParentClassLoader(ClassAdapter.class.getClassLoader());
            }

            sc.cook(generatedCode);             // compile program

            if (bTiming==true) endTime=System.currentTimeMillis(); // System.nanoTime(); // System.currentTimeMillis();

            clz=sc.getClassLoader().loadClass(fullNewClzName);

            if (bTiming==true) endOfLoadTime=System.currentTimeMillis(); // System.nanoTime(); // System.currentTimeMillis();
        }
        catch (Exception e)
        {
            e.printStackTrace();
            throw new BSFException (BSFException.REASON_EXECUTION_ERROR, e.toString(), e);
        }

        if (bTiming==true)
        {
            System.err.println("clz=["+clz+"]");
            System.err.println("creating source  (in milliseconds): "+(crtSrcEndTime-crtSrcStartTime));

            System.err.println("compilation time (in milliseconds): "+(endTime-startTime));
            System.err.println("load time        (in milliseconds): "+(endOfLoadTime-endTime));
        }

        return clz;
    }




    // create import statements, class definition, fields and setter/getter for default RexxProxy handler
    // 2017-03-11, rgf: support Interface class(es) only
    /** Creates the prolog statements (import, class definition, fields, getter/setter for default
     *  RexxProxy handlers.
     *
     * @param sb the StringBuffer to append the statements to
     * @param newClzName the new name of the new class
     * @param javaClassToProxy the Java (concrete, abstract, interface) class that gets extended
     * @param interfaceClasses the extended class should implement
     * @param ri RunInfos runtime infos for managing the process
     *
     * @throws BSFException if any execution error occurs
     */
    static void createProlog (StringBuffer sb, String newClzName, Class javaClassToProxy, Class[] interfaceClasses, RunInfos ri)
                throws BSFException
    {
        sb.append("import org.rexxla.bsf.engines.rexx.RexxProxy; \n");
        // sb.append("import org.rexxla.bsf.engines.rexx.RexxException; \n");
        sb.append("import org.apache.bsf.BSFException; \n");

        sb.append("\npublic class ");
        sb.append( newClzName );

        boolean isJavaClassToProxyAnInterface=javaClassToProxy.isInterface();
        if (!isJavaClassToProxyAnInterface)
        {
            sb.append(" extends ").append(javaClassToProxy.getName());
        }

            // determine whether javaClassToProxy is an interface class itself

        if ( (interfaceClasses!=null && interfaceClasses.length>0) || isJavaClassToProxyAnInterface)       // 2017-02-09, rgf: add "implements "
        {
            sb.append("\n\t\timplements");
            boolean bAddComma=false;

            if (isJavaClassToProxyAnInterface)  // indicate that this interface class is implemented by the concrete class
            {
                sb.append(' ').append(javaClassToProxy.getName());
                bAddComma=true;
            }

            if (interfaceClasses!=null)         // an array of interfaceClasses supplied
            {
// TODO: rgf, 2019-08-25: also supply all super interface classes; if so make sure no duplicate super interfaces ?
                for (Class iclz: interfaceClasses)  // iterate over interface classes
                {
                    if (bAddComma)
                    {
                        sb.append(",");
                    }
                    sb.append(' ').append(iclz.getName());  // add fully qualified interface class name
                    if (bAddComma==false)
                    {
                        bAddComma=true;
                    }
                }
            }
        }
        sb.append("\n{\n");

        sb.append("\tpublic  static          Object    classLock     =new Object(); \n");
        sb.append("\tprivate static volatile RexxProxy defaultHandler=null;         \n");
        sb.append("\tprivate                 RexxProxy target        =null;         \n\n");


            // define getDefaultHandler()
        sb.append("\n\tpublic static RexxProxy getDefaultHandler() \n");
        sb.append("\t{\n");
        sb.append("\t\treturn defaultHandler; \n");
        sb.append("\t}\n\n");

        HashSet<String> hs=new HashSet<String>();   // memorize that we created a method with this signature
        hs.add("()");
        ri.uMN2setOfSignatures.put("getDefaultHandler", hs);


            // define setDefaultHandler()
        sb.append("\n\tpublic static void setDefaultHandler(RexxProxy rexxproxy) \n");
        sb.append("\t{\n");
        sb.append("\t\tsynchronized (classLock)    \n");
        sb.append("\t\t{                           \n");
        sb.append("\t\t\tdefaultHandler=rexxproxy; \n");
        sb.append("\t\t}                           \n");
        sb.append("\t}\n\n");

        hs=new HashSet<String>();           // memorize that we created a method with this signature
        hs.add("(org.rexxla.bsf.engines.rexx.RexxProxy)");
        ri.uMN2setOfSignatures.put("setDefaultHandler", hs);


            // define getTargetRexxProxy()
        sb.append("\n\tpublic synchronized RexxProxy getTargetRexxProxy() \n");
        sb.append("\t{\n");
        sb.append("\t\treturn target; \n");
        sb.append("\t}\n\n");

        hs=new HashSet<String>();           // memorize that we created a method with this signature
        hs.add("()");
        ri.uMN2setOfSignatures.put("getTargetRexxProxy", hs);


            // define setTargetRexxProxy()
        sb.append("\n\tpublic synchronized void setTargetRexxProxy(RexxProxy newTarget) \n");
        sb.append("\t{\n");
        sb.append("\t\ttarget=newTarget; \n");
        sb.append("\t}\n\n");

        hs=new HashSet<String>();           // memorize that we created a method with this signature
        hs.add("(org.rexxla.bsf.engines.rexx.RexxProxy)");
        ri.uMN2setOfSignatures.put("setTargetRexxProxy", hs);
    }



    /** Creates the epilog statement.
     *
     * @param sb the StringBuffer to append the statements to
     */
    static void createEpilog(StringBuffer sb)
    {
        sb.append("}\n");   // close class block
    }


    /** Creates the base constructor (the default constructor and the default constructor
     *  with a RexxProxy argument) statements, if possible.
     *
     * @param sb the StringBuffer to append the statements to
     * @param newClzName the new name of the new class
     * @param javaClassToProxy the Java (abstract) class that gets extended
     * @param bProxyConstructors determines whether constructor invocations get proxied to the RexxProxy (<code>true</code>)
     * @param ri RunInfos runtime infos for managing the process
     */
    static void createConstructorsBase(StringBuffer sb, String newClzName, Class<?> javaClassToProxy,
                              boolean bProxyConstructors, RunInfos ri)
    {
if (bDebug==true) System.err.println("createConstructorsBase(), begin...");

            // determine whether super's default constructor exists (if so, it needs to be invoked)
        // originally: String throwsClauses[]=new String[] {"", "throws BSFException"};    // default values
        String throwsClauses[]=new String[] {"", ""};    // default values

        if (javaClassToProxy.isInterface()) // implementing a Java interface means that we have to implement the default constructor
        {
            sb.append("\n\t// --- creating base (default) constructor \n");

                // define default constructor
            sb.append("\n\tpublic "+newClzName+"() \n");
            sb.append("\t{\n");
            sb.append("\t\tsuper();                    \n");
            sb.append("\t\ttarget=defaultHandler;      \n");

            if (bProxyConstructors)
            {
                sb.append("\t\ttry {\n");
                sb.append("\t\ttarget.invoke(this, \"<init>\", \"public "+newClzName+"()\", null);   \n");
                sb.append("\t\t} \n\t\tcatch (BSFException be)\n");
                sb.append("\t\t{ \n\t\t\tthrow new RuntimeException(be.toString(), be); \n\t\t}\n");
            }
            sb.append("\t}\n\n");


            // define constructor with one RexxProxy (the target for handling invocations)
            sb.append("\n\tpublic "+newClzName+"(RexxProxy rexxproxy) \n");
            sb.append("\n");
            sb.append("\t{\n");
            sb.append("\t\tsuper();                 \n");
            sb.append("\t\ttarget=rexxproxy;        \n");
                // make sure defaultHandler gets set at least now that an instance gets created (may be
                // too late sometimes, hence programmer should make sure it got set before instantiating the class)
            sb.append("\t\tif (defaultHandler==null)   \n");
            sb.append("\t\t{                           \n");
            sb.append("\t\t\tdefaultHandler=rexxproxy; \n");
            sb.append("\t\t}                           \n");
            if (bProxyConstructors)
            {
                sb.append("\t\ttry {\n");
                sb.append("\t\ttarget.invoke(this, \"<init>\", \"public "+newClzName+"(RexxProxy rexxproxy)\", new Object[] {rexxproxy});   \n");
                sb.append("\t\t} \n\t\tcatch (BSFException be)\n");
                sb.append("\t\t{ \n\t\t\tthrow new RuntimeException(be.toString(), be); \n\t\t}\n");
            }
            sb.append("\t}\n\n");
            return;     // done for implementing this interface class
        }


        boolean bDefaultConstructorExistsInSuper=false;
        try
        {
            Constructor tmpConstr=javaClassToProxy.getDeclaredConstructor( new Class[] {} );
            // if constructor is private, cannot reach it, behave as if not existent
            // bDefaultConstructorExistsInSuper = Modifier.isPublic(tmpConstr.getModifiers());
            int mods=tmpConstr.getModifiers();
            bDefaultConstructorExistsInSuper = Modifier.isPublic(mods) || Modifier.isProtected(mods) ;

            if (bDefaultConstructorExistsInSuper==false)    // not reachable
            {
if (bDebug==true) System.err.println("createConstructorsBase(), default constructor not reachable, RETURNING prematurely.");
                return;
            }

                // return new String[] { throwsClauseOri, throwsClauseWithBSFException };
            throwsClauses=createThrowsClause(tmpConstr.getExceptionTypes());
        }
        catch (Exception e)
        {
if (bDebug==true) System.err.println("createConstructorsBase(), no default constructor defined in javaClassToProxy, hence cannot create these base constructors, returning.");
            return;
        }


        String strMethName="<INIT>";
        HashSet<String> hs=(HashSet<String>) ri.uMN2setOfSignatures.get(strMethName);
        if (hs==null)
        {
if (bDebug==true) System.err.println("createConstructorsBase(), no HashSet for logging created methods/constructors, had to CREATE one here <--- !");
            hs=new HashSet<String>();
            ri.uMN2setOfSignatures.put(strMethName, hs);
        }

if (bDebug==true)
{
    java.util.Iterator i=hs.iterator();

    while (i.hasNext()==true)
    {
        System.out.print("...["+i.next()+"] ");
    }
    System.out.println(" <--- <--- <---\n");

}


        String methSignature="public "+javaClassToProxy.getName()+"()";
        String methSignature2="()";   // for internal use

        // ------------------------ default constructor: () -------------------------
            // this constructor exists in super and was not created yet: create it!
            // ---rgf, 2010-11-30 if (bDefaultConstructorExistsInSuper && (!hs.contains(methSignature2)))
            // if a reachable default constructor exists in superclass, create one here as well
            //      note: an existing public constructor may already have been handled with injecting
            //            a RexxProxy as first (and single) argument
        if (bDefaultConstructorExistsInSuper)
        {
if (bDebug==true) System.err.println("createConstructorsBase(), creating: ["+methSignature2+"] ...");

            sb.append("\n\t// --- creating base (default) constructor \n");

                // define default constructor
            sb.append("\n\tpublic "+newClzName+"() "+(bProxyConstructors ? throwsClauses[1] : throwsClauses[0])+"\n");
            sb.append("\t{\n");

            if (bDefaultConstructorExistsInSuper)
            {
                sb.append("\t\tsuper();                    \n");
            }

            sb.append("\t\ttarget=defaultHandler;      \n");

            if (bProxyConstructors)
            {
                sb.append("\t\ttry {\n");
                sb.append("\t\ttarget.invoke(this, \"<init>\", \""+methSignature+"\", null);   \n");
                sb.append("\t\t} \n\t\tcatch (BSFException be)\n");
                sb.append("\t\t{ \n\t\t\tthrow new RuntimeException(be.toString(), be); \n\t\t}\n");
            }
            sb.append("\t}\n\n");

            // rgf, 2010-11-30a
            if (!hs.contains("()"))
            {
                Constructor tmpConstr=null;
                boolean bSingleRexxProxyConstructor_ExistsInSuper=true; // assume it exists in superclass
                try
                {
                    tmpConstr=javaClassToProxy.getDeclaredConstructor( new Class[] {RexxProxy.class} );
                    // if constructor is private, cannot reach it, behave as if not existent
                    if (Modifier.isPrivate(tmpConstr.getModifiers()))
                    {
                        tmpConstr=null;
                        throwsClauses=new String[] {"", "throws BSFException"};    // default values
                    }
                    else
                    {
                           // return new String[] { throwsClauseOri, throwsClauseWithBSFException };
                        throwsClauses=createThrowsClause(tmpConstr.getExceptionTypes());
                    }
                }
                catch (NoSuchMethodException nsme)  // if it does not exist yet, we must not invoke it
                {
                    bSingleRexxProxyConstructor_ExistsInSuper=false;
                }


/* rgf, 2010-11-30 --> */
if (bDebug==true) System.err.println("createConstructorsBase(), creating default constructor "+"[(RexxProxy rexxproxy)]"+" ...");

                sb.append("\n\tpublic "+newClzName+"(RexxProxy rexxproxy) "+(bProxyConstructors ? throwsClauses[1] : throwsClauses[0])+" \n");

                sb.append("\t{\n");
                if (tmpConstr==null)    // no constructor of this type declared as of yet
                {
                    if (bDefaultConstructorExistsInSuper)
                    {
                        sb.append("\t\tsuper();                 \n");
                    }
                }

                else if (bSingleRexxProxyConstructor_ExistsInSuper) // forward to superclass constructor with the same argument
                {
                    sb.append("\t\tsuper(rexxproxy);        \n");
                }

                sb.append("\t\ttarget=rexxproxy;        \n");

                    // make sure defaultHandler gets set at least now that an instance gets created (may be
                    // too late sometimes, hence programmer should make sure it got set before instantiating the class)
                sb.append("\t\tif (defaultHandler==null)   \n");
                sb.append("\t\t{                           \n");
                sb.append("\t\t\tdefaultHandler=rexxproxy; \n");
                sb.append("\t\t}                           \n");
/* ---rgf, 2010-11-30  */


                if (tmpConstr!=null)
                {
                    methSignature=tmpConstr.toString();
                }
                else
                {
                    methSignature="public "+javaClassToProxy.getName()+"("+STR_REXX_PROXY+")";
                }
                hs.add(methSignature2);     // memorize this

                if (bProxyConstructors)
                {
                    sb.append("\t\ttry {\n");
                    sb.append("\t\ttarget.invoke(this, \"<init>\", \""+methSignature+"\", new Object[] {rexxproxy});   \n");
                    sb.append("\t\t} \n\t\tcatch (BSFException be)\n");
                    sb.append("\t\t{ \n\t\t\tthrow new RuntimeException(be.toString(), be); \n\t\t}\n");
                }
                sb.append("\t}\n\n");

            }

            // rgf, 2010-11-30b
            hs.add(methSignature2);     // memorize this
        }
else if (bDebug==true) System.err.println("createConstructorsBase(), creating: ["+methSignature2+"] - ALREADY CREATED!");


    }


    static void createConstructors(StringBuffer sb, String newClzName, Class javaClassToProxy,
                              boolean bProxyConstructors, RunInfos ri)
    {
if (bDebug==true) System.err.println("createConstructors(), begin...");
            // fetch set of methods created
        HashSet<String> hs=(HashSet<String>) ri.uMN2setOfSignatures.get("<INIT>");
        if (hs==null)
        {
            hs=new HashSet<String>();
            ri.uMN2setOfSignatures.put("<INIT>", hs);
        }


if (bDebug==true)
{
    java.util.Iterator i=hs.iterator();

    while (i.hasNext()==true)
    {
        System.out.print("...["+i.next()+"] ");
    }
    System.out.println(" <=== <=== <=== (1)\n");

}


        Constructor tmpConstr=null;
        // create proxy constructors for existing public constructors
        Constructor constr[]=javaClassToProxy.getDeclaredConstructors();
if (bDebug==true) System.err.println("createConstructors: constr[].length="+constr.length);

        if (constr.length>0)
        {
            sb.append("\n\t// --- creating constructors from existing public and protected constructors using ["+javaClassToProxy.getName()+"] (if any) \n");
        }

        for (int i=0; i<constr.length; i++)
        {
            tmpConstr=constr[i];

if (bDebug==true) System.err.println("---> createConstructors(): tmpConstr=["+tmpConstr.toString()+"]");

            int mods=tmpConstr.getModifiers();
            if (!( Modifier.isPublic(mods) || Modifier.isProtected(mods)) )   // only create for public and protected constructors
            {
                continue;
            }

            Class paramTypes[]=tmpConstr.getParameterTypes();

if (bDebug==true) System.err.println("---> createConstructors(): paramTypes=["+paramTypes+"]...");

                // construct pseudo-signature (without throws-clause)
            String methSignature2=createListOfClassNames(paramTypes, METHOD_SIGNATURE); // createMethodSignatureParen(paramTypes);
            if (hs.contains(methSignature2))    // constructor already created?
            {
if (bDebug==true) System.err.println("---> createConstructors(): methSignature2=["+methSignature2+"] already created!");
                continue;
            }

                // return new String[] { throwsClauseOri, throwsClauseWithBSFException };
            String throwsClauses[]=createThrowsClause(tmpConstr.getExceptionTypes());

            String paramArg[]=createArgumentHead(paramTypes, true, bProxyConstructors);


if (bDebug==true) System.err.println("///  createConstructors(): methSignature2=["+methSignature2+"], paramArg[0]=["+paramArg[0]+"] now created!");

            sb.append("\n\tpublic "+newClzName+paramArg[0]+" "+
                                    (bProxyConstructors ? throwsClauses[1] : throwsClauses[0])+"     \n");

            sb.append("\t{\n");
            sb.append("\t\tsuper"+paramArg[1]+";                    \n");
            sb.append("\t\ttarget=a0;      \n");    // fetch and save RexxProxy argument

                // make sure defaultHandler gets set at least now that an instance gets created (may be
                // too late sometimes, hence programmer should make sure it got set before instantiating the class)
            sb.append("\t\tif (defaultHandler==null) \n");
            sb.append("\t\t{                         \n");
            sb.append("\t\t\tdefaultHandler=a0;      \n");
            sb.append("\t\t}                         \n");

            if (bProxyConstructors)
            {
                sb.append("\t\t"+paramArg[2]+"\n");     // define arguments as "Object[] obj"
                sb.append("\t\ttry {\n");
                sb.append("\t\ttarget.invoke(this, \"<init>\", \""+tmpConstr.toString()+"\", obj);   \n");
                sb.append("\t\t} \n\t\tcatch (BSFException be)\n");
                sb.append("\t\t{ \n\t\t\tthrow new RuntimeException(be.toString(), be); \n\t\t}\n");
            }

            sb.append("\t}\n\n");
            hs.add(methSignature2);     // memorize that we created this constructor
        }


if (bDebug==true)
{
    java.util.Iterator i=hs.iterator();

    while (i.hasNext()==true)
    {
        System.out.print("...["+i.next()+"] ");
    }
    System.out.println(" <=== <=== <=== (2)\n");

}
if (bDebug==true) System.err.println("createConstructors(), END. <--");

    }


    /** Controls the creation of the abstract interface methods.
     *
     * @param sb the StringBuffer to append the statements to
     * @param newClzName the new name of the new class
     * @param ifClz an interface class which needs to processed together with its super interface classes if any
     * @param ri RunInfos runtime infos for managing the process
     * @param tmpClz <code>null</code> (create all methods) or a class object which will be checked
     *                (create proxy method only, if no concrete declaredMethod() of it exists)
     *
     */
// TODO: make sure we proxy also default interface methods, if "*" was supplied ?
    static void createInterfaceMethods(StringBuffer sb, String newClzName, Class ifClz, RunInfos ri, Class<?> tmpClz)    // rgf, 2017-02-09: create methods for the interface classes
    {
        createInterfaceMethodsWorker(sb, newClzName, ifClz, ri, tmpClz);

        // we must proxy all interface methods of "super interfaces"
        for (Class tmpIfClz : ifClz.getInterfaces())  // iterate over super interfaces if any
        {
            createInterfaceMethods(sb, newClzName, tmpIfClz, ri, tmpClz);   // recurse
        }
    }

    static void createInterfaceMethodsWorker(StringBuffer sb, String newClzName, Class ifClz, RunInfos ri, Class <?> tmpClz)
    {
        sb.append("\n\t// --- creating methods for interface ["+ifClz.getName()+"]" +
//                  (tmpClz==null? "" : " for ["+tmpClz.getName()+"]") +
                  ", if any and not yet created \n");

        // create abstract methods
        Method []declMethods=null;
        if (ri.clz2declMethods.containsKey(ifClz))     // declared methods for this class already available
        {
            declMethods=(Method []) ri.clz2declMethods.get(ifClz);
        }
        else    // get declared methods, cache them, in case we need them for processing the method list later
        {
            declMethods=ifClz.getDeclaredMethods();
            ri.clz2declMethods.put(ifClz, declMethods);// save declared methods
        }

        for (int i=0; i<declMethods.length; i++)        // create implementations for abstract methods, if necessary
        {
            Method m=declMethods[i];
            if (bDebug==true) System.err.println("createInterfaceMethods(): declMethods, i=["+i+"], m=["+m.toString()+"]");

            // rgf, 2018-02-19: if interface method is default or static (starting with Java 1.8/8), then ignore
            if (!Modifier.isAbstract(m.getModifiers())) // only process abstract interface methods
            {
                if (bDebug==true) System.err.println("createInterfaceMethods(): skipping concrete interface method, i=["+i+"], m=["+m.toString()+"]");
                continue;
            }

            // check method against the abstract class object's declaredMethods; is there a concrete
            // (non-abstract) one available already? If so, do not create a proxy method for it.
            try
            {
                Method dm=tmpClz.getDeclaredMethod(m.getName(), m.getParameterTypes());
                // declared method is not abstract?
                int mods=dm.getModifiers();
                if (Modifier.isAbstract(mods))      // make sure we implement proxy method only, if
                {
                    createMethod(sb, m, newClzName, ri);         // create method code
                }
            }
            catch (NoSuchMethodException nsme)
            {
                createMethod(sb, m, newClzName, ri);         // create method code
            }
        }
    }



    /** Controls which public methods get created on the fly. All abstract methods get implemented, optionally methods
     * that are supplied in the <code>methods2proxy</code> argument. As each method must have a
     * unique signature, hence no method with a unique signature is created more than once.
     *
     * @param sb the StringBuffer to append the statements to
     * @param newClzName the new name of the new class
     * @param javaClassToProxy the Java (abstract) class that gets extended
     * @param methods2proxy    String list of method names to create proxies for; a method name
     *                         may be prepended with the name of a superclass delimited with a blank
     * @param ri RunInfos runtime infos for managing the process
     *
     * @throws BSFException if any execution error occurs
     */
    static void createMethods(StringBuffer sb, String newClzName, Class javaClassToProxy,
                              String [] methods2proxy, RunInfos ri) throws BSFException
    {
// boolean bDebug=true;
if (bDebug==true) System.err.println("createMethods(), begin...");

        if (methods2proxy==null || methods2proxy.length==0)    // nothing to proxy supplied
        {
if (bDebug==true) System.err.println("///\tmethods2proxy " + (methods2proxy==null ? "is null" : "is empty") +": no methods get created, returning" );
            return;
        }

        sb.append("\n\t// --- creating methods according to String array of methods to proxy (if any) \n");

        for (int i=0; i<methods2proxy.length; i++)
        {
if (bDebug==true) System.err.println("///---> next, i=["+i+"]\ncreateMethods(), i=["+i+"] methods2proxy[i]=["+methods2proxy[i]+"]");
            if (methods2proxy[i]==null || methods2proxy[i].compareTo("")==0)    // ignore null and empty strings
            {
                continue;
            }


            String [] arrM2P=parseMethod2Proxy(methods2proxy[i]);

            if (arrM2P==null)   // wrong string content (empty or too many words)
            {
                throw new BSFException("ClassAdapter.createMethods(): erroneous value for the "+
                                      (i+1)+(i==0 ? "st" : (i==1 ? "nd" : (i==2 ? "rd" : "th") ) )+
                                       " (1-based) entry in the supplied String array of methods to proxy: ["+methods2proxy[i]+"].");
            }

            Class tmpClz=javaClassToProxy;  // default to class to be extended

            if (arrM2P[0]!=null)        // an explicit class to be used, find the class object and check whether it is a superclass
            {
if (bDebug==true) System.err.println("createMethods(), arrM2P[0]=["+arrM2P[0]+"], arrM2P[1]=["+arrM2P[1]+"], arrM2P[2]=["+arrM2P[2]+"], arrM2P[3]=["+arrM2P[3]+"], tmpClz=["+tmpClz.toString()+"]");
                tmpClz=(Class) ri.uCN2clz.get(arrM2P[1]);   // try to get class object
                if (tmpClz==null)       // not yet in map
                {
                    for (Class wrkClz=javaClassToProxy.getSuperclass(); wrkClz!=null; wrkClz=wrkClz.getSuperclass() )
                    {
if (bDebug==true) System.err.println("createMethods(), wrkClz=["+wrkClz+"]=["+wrkClz.toString()+"]");

                        String clzName=wrkClz.getName().toUpperCase();
                        if (ri.uCN2clz.containsKey(clzName))   // already handled
                        {
                            continue;
                        }
                        ri.uCN2clz.put(clzName, wrkClz);    // save class object
                        if (clzName.compareTo(arrM2P[1])==0)  // found !
                        {
                            tmpClz=wrkClz;
                            newClzName=arrM2P[0];
                            break;
                        }
                    }
                }

                if (tmpClz==null)   // class not found, resp. not a superclass !
                {
                    throw new BSFException("ClassAdapter.createMethods(): erroneous value (class not found or not a superclass) for the "+
                                          (i+1)+(i==0 ? "st" : (i==1 ? "nd" : (i==2 ? "rd" : "th") ) )+
                                           " (1-based) entry in the supplied String array of methods to proxy: ["+methods2proxy[i]+"].");

                }
            }

            if (arrM2P[3].compareTo("<INIT>")==0)   // proxy constructors ?
            {
if (bDebug==true) System.err.println("---> BINGO !    <INIT>   BINGO! <---, newClzName=["+newClzName+"], tmpClz=["+tmpClz+"]");
                createConstructors(sb, newClzName, tmpClz, true, ri);   // create and proxy them!
                continue;
            }

            // get all declared methods, analyze them and if matching, create proxied method
            // for non-abstract methods with the suffix "_forwardToSuper"
            Method []meths=(Method []) ri.clz2declMethods.get(tmpClz);
            if (meths==null)    // no declared methods cached so far
            {
                meths=tmpClz.getDeclaredMethods();
                ri.clz2declMethods.put(tmpClz, meths);  // cache declared methods
            }
                // find method with the same name (can be multiple methods due to case and/or signature)
            boolean bMethodFound=false;
            for (int k=0; k<meths.length; k++)
            {
                if (meths[k].isSynthetic())     // skip synthetic (bridge) methods
                {
                    continue;
                }

                String tmpName=meths[k].getName().toUpperCase();
                if (tmpName.compareTo(arrM2P[3])!=0)        // not found?
                {
                    continue;
                }
                int mMods=meths[k].getModifiers();

                // rgf, 20140830: allow to override protected methods too as a subclass is allowed to invoke it
                if ((mMods & (Modifier.FINAL | Modifier.PRIVATE )) != 0)    // ignore if final or private
                {
                    continue;   // don't proxy
                }

                bMethodFound=true;

if (bDebug==true) System.err.println("createMethods(), k="+k+", about to call createMethod, methName ["+meths[k].getName()+"] BINGO!!!");

                createMethod(sb, meths[k], newClzName, ri);
            }

            if (bMethodFound==false)    // no method by the given name found !
            {
                throw new BSFException("ClassAdapter.createMethods(): no public nor protected method definition to override found for the "+
                                       (i+1)+(i==0 ? "st" : (i==1 ? "nd" : (i==2 ? "rd" : "th") ) )+
                                       " (1-based) entry in the supplied String array of methods to proxy: ["+methods2proxy[i]+"].");
            }

        }
    }




    /** Creates a public method proxy for the supplied Method object. If a method with the same signature (matching
     *  method name and parameter types) got created already, it will not be created again.
     *
     * @param sb the StringBuffer to append the statements to
     * @param m a Method object which needs to get proxied
     * @param newClzName the new name of the new class
     * @param ri RunInfos runtime infos for managing the process
     */
    static void createMethod (StringBuffer sb, Method m, String newClzName, RunInfos ri)
    {
if (bDebug==true) System.err.println("---> createMethod() | 1 - method: "+m);

        Class paramTypes[]=m.getParameterTypes();

            // construct pseudo-signature (without throws-clause)
        String methSignature2=createListOfClassNames(paramTypes, METHOD_SIGNATURE); //  createMethodSignatureParen(paramTypes);

        String methName   =m.getName();

        HashSet<String> hs=(HashSet<String>) ri.uMN2setOfSignatures.get(methName);
        if (hs==null)
        {
            hs=new HashSet<String>();
            ri.uMN2setOfSignatures.put(methName, hs);
        }
        else if (hs.contains(methSignature2))    // method with this signature already created?
        {

            if (bDebug==true) System.err.println("---> createMethod() | 2 - already created signature: "+methSignature2);
            return;
        }

        String strMethName=methName.toUpperCase();

        int     iModifiers=m.getModifiers();

        // create forwardToSuper-method only, if not an abstract method
        boolean bCreateForwardToSuper=!(Modifier.isAbstract(iModifiers));

        String  strModifiers="public ";
        boolean bIsStatic=Modifier.isStatic(iModifiers);

        if (bIsStatic)
        {
            strModifiers=strModifiers+"static ";
        }


        Class returnType=m.getReturnType();
        // String strReturnType=returnType.getName();
        String strReturnType=getEditedClassName(returnType);

        strModifiers=strModifiers+strReturnType;    // add return type

            // return new String[] { throwsClauseOri, throwsClauseWithBSFException };
        String throwsClauses[]=createThrowsClause(m.getExceptionTypes());

        String paramArg[]=createArgumentHead(paramTypes, false, true);
        String retStmts  =createReturnStatements(returnType);

        sb.append("\n\t"+strModifiers+" "+methName+paramArg[0]+' '+throwsClauses[1]+"\n");
        sb.append("\t{\n");
        sb.append("\t\t"+paramArg[2]+"\n");     // define obj=....

            // invoke RexxProxy
        sb.append("\t\tObject    res=null;     \n");

        String strThis="this";  // default to this object
        if (bIsStatic)
        {
            sb.append("\t\tRexxProxy rpTarget=defaultHandler;     \n");
            strThis=newClzName+".class";    // supply the class object
        }
        else
        {
            sb.append("\t\tRexxProxy rpTarget=(target==null ? defaultHandler : target);     \n");
        }

        sb.append("\t\ttry {\n");
        sb.append("\t\t\tres=rpTarget.invoke("+strThis+", \""+methName+"\", \""+strModifiers+" "+methName+methSignature2+"\", obj); \n");
        sb.append("\t\t} \n\t\tcatch (BSFException be)\n");
        sb.append("\t\t{ \n\t\t\tthrow new RuntimeException(be.toString(), be); \n\t\t}\n");

        sb.append(retStmts);
        sb.append("\t}\n\n");

            // forward to super
        if ( bCreateForwardToSuper )
        {

if (bDebug==true) System.err.println("---> createMethod() |     created 'forwardToSuper' method as well");
            sb.append("\n\t"+strModifiers+" "+methName+
                            "_forwardToSuper"+
                             paramArg[0]+' '+throwsClauses[0]+"\n");

            sb.append("\t{\n");

                // determine whether returning a value is necessary
            String strReturnStmt="return ";
            if (returnType==Void.class || returnType==void.class)
            {
                strReturnStmt="";
            }

                // determine target of method invocation
            String strTarget="super";
            if (bIsStatic)
            {
                strTarget=getEditedClassName(m.getDeclaringClass());

            }

            sb.append("\t\t"+strReturnStmt+
                             strTarget+"."+methName+
                             paramArg[1]+";    \n");   // forward to super

            sb.append("\t}\n\n");
        }

        hs.add(methSignature2);     // memorize that we created a method with this signature
    }



    /** Parses a string from Rexx that consists of "[Java_Class_Name ]method_name".
     *
     * @param val a String value which denotes an optional Java class name, followed by a blank,
     *            followed by a mandatory method name. All methods with the same name in the given
     *            class will get proxied. If no class name is given, the method name relates to the
     *            extended Java class.
     * @return    <code>null</code>, if supplied argument is null or empty, else a
     *            String array, containing
     *            <dl>
     *            <dt>index <code>0</code>
     *            <dd>the mixed case Java class name or null, if missing
     *
     *            <dt>index <code>1</code>
     *            <dd>the upper case Java class name or null, if missing
     *
     *            <dt>index <code>2</code>
     *            <dd>the mixed case method name
     *
     *            <dt>index <code>3</code>
     *            <dd>the upper case Java method name
     *
     *            </dl>
     */
    static String [] parseMethod2Proxy (String val)
    {
        StringBuffer sb[]={new StringBuffer(), new StringBuffer()};

        int jIdx=-1,
            len =val.length();

        for (int i=0; i<len; i++)
        {
            char c=val.charAt(i);       // get char

            if (c==' ' || c=='\t')      // skip white-space
            {
                continue;
            }

            // a word char found!
            jIdx++;
            if (jIdx>1)         // more than two words in string, an error, indicate error condition!
            {
                return null;
            }

            sb[jIdx].append(c);
            i++;
            for (; i<len; i++)
            {
                c=val.charAt(i);   // get char

                if (c==' ' || c=='\t')  // break, if white-space
                {
                    break;
                }
                sb[jIdx].append(c);     // add char
            }
        }

        if (jIdx<0)         // no chars found
        {
            return null;
        }

        if (jIdx==0)        // only one word found
        {

            String s=sb[0].toString();
            return new String[] { null, null,           // no class name
                                  s, s.toUpperCase()    // method name
                                };
        }

        String s0=sb[0].toString(),
               s1=sb[1].toString();

        return new String[] { s0, s0.toUpperCase(),     // class name
                              s1, s1.toUpperCase()      // method name
                            };

    }


    /** Creates an argument list as a String from the supplied array of Classes representing the types.
     *  @param paramTypes array of Class objects representing the types
     *  @param bCreateForConstructor first String should inject as first argument the RexxProxy to forward to
     *  @param bCreateInvokeArg determines whether the invocations of constructors get forwarded to the RexxProxy
     *
     * @return a String array, where index <code>0</code> represents the parenthized argument list,
     *         index <code>1</code> represents the parenthized argument list without the RexxProxy argument,
     *         index <code>2</code> is either null or defines an array of type Object to allow the received
     *         arguments to be forwarded to the rexxProxy
     *
     */
    static String [] createArgumentHead (Class [] paramTypes, boolean bCreateForConstructor, boolean bCreateInvokeArg)
    {
        StringBuffer sb1=new StringBuffer(),    // first arg is RexxProxy, args have type information
                     sb2=new StringBuffer(),    // first arg is second arg (dropped RexxProxy)
                     sb3=null;                  // first arg is second arg, cast primitives to their wrapper type

        sb1.append("(");

        if (bCreateForConstructor)   // for constructors
        {
            sb1.append(STR_REXX_PROXY).append(" a0");
            if (paramTypes.length>0)
            {
                sb1.append(", ");
            }
        }

        sb2.append("(");

        if (bCreateInvokeArg)   // arguments for forwarding to RexxProxy
        {
            sb3=new StringBuffer();
            sb3.append("Object []obj=new Object[] { ");
        }

        for (int i=0; i<paramTypes.length; i++)
        {
            int argNr=i+1;                  // number to use for argument
            Class tmpClz=paramTypes[i];
            String tmpArg=getEditedClassName(tmpClz)+" "+"a"+argNr;

            if (i>0)
            {
                sb1.append(", ");
                sb2.append(", ");

                if (bCreateInvokeArg)
                {
                    sb3.append(", ");
                }
            }
            sb1.append(tmpArg);
            sb2.append("a"+argNr);

            if (bCreateInvokeArg)
            {
                String tmpStr=null;
                if (tmpClz.isPrimitive())   // wrap primitive types
                {
                    // as code should run on Java 1.4, do not rely on existence of "valueOf()"
                        if (tmpClz==boolean.class) { sb3.append("Boolean.valueOf(a"+argNr+")"); }
                   else if (tmpClz==byte.class   ) { sb3.append("new Byte(a"+argNr+")"       ); }
                   else if (tmpClz==char.class   ) { sb3.append("new Character(a"+argNr+")"  ); }
                   else if (tmpClz==short.class  ) { sb3.append("new Short(a"+argNr+")"      ); }
                   else if (tmpClz==int.class    ) { sb3.append("new Integer(a"+argNr+")"    ); }
                   else if (tmpClz==float.class  ) { sb3.append("new Float(a"+argNr+")"      ); }
                   else if (tmpClz==double.class ) { sb3.append("new Double(a"+argNr+")"     ); }
                   else if (tmpClz==long.class   ) { sb3.append("new Long(a"+argNr+")"       ); }
                }
                else    // just forward argument
                {
                    sb3.append("a"+argNr);
                }
            }
        }

        sb1.append(")");
        sb2.append(")");
        return new String [] {sb1.toString(), sb2.toString(), (sb3==null ? null : sb3.append(" };").toString())};
    }




    /** Creates the statements for returning the result of invoking the RexxProxy, possibly
     *  cast to primitive types. This method assumes that the result of invoking a method on
     *  a RexxProxy will be stored in a local variable named &quot;res&quot;.
     *
     * @param returnType the class object of the return type
     * @return returns a string representing the necessary statements to return the return value with the proper type
    */
    static String createReturnStatements(Class returnType)
    {
            // nothing to return?
        if (returnType==Void.class || returnType==void.class || returnType==null)
        {
            return "";
        }


        StringBuffer sb=new StringBuffer();
        // String returnTypeName=returnType.getName();
        String returnTypeName=getEditedClassName(returnType);

        if (!returnType.isPrimitive())  // return value cast to return type
        {
            if (returnType == Boolean.class   ||
                returnType == Byte.class      ||
                returnType == Character.class ||
                returnType == Short.class     ||
                returnType == Integer.class   ||
                returnType == Long.class      ||
                returnType == Float.class     ||
                returnType == Double.class    )
            {
                sb.append("\t\tif (res instanceof String)    \n")
                  .append("\t\t{ \n");

                if (returnType==Boolean.class)
                {
                    sb.append("\t\t\tif ( ((String) res).compareTo(\"1\")==0 ) \n");
                    sb.append("\t\t\t{                   \n");
                    sb.append("\t\t\t\tres=Boolean.TRUE; \n");
                    sb.append("\t\t\t}                   \n");
                    sb.append("\t\t\telse                \n");
                    sb.append("\t\t\t{                   \n");
                    sb.append("\t\t\t\tres= new "+returnTypeName+"((String) res); \n");
                    sb.append("\t\t\t}                   \n");
                }
                else if (returnType==Character.class)
                {
                    sb.append("\t\t\tres=((String) res).charAt(0); \n");
                }
                else
                {
                    sb.append("\t\t\tres= new "+returnTypeName+"((String) res); \n");
                }
                sb.append("\t\t} \n");
            }
            return sb.append("\t\treturn ("+returnTypeName+") res; \n").toString();
        }

        // primitive types
        String objTypeName=null;

             if (returnType==boolean.class) { objTypeName="Boolean";   }
        else if (returnType==byte.class   ) { objTypeName="Byte";      }
        else if (returnType==char.class   ) { objTypeName="Character"; }
        else if (returnType==short.class  ) { objTypeName="Short";     }
        else if (returnType==int.class    ) { objTypeName="Integer";   }
        else if (returnType==float.class  ) { objTypeName="Float";     }
        else if (returnType==double.class ) { objTypeName="Double";    }
        else if (returnType==long.class   ) { objTypeName="Long";      }


        sb.append("\t\tif (res==null)                   \n")
          .append("\t\t{ \n")
          .append("\t\t\tthrow new ClassCastException(\"Result returned from RexxProxy '\"+rpTarget+\"' is 'null', ")
          .append(      "which cannot be cast to the primitive return type '")
          .append(      returnTypeName+"'.\");    \n")
          .append("\t\t} \n");


        sb.append("\t\tif (res instanceof String)    \n")
          .append("\t\t{ \n");

        if (returnType==boolean.class)
        {
            sb.append("\t\t\tif ( ((String) res).compareTo(\"1\")==0 ) \n");
            sb.append("\t\t\t{                   \n");
            sb.append("\t\t\t\tres=Boolean.TRUE; \n");
            sb.append("\t\t\t}                   \n");
            sb.append("\t\t\telse                \n");
            sb.append("\t\t\t{                   \n");
            sb.append("\t\t\t\tres= new "+objTypeName+"((String) res); \n");
            sb.append("\t\t\t}                   \n");
        }
        else if (returnType==char.class)
        {
            sb.append("\t\t\tres=((String) res).charAt(0); \n");
        }
        else
        {
            sb.append("\t\t\tres= new "+objTypeName+"((String) res); \n");
        }
        sb.append("\t\t} \n");

        return sb.append("\t\treturn (("+objTypeName+") res)."+returnTypeName+"Value(); \n").toString();
    }



    /** Create comma-separated list of parameter types, enclosed in round parenthesis.

     * @param paramTypes array of Class objects
     * @param kind describes the kind (method signature or a throws clause)
     *
     * @return the clause depending on on the kind
    */
    // static String createMethodSignatureParen(Class [] paramTypes, boolean bMethodSignature)
    static String createListOfClassNames(Class [] paramTypes, int kind)
    {
        if (paramTypes==null || paramTypes.length==0)
        {
            if (kind==METHOD_SIGNATURE)
            {
                return "()";
            }
            else if (kind==THROWS_CLAUSE)
            {
                return null;
            }
            return "";
        }

        StringBuffer sb=new StringBuffer();

        if (kind==METHOD_SIGNATURE)
        {
            sb.append("(");
        }
        else if (kind==THROWS_CLAUSE)
        {
            sb.append("throws ");
        }

        for (int i=0; i<paramTypes.length; i++)
        {
            if (i>0)
            {
                sb.append(",");
            }
            sb.append(getEditedClassName(paramTypes[i]));
        }

        if (kind==METHOD_SIGNATURE)
        {
            sb.append(")");
        }

        return sb.toString();
    }




    /** Create  comma-separated list of parameter types, enclosed in round parenthesis.
     *
     * @param excTypes array of Class objects representing the exceptions that may be thrown
     * @return a String array with two elements, the first entry representing the original list of
     *         thrown exceptions, the second entry used to extend this list by the <code>BSFException</code>,
     *         which since 2019-08-26 is not the case anymore, therefor the second entry is the same
     *         as the first one
     *
    */
    static String[] createThrowsClause(Class []excTypes)
    {
        // Class []excTypes=tmpConstr.getExceptionTypes();
        String throwsClause=createListOfClassNames(excTypes, THROWS_CLAUSE);

        if (throwsClause==null)
        {
            return new String[] { "", ""};
        }

        return new String[] { throwsClause, throwsClause };

/* we do not add BSFException anymore as this would be illegal for JDK
        String throwsClause=throwsClauseOri;

        else
        {
                // BSFException already listed? If not, append it.
            boolean bFound=false;
            for (int t2=0; t2<excTypes.length && bFound==false; t2++)
            {
                bFound=(excTypes[t2]==RexxProxy.class);
            }

            if (bFound==false)      // not found, hence add it!
            {
                throwsClause=throwsClause+",BSFException";
            }
        }

        return new String[] { throwsClauseOri, throwsClause };
*/
    }


    /** Creates and returns the name of the supplied class, taking array classes into consideration.
     * @param clz the class to create a name for
     * @return the class name
     */
    static String getEditedClassName(Class clz)
    {
        if (!clz.isArray())
        {
            return clz.getName();
        }

            // an array class in hand, get base component type, count dimensionality
        Class compType=clz;
        int i=0;
        for ( ; compType.isArray(); compType=compType.getComponentType())
        {
            i++;
        }

        StringBuffer sb=new StringBuffer();
        sb.append(compType.getName());      // get class name of component type
        for (int k=0; k<i; k++)
        {
            sb.append("[]");        // append
        }
        return sb.toString();
    }


    /** Writes generated code to the file system using {@link #pathToSaveTo} as the location.
     *
     *  @param fileName the name to use for storing the generated code
     *  @param generatedCode the generated Java source
     *
     *  @return true if writing the file succeeded, false if {@link #bSaveToFilesystem} is false
     *  @throws BSFException in case an I/O error occurs
     */
    static boolean writeToFile(String fileName, String generatedCode) throws BSFException
    {
        if (bSaveToFilesystem==false)
        {
            return false;       // indicate no file created
        }

        FileOutputStream outFile=null;

        try
        {
            outFile=new FileOutputStream(pathToSaveTo+fileSeparator+fileName);
// System.err.println("writeToFile(...), path=["+pathToSaveTo+fileSeparator+fileName+"]");
            outFile.write(generatedCode.getBytes());
            outFile.close();
        }
        catch (Exception exc)
        {
            exc.printStackTrace();
            throw new BSFException (BSFException.REASON_OTHER_ERROR, exc.toString(), exc);
        }
        return true;
    }



    /** Creates and formats a {@link Date} object. Depending on {@link #bFileNameWithDateOnly}
     *  the time portion may be left out.
     *
     * @return formatted {@link Date} object representing the date and time of invocation
     */
    public static String formatDateTime()
    {
        if (bFileNameWithDateOnly)       // use Date and Time portion
        {
            return sdf_d.format(new Date());
        }
        return sdf_dt.format(new Date());    // use Date portion only
    }

    /** Formats a {@link Date} object.  Depending on {@link #bFileNameWithDateOnly}
     *  the time portion may be left out.
     *
     * @param d the {@link Date} object to format
     * @return formatted {@link Date} object representing
     */
    public static String formatDateTime(Date d)
    {
        if (bFileNameWithDateOnly)       // use Date and Time portion
        {
            return sdf_d.format(d);
        }
        return sdf_dt.format(d);    // use Date portion only
    }

    /** Calculate the CRC32 value for the supplied string and return an uppercased hex-string.
     *
     * @param str the String for which the checksum should get calculated
     * @return the uppercased hexadecimal value
     */
    public static String calcCRC32(String str)
    {
        if (str==null)      // return null, if no String
        {
            return null;
        }
        byte [] barrData=str.getBytes();            // get byte array
        Checksum cs=new CRC32();                    // get CRC32 Checksum object
        cs.update(barrData, 0, barrData.length);    // calc CRC32
        return Long.toHexString(cs.getValue()).toUpperCase();
    }

/** This method will create a file name that by default gets built in the following
 *  form: <code>SimpleClassName_ClassName_$RexxExtendClass$_CRC32_DATE_TIME_MSEC.java</code>.
 *  The generation of the file name is controlled by {@link #bFileNameWithSimpleName},
 *  {@link #bFileNameWithDateTime} and {@link #bFileNameWithDateOnly}.
 *
 *  @param strClzSimpleName the simple name of the extended class
 *  @param strClzName the full name of the extended class
 *  @param newClzName the new full name of the class
 *  @param strMethodNames2proxy the list of method names to proxy
 *  @param strInterfaceClasses the list of interface classes to proxy
 *
 *  @return the file name that should be used for storing the generated proxy class
 */

static String createFileName(final String strClzSimpleName,
                             final String strClzName,
                             final String newClzName,
                             final String strMethodNames2proxy,
                             final String strInterfaceClasses,
                             final String strCRC32
                             )

    {
            // String that gets used for calculating CRC32; this way the CRC32 will
            // indicate that generated classes may have identical content
//        String strToCalcCRC32=strClzName+"-"+newClzName+"-"+strMethodNames2proxy+"-"+strInterfaceClasses;
//        String strCRC32=calcCRC32(strToCalcCRC32);

        StringBuffer sb=new StringBuffer();

        if (bFileNameWithSimpleName)    // lead in with simple class name?
        {
            sb.append(strClzSimpleName).append("_");
        }

        sb.append(strClzName).append("_").append(extensionIndicator).append("_").append(strCRC32);

        if (bFileNameWithDateTime)      // include date/time?
        {
            sb.append("_").append(formatDateTime());
        }
        sb.append(".java");             // append file type

        // String tmpFileName=strClzSimpleName+"_"+strClzName+"_$RexxExtendClass$_"+strCRC32+"_"+formatDateTime()+".java";

        String tmpFileName=sb.toString();

// System.out.println("strToCalcCRC32=["+strToCalcCRC32+"]");
// System.out.println("\ttmpFileName=["+tmpFileName+"]\n");

       return tmpFileName;
    }






    /** Inner class to make maintenance and access to management data structures easy.
    *
    *     uCN       ... uppercased class name
    *     uMN       ... uppercased method name
    *     clz       ... class object
    *
    */
    static class RunInfos
    {
        HashMap<String,HashSet<String>>   uMN2setOfSignatures=new HashMap<String,HashSet<String>>();    // maps uppercased method name
        HashMap<Class<?>,Method[]>        clz2declMethods    =new HashMap<Class<?>,Method[]>();    // maps class object to declared methods
        HashMap<String,Class<?>>          uCN2clz            =new HashMap<String,Class<?>>();    // maps uppercase class name to class object
    }

}
