InternalEntry.java
/*
** Module : InternalEntry.java
** Abstract : stores metadata relating to an internal entry
**
** Copyright (c) 2013-2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
** 001 GES 20131124 Moved this code out of SourceNameMapper into its own class. This enables
** cleaner separation and modularity. Added synchronization and made changes
** for RETURN support.
** 002 ECF 20140918 Reduced memory footprint.
** 003 ECF 20150424 Minor memory tweak.
** 004 ECF 20150601 Minor performance fix.
** 005 OM 20150717 Fixed range check for parameters.
** 006 ECF 20150802 Removed synchronization, which was a performance bottleneck. Made constructors
** protected to ensure instances are not created elsewhere, other than by
** subclasses which presumably will be written with an understanding of thread
** safety. See class description notes for explanation of why unsynchronized
** methods are safe.
** 007 CA 20160418 Added getter for "parameters" field.
** 008 ECF 20180818 Avoid use of expensive String.format.
** 009 CA 20181029 Exposed setParameterList() and initialize() to sub-classes.
** 010 CA 20181216 Fixed loading OO methods, constructors and destructors.
** 011 CA 20190219 OO members (methods, constructors, class events) are annotated via the
** LegacySignature and statically linked with the converted Java method.
** CA 20190324 Cache the libname and mapTo values.
** 012 CA 20190520 Added some more OO method types, for internal FWD usage.
** 013 CA 20190604 Fixed a typo for LENGTH-OF name.
** 014 ME 20200518 Added VARIABLE and PROPERTY.
** 015 CA 20200605 The Java method instance is resolved on first access of getMethod().
** 016 ME 20200810 Added qualified name support in signature processing.
** CA 20210218 Fixed signature processing for qualified return type.
** CA 20220909 Fixed TABLE-HANDLE, DATASET, DATASET-HANDLE parameters reported by GET-SIGNATURE.
** 017 HC 20230125 Added caching of resolved procedure internal entries.
** 018 CA 20230928 Small optimization in 'initialize', user plain Java for instead of 'for each'.
** 019 CA 20231213 The synthetic 'execute' methods emitted for legacy classes can be dropped if there is
** nothing emitted in them. These Java methods are also marked with Type.EXECUTE
** LegacySignature annotation, and their BlockManager API removed - the FWD runtime will run
** this only once.
*/
/*
** 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.util;
import java.lang.reflect.*;
import java.util.*;
import com.goldencode.p2j.util.SourceNameMapper.ExternalProgram;
/**
* Container for legacy info belonging to an internal entry (a user-defined internal procedure,
* function or external API call).
* <p>
* The methods of this class are unsynchronized because the methods of this class are invoked
* very frequently and testing and profiling indicates that a synchronized architecture creates
* a bottleneck. Instances of this class are only (and MUST only) be constructed and initialized
* from within synchronized code within {@link SourceNameMapper}. Although there are public
* methods in this class which access internal state, they are only invoked after all internal
* state has been set. Subclasses must be aware of this restriction to ensure they are thread
* safe.
*/
public class InternalEntry
{
/** Type of internal entry */
public static enum Type
{
/** User defined function */
FUNCTION("FUNCTION"),
/** Internal procedure */
PROCEDURE("PROCEDURE"),
/** Object method */
METHOD("METHOD"),
/** Class event */
EVENT("EVENT"),
/** Event's subscribe method definition */
SUBSCRIBE("SUBSCRIBE"),
/** Event's unsubscribe method definition */
UNSUBSCRIBE("UNSUBSCRIBE"),
/** Event's publish method definition */
PUBLISH("PUBLISH"),
/** Property's getter method. */
GETTER("GET"),
/** Property's setter method. */
SETTER("SET"),
/** Extent property's resize method. */
RESIZE("RESIZE"),
/** Extent property's length-of method. */
LENGTH("LENGTH-OF"),
/** Class constructor */
CONSTRUCTOR("CONSTRUCTOR"),
/** Class destructor */
DESTRUCTOR("DESTRUCTOR"),
/** Native procedure */
DLL_ENTRY("DLL-ENTRY"),
/** User defined function defined in another procedure */
EXTERN("EXTERN"),
/** Main procedure */
MAIN("MAIN"),
/** Synthetic 'execute' methods emitted for legacy classes. */
EXECUTE("EXECUTE"),
/** Object variable */
VARIABLE("VARIABLE"),
/** Object property */
PROPERTY("PROPERTY"),
;
/** Map of Progress entry names to enum values */
private static Map<String, Type> map = new HashMap<>(8);
static
{
for (Type t : Type.values())
{
map.put(t.name, t);
}
}
/** Progress entry name */
private final String name;
/**
* Constructor.
*
* @param name
* Progress entry name.
*/
Type(String name)
{
this.name = name.intern();
}
/**
* Given a Progress entry name, retrieve the appropriate enum value.
*
* @param name
* Progress entry name.
*
* @return Associated enum value.
*
* @throws IllegalArgumentException
* if the given Progress entry name is unrecognized.
*/
public static Type fromString(String name)
{
Type t = map.get(name);
if (t != null)
{
return t;
}
throw new IllegalArgumentException("Unrecognized InternalEntry type: " + name);
}
/**
* Return the Progress entry name.
*
* @return Progress entry name.
*/
public String toString()
{
return name;
}
};
public static enum SerializeMode {
DEFAULT,
VISIBLE,
HIDDEN
}
/** Identify parameters in INPUT mode. */
public static final char INPUT_MODE = 'I';
/** Identify parameters in INPUT-OUTPUT mode. */
public static final char INPUT_OUTPUT_MODE = 'U';
/** Identify parameters in OUTPUT mode. */
public static final char OUTPUT_MODE = 'O';
/** Identify buffer parameters. */
public static final char BUFFER_MODE = 'B';
/** Legacy 4GL name for this internal-entry. */
final String pname;
/** Converted Java name for this internal-entry. */
final String jname;
/** The type of this internal entry. */
private final Type type;
/**
* The signature of this internal entry, as returned by the
* <code>get-signature</code> 4GL API.
*/
private String signature = null;
/** The string representation of the parameter modes for this internal entry. */
private String parameterModes = null;
/** The parameter list of this internal entry. */
private List<Parameter> parameters = Collections.emptyList();
/** A map containing all the internal entry's attributes. */
private Map<String, String> attributes = new HashMap<>(4);
/**
* Flag indicating if this is a IN SUPER internal entry (when set to <code>true</code>).
* Gets its permanent value when internal entry is initialized by {@link SourceNameMapper}.
*/
private Boolean isSuper = null;
/**
* Flag indicating if this is a IN handle internal entry (when set to <code>true</code>).
* Gets its permanent value when internal entry is initialized by {@link SourceNameMapper}.
*/
private Boolean inHandle = null;
/**
* Flag indicating if this is a PRIVATE internal entry (when set to <code>true</code>).
* Gets its permanent value when internal entry is initialized by {@link SourceNameMapper}.
*/
private Boolean isPrivate = null;
/** The return type's extent. */
private int extent = SourceNameMapper.NO_EXTENT;
/** The qualified OO name. */
private String qualified = null;
/** The converted Java method from where this internal entry was built, using the
* {@link LegacySignature} annotation.
*/
private Method method = null;
/** The OS library name, if this is a native method. */
private String libname = null;
/** The MAP-TO option, if it applies. */
private String mapTo = null;
/**
* The external program containing this entry. It is non-null only for external programs, until the
* {@link #method} is resolved.
*/
private ExternalProgram extprog;
/** Represents null InternalEntry value */
public static final InternalEntry NULL = new InternalEntry();
/**
* Private constructor for the internal implementation.
*/
private InternalEntry()
{
pname = null;
jname = null;
type = null;
}
/**
* Basic c'tor.
*
* @param pname
* Legacy 4GL name for this internal-entry.
* @param jname
* Converted Java name for this internal-entry.
* @param type
* The type of this internal entry.
*/
protected InternalEntry(String pname, String jname, String type)
{
this(pname, jname, Type.fromString(type));
}
/**
* Basic c'tor.
*
* @param pname
* Legacy 4GL name for this internal-entry.
* @param jname
* Converted Java name for this internal-entry.
* @param type
* The type of this internal entry.
*/
protected InternalEntry(String pname, String jname, Type type)
{
this.pname = pname.intern();
this.jname = jname.intern();
this.type = type;
}
/**
* Check if the given mode is an OUTPUT mode (i.e. {@link #OUTPUT_MODE} or
* {@link #INPUT_OUTPUT_MODE}.
*
* @return <code>true</code> if the given character represents one of the output
* modes.
*/
public static boolean isOutputMode(char mode)
{
return mode == OUTPUT_MODE || mode == INPUT_OUTPUT_MODE;
}
/**
* Get the OS library name, if it applies.
*
* @return The {@link #libname}.
*/
public String getLibname()
{
return libname;
}
/**
* Get the MAP-TO option, if it applies.
*
* @return The {@link #mapTo} option.
*/
public String getMapTo()
{
return mapTo;
}
/**
* Return <code>true</code> if this internal-entry has its <code>in-super</code> attribute set
* to <code>"true"</code>.
*
* @return See above.
*/
public boolean isSuper()
{
return isSuper;
}
/**
* Return <code>true</code> if this internal-entry has its
* <code>private</code> attribute set to <code>"true"</code>.
*
* @return See above.
*/
public boolean isPrivate()
{
return isPrivate;
}
/**
* Return <code>true</code> if internal-entry is a function (its {@link #type} is set to
* {@link Type#FUNCTION}).
*
* @return See above.
*/
public boolean isFunction()
{
return type == Type.FUNCTION;
}
/**
* Return <code>true</code> if this internal-entry has its
* <code>in-handle</code> attribute set to <code>"true"</code>.
*
* @return See above.
*/
public boolean isInHandle()
{
return inHandle;
}
/**
* Obtain the original name of the procedure being executed as specified in the 4GL
* code.
*
* @return The original (unconverted) name.
*/
public String getLegacyName()
{
return pname;
}
/**
* Get the qualified OO name (if it applies).
*
* @return See above.
*/
public String getQualified()
{
return qualified;
}
/**
* Get the return's extent (if it applies).
*
* @return See above.
*/
public int getExtent()
{
return extent;
}
/**
* Get the Java {@link #method}.
*
* @return See above.
*/
public Method getMethod()
{
if (method == null && extprog != null && extprog.ooname == null)
{
SourceNameMapper.buildLegacyProgram(extprog);
// in case the Java method is not needed for this entry (is a in-handle or in-super), avoid multiple
// lookups.
extprog = null;
}
return method;
}
/**
* Get the value of the given attribute. If the attribute is null or
* is not set, return <code>null</code>.
*
* @param attr
* The attribute's name.
*
* @return See above.
*/
public String getAttribute(String attr)
{
if (attr == null)
{
return null;
}
attr = attr.toLowerCase();
return attributes.get(attr);
}
/**
* Set the value of the given attribute.
*
* @param name
* The attribute's name.
* @param value
* The attribute's value.
*/
public void putAttribute(String name, String value)
{
attributes.put(name.toLowerCase().intern(), value.intern());
}
/**
* Gets the signature of this internal-entry, as returned by the <code>get-signature</code>
* API of an persistent procedure. If this is the first access, it initializes the
* {@link #signature} field.
*
* @return The 4GL-compatible signature string.
*/
public String getSignature()
{
return signature;
}
/**
* Gets the details of the given parameter.
*
* @param idx
* 0-based parameter index.
*
* @return The parameter instance or <code>null</code> if no such parameter exists.
*/
public Parameter getParameter(int idx)
{
if (idx < 0 || idx >= parameters.size())
{
return null;
}
return parameters.get(idx);
}
/**
* Returns the number of parameters for this internal entry.
*
* @return The size of the parameter list.
*/
public int getParameterListSize()
{
return parameters.size();
}
/**
* Returns the list of parameters for this internal entry.
*
* @return The parameter list.
*/
public List<Parameter> getParameterList()
{
// this is safe because the list cannot be modified
return parameters;
}
/**
* Gets the string representation of the parameter modes for this internal-entry.
*
* @return See above.
*/
public String getParameterModes()
{
return parameterModes;
}
/**
* Gets the parameters for this internal entry.
*
* @return See above.
*/
public List<Parameter> getParameters()
{
return new ArrayList<>(parameters);
}
/**
* Set the target Java method for this internal entry to the specified one.
*
* @param mthd
* The target method.
*/
void setMethod(Method mthd)
{
this.method = mthd;
}
/**
* Set the external program for this entry.
*
* @param extprog
* The external program for this entry.
*/
void setExternalProgram(ExternalProgram extprog)
{
this.extprog = extprog;
}
/**
* Sets the list of parameters for this internal entry. Changes to the input list will
* not be reflected in the saved list.
*
* @param parameters
* The parameter list.
*/
protected void setParameterList(List<Parameter> parameters)
{
this.parameters = Collections.unmodifiableList(parameters);
}
/**
* Initialize this object's internal state. This method must be called after all attributes and
* parameters have been set, but before any public access to {@link #isSuper()} is possible.
* This is done within synchronized code in {@link SourceNameMapper}, such that the creation
* of this object and initialization of this state is atomic from a concurrency perspective.
*/
protected void initialize()
{
isSuper = "true".equalsIgnoreCase(getAttribute("in-super"));
isPrivate = "true".equalsIgnoreCase(getAttribute("private"));
inHandle = "true".equalsIgnoreCase(getAttribute("in-handle"));
String sextent = getAttribute("extent");
if (sextent != null)
{
extent = Integer.parseInt(sextent);
}
qualified = getAttribute("qualified");
libname = (type == InternalEntry.Type.DLL_ENTRY) ? getAttribute("libname") : null;
mapTo = (type == Type.FUNCTION) ? getAttribute("map-to") : null;
// must come after attributes
signature = buildSignature().intern();
if (!parameters.isEmpty())
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < parameters.size(); i++)
{
Parameter p = parameters.get(i);
String pmode = p.getMode();
String type = p.getType();
char mode = 0;
if ("BUFFER".equalsIgnoreCase(type))
{
mode = BUFFER_MODE;
}
else if ("INPUT".equalsIgnoreCase(pmode))
{
mode = INPUT_MODE;
}
else if ("INPUT-OUTPUT".equalsIgnoreCase(pmode))
{
mode = INPUT_OUTPUT_MODE;
}
else if ("OUTPUT".equalsIgnoreCase(pmode) || "RETURN".equalsIgnoreCase(pmode))
{
// the resulting string is used for validation purposes, so we must convert
// RETURN parms to OUTPUT (although since we defer validation to
// NativeInvoker.invoke(), I'm not sure this is actually needed)
mode = OUTPUT_MODE;
}
else
{
String err = "Malformed parameter mode " + pmode + " for parameter " + pname;
throw new IllegalStateException(err);
}
sb.append(mode);
}
parameterModes = sb.toString().intern();
}
}
/**
* Build the 4GL-style signature of this instance.
*
* @return The internal-entry's signature.
*/
private String buildSignature()
{
// The format is: TYPE,return type,param1,param2,param3 where:
// - TYPE is either PROCEDURE, FUNCTION, EXTERN or DLL-ENTRY
// - return type is the 4GL name of the return data type
// - param# is one of:
// INPUT varname TYPE
// OUTPUT varname TYPE or INPUT-OUTPUT varname TYPE.
// Varname is either the original 4GL variable name or "arg#", if this
// is a header definition (i.e. an IN SUPER or IN handle) which had
// its parameter set as:
// "function f1 returns int (input int) in super."
String type = this.type.toString();
String returns = getAttribute("returns");
String qualified = getAttribute("qualified");
if (qualified != null)
{
returns = qualified;
}
if (returns == null)
{
returns = "";
}
if (isSuper() || isInHandle())
{
returns = returns.toUpperCase();
}
else
{
returns = returns.toLowerCase();
}
StringBuilder sb = new StringBuilder();
if (isInHandle())
{
type = Type.EXTERN.toString();
}
sb.append(type).append(",");
sb.append(returns);
if (parameters.size() == 0)
{
sb.append(",");
}
else
{
int idx = 0;
for (Parameter p : parameters)
{
idx++;
sb.append(",");
// we can't just use the raw mode string, we must use the version that is meant
// for the signature string since RETURN is reported by the 4gl as OUTPUT
String pmode = p.getSignatureMode();
if (pmode.length() > 0)
{
sb.append(pmode).append(" ");
}
if ("BUFFER".equalsIgnoreCase(p.getType()))
{
String buffer = p.getAttribute("buffer");
String forTable = p.getAttribute("for");
sb.append("BUFFER").append(" ");
sb.append(buffer).append(" ");
sb.append(forTable);
}
else if ("TABLE".equalsIgnoreCase(p.getType()))
{
String table = p.getAttribute("table");
String append = p.getAttribute("append");
sb.append("TABLE").append(" ");
sb.append(table);
if ("true".equalsIgnoreCase(append))
{
sb.append(" ").append("APPEND");
}
}
else if ("HANDLE".equalsIgnoreCase(p.getType()) && p.getAttribute("TABLE") != null)
{
String name = p.getAttribute("pname");
sb.append("TABLE-HANDLE").append(" ");
sb.append(name);
}
else if ("DATASET".equalsIgnoreCase(p.getType()))
{
String dataset = p.getAttribute("dataset");
sb.append("DATASET").append(" ");
sb.append(dataset);
}
else if ("HANDLE".equalsIgnoreCase(p.getType()) && p.getAttribute("DATASET") != null)
{
String name = p.getAttribute("pname");
sb.append("DATASET-HANDLE").append(" ");
sb.append(name);
}
else
{
String pname = p.getLegacyName();
if (pname == null || pname.length() == 0)
{
pname = "arg" + idx;
}
sb.append(pname).append(" ");
if ("OBJECT".equalsIgnoreCase(p.getType()) || "JOBJECT".equals(p.getType()))
{
sb.append(p.getQualified().toUpperCase());
}
else
{
sb.append(p.getType());
}
// emit extent info
if (p.isExtent())
{
sb.append(" EXTENT");
if (p.isFixedExtent())
{
sb.append(" ").append(p.getExtent());
}
}
}
}
}
return sb.toString();
}
}