=== modified file 'src/com/goldencode/p2j/schema/SchemaDictionary.java'
--- old/src/com/goldencode/p2j/schema/SchemaDictionary.java	2021-04-13 00:57:06 +0000
+++ new/src/com/goldencode/p2j/schema/SchemaDictionary.java	2021-04-13 20:30:32 +0000
@@ -499,6 +499,9 @@
 **    CA   20201011          Scope.promoted changed to an identity hash set.
 **    CA   20201015          Replaced java.util.Stack with a non-synchronized custom implementation.
 **     AIL 20210309          Clean local schema dictionary instances. 
+**    GES  20210208          Added table-level signature calculation (saved as an annotation). Added
+**                           findTableInfo() to provide a set of related data about a matched record. This
+**                           saves multiple calls each having to resolve the same name node lookup.
 */
 
 /*
@@ -1765,6 +1768,9 @@
       {
          copyTableProperties(ast, table);
       }
+      
+      // calculate and save the table signature
+      calculateSignature(table);
 
       if (modelTable == null)
       {
@@ -1916,6 +1922,65 @@
    }
    
    /**
+    * Get the Progress parser token type of a table, temp-table, work-table,
+    * or buffer. If <code>name</code> represents a buffer, the type of the
+    * backing record is returned. That is, a type of <code>BUFFER</code> is
+    * never returned, only the type of the record which backs the buffer.
+    * <p>
+    * If both forceTemp and forcePersistent are <code>false</code>, then the lookup is performed
+    * with no preference for temp-table or persistent table.
+    *
+    * @param   name
+    *          Case insensitive record name; may be qualified or not. If
+    *          qualified, only the table portion of the name may be
+    *          abbreviated.
+    * @param   forceTemp
+    *          If <code>true</code>, force a temp-table lookup.
+    * @param   forcePersistent
+    *          If <code>true</code>, force a persistent table lookup.
+    *
+    * @return  One of the Progress parser token types <code>TABLE</code>,
+    *          <code>TEMP_TABLE</code>, or <code>WORK_TABLE</code>, or
+    *          <code>UNKNOWN</code> if <code>name</code> cannot be found.
+    *
+    * @throws  AmbiguousSchemaNameException
+    *          if <code>name</code> is an ambiguous identifier for the
+    *          target record.
+    */
+   public TableInfo findTableInfo(String name, boolean forceTemp, boolean forcePersistent)
+   throws AmbiguousSchemaNameException
+   {
+      // TODO: The recordType lookup used scopes.size() - 1 instead of nearestEnclosingInternal(), is this
+      //       an issue?  It seems like it should have been consistent with the other lookups, so this is
+      //       being made consistent here.
+      int l1 = computeLevel(forceTemp, forcePersistent, true, nearestEnclosingInternal());
+      int l2 = computeLevel(forceTemp, forcePersistent, false, SCHEMA_GLOBAL_SCOPE);
+
+      // Look up record name node.
+      NameNode node = findEntry(name, EntityName.TABLE, l1, l2, false);
+      
+      TableInfo info = null;
+      
+      if (node != null)
+      {
+         NameNode dbNode  = node.getParent();
+         
+         // don't just use getAst() here, we need the AST for the backing table if this is a buffer
+         // (this will work for both the simple case and the buffer case)
+         Aast   ast   = getTable(node); 
+         String sig   = ast != null ? (String) ast.getAnnotation("signature") : null;
+         String sname = getQualifiedName(node);
+         String bname = getRecordName(node, EntityName.TABLE, name);
+         String dname = (dbNode != null) ? dbNode.getName() : "";
+         int    ttype = getTable(node).getType();
+         
+         info = new TableInfo(sig, sname, bname, dname, ttype);
+      }
+      
+      return info;
+   }
+   
+   /**
     * Get an object which exposes schema-related information about a field which is referenced in
     * source code.
     * 
@@ -4908,6 +4973,74 @@
    }
    
    /**
+    * Calculate and save the signature for the table.  Each field is represented by "datatype" or
+    * "datatype[extent]" format.  The table level signature is all of the field signatures separated
+    * by "|" delimiter with the number and order of the fields matching the order in the AST.
+    *  
+    * @param    table
+    *           The table AST substree.
+    */
+   private void calculateSignature(Aast table)
+   {
+      String signature = "";
+      
+      Aast next = (Aast) table.getFirstChild();
+
+      // iterate through all fields
+      while (next != null)
+      {
+         int type = next.getType();
+         
+         // if this is a field, calculate the field's portion of the signature
+         if (type >= BEGIN_FIELDTYPES && type <= END_FIELDTYPES ||
+             type >= BEGIN_METATYPES  && type <= END_METATYPES)
+         {
+            Aast ext = next.getImmediateChild(KW_EXTENT, null);
+            
+            String extent = null;
+            
+            // read the extent value if any
+            if (ext != null)
+            {
+               if (ext.getNumImmediateChildren() == 0)
+               {
+                  String err = String.format("Indeterminate EXTENT in a temp-table definition for %s",
+                                             next.dumpTree());
+                  
+                  // indeterminate extent does not make sense for a temp-table, should not happen
+                  throw new RuntimeException(err);
+               }
+               else
+               {
+                  // the only child is a NUM_LITERAL
+                  Aast num = (Aast) ext.getFirstChild();
+                  extent = num.getText();
+               }
+            }
+            
+            // create the field portion of the signature
+            String fsig = SchemaParser.fieldSignature(type, extent);
+            
+            // add it in to the table signature
+            signature += (signature.length() == 0) ? fsig : "|" + fsig;
+         }
+
+         next = (Aast) next.getNextSibling();
+      }
+
+      // save the result
+      if (signature.isEmpty())
+      {
+         // there should always be at least 1 field
+         throw new RuntimeException(String.format("Invalid temp-table, no fields!\n%s", table.dumpTree()));
+      }
+      else
+      {
+         table.putAnnotation("signature", signature);
+      }
+   }
+   
+   /**
     * Copy TABLE properties from a source AST to a destination AST. The set
     * of properties (essentially child AST nodes) copied will be limited to
     * those that are valid in a table (filtered by token type). It is assumed

=== added file 'src/com/goldencode/p2j/schema/TableInfo.java'
--- old/src/com/goldencode/p2j/schema/TableInfo.java	1970-01-01 00:00:00 +0000
+++ new/src/com/goldencode/p2j/schema/TableInfo.java	2021-04-09 17:19:41 +0000
@@ -0,0 +1,166 @@
+/*
+** Module   : TableInfo.java
+** Abstract : Schema information about a database field.
+**
+** Copyright (c) 2021, Golden Code Development Corporation.
+**
+** -#- -I- --Date-- --------------------------------Description----------------------------------
+** 001 GES 20210224 Created initial version.
+*/
+
+/*
+** 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.schema;
+
+/**
+ * A read-only bean which exposes schema-related information associated with a database table
+ * which is referenced in source code.
+ */
+public class TableInfo
+{
+   /** Table signature associated with the table. */
+   private final String signature;
+   
+   /** Fully qualified schema name of the table. */
+   private final String qualified;
+   
+   /** Table's buffer name as indicated in source. */
+   private final String buffer;
+   
+   /** Table's database logical name or alias. */
+   private final String database;
+   
+   /** Table's token type. */
+   private final int recordType;
+   
+   /**
+    * Constructor.
+    * 
+    * @param   signature
+    *          Structural signature for the table.
+    * @param   qualified
+    *          Fully qualified schema name of the table.
+    * @param   buffer
+    *          Name of the buffer indicated by the table reference in source code.
+    * @param   database
+    *          Name of the database which contains the table, as specified by the table reference
+    *          in source code, or looked up if the source reference was unqualified.
+    * @param   recordType
+    *          Token type of the table backing the buffer indicated by the table reference in
+    *          source code.
+    */
+   TableInfo(String signature, String qualified, String buffer, String database, int recordType)
+   {
+      this.signature  = signature;
+      this.qualified  = qualified;
+      this.buffer     = buffer;
+      this.database   = database;
+      this.recordType = recordType;
+   }
+   
+   /**
+    * Get the signature of the table in the schema.
+    * 
+    * @return   Table signature.
+    */
+   public String getSignature()
+   {
+      return signature;
+   }
+   
+   /**
+    * Get the fully qualified schema name of the field.
+    * 
+    * @return  Qualified field name.
+    */
+   public String getQualified()
+   {
+      return qualified;
+   }
+   
+   /**
+    * Get the buffer name associated with the field.
+    * 
+    * @return  Buffer name.
+    */
+   public String getBuffer()
+   {
+      return buffer;
+   }
+   
+   /**
+    * Get the logical database name or alias, if any, associated with the field.
+    * 
+    * @return  The logical name of the database associated with the buffer associated with the
+    *          field. An empty string is returned if the containing table is a temp- or
+    *          work-table.
+    */
+   public String getDatabase()
+   {
+      return database;
+   }
+   
+   /**
+    * Get the Progress parser token type of a table, temp-table, work-table, or buffer which
+    * contains the field. If the record containing the field is a buffer, the type of the backing
+    * record is returned. That is, a type of {@code BUFFER} is never returned, only the type of
+    * the record which backs the buffer.
+    * 
+    * @return  Record token type.
+    */
+   public int getRecordType()
+   {
+      return recordType;
+   }
+}
\ No newline at end of file

=== modified file 'src/com/goldencode/p2j/schema/schema.g'
--- old/src/com/goldencode/p2j/schema/schema.g	2020-09-30 16:32:34 +0000
+++ new/src/com/goldencode/p2j/schema/schema.g	2021-02-08 21:46:29 +0000
@@ -2,7 +2,7 @@
 ** Module   : schema.g
 ** Abstract : Progress database schema dump file (.df) parser
 **
-** Copyright (c) 2004-2019, Golden Code Development Corporation.
+** Copyright (c) 2004-2021, Golden Code Development Corporation.
 **
 ** -#- -I- --Date-- --JPRM-- ----------------------------Description-----------------------------
 ** 001 ECF 20041130   @18769 First version implementing a complete lexer,
@@ -99,6 +99,7 @@
 ** 037 OM  20200530          Fix for datetime[tz].
 ** 038 CA  20200930          Use factory.setASTNodeClass(Class) instead of setASTNodeClass(String), to avoid
 **                           loadClass call.
+**     GES 20210208          Added table-level signature calculation and it is saved as an annotation.
 */
 
 /*
@@ -161,7 +162,7 @@
 **            SchemaParserTokenTypes.java
 ** Abstract : Progress database schema dump file (.df) parser
 **
-** Copyright (c) 2004-2016, Golden Code Development Corporation.
+** Copyright (c) 2004-2021, Golden Code Development Corporation.
 **
 ** -#- -I- --Date-- --JPRM-- ----------------------------Description-----------------------------
 ** 001 ECF 20041130   @18769 WARNING, THIS IS A GENERATED FILE!!! DO NOT EDIT THIS FILE. The
@@ -241,6 +242,99 @@
    }
    
    /**
+    * Calculate the signature string for a field.
+    *
+    * @param    type
+    *           The field's data type.
+    * @param    extent
+    *           The extent size (as a num literal) or {@code null} if the field is scalar.
+    *
+    * @return   The signature in "datatype" or "datatype[extent]" format.
+    */
+   public static String fieldSignature(int type, String extent)
+   {
+      String code = null;
+      
+      switch (type)
+      {
+         case FIELD_BLOB:
+            code = "blob";
+            break;
+         case FIELD_CHAR:
+            code = "char";
+            break;
+         case FIELD_CLOB:
+            code = "clob";
+            break;
+         case FIELD_COM_HANDLE:
+            code = "comh";
+            break;
+         case FIELD_DATE:
+            code = "date";
+            break;
+         case FIELD_DATETIME:
+            code = "dt";
+            break;
+         case FIELD_DATETIME_TZ:
+            code = "dttz";
+            break;
+         case FIELD_DEC:
+            code = "dec";
+            break;
+         case FIELD_INT:
+            code = "int";
+            break;
+         case FIELD_INT64:
+            code = "i64";
+            break;
+         case FIELD_LOGICAL:
+            code = "log";
+            break;
+         case FIELD_RECID:
+            code = "rec";
+            break;
+         case FIELD_ROWID:
+            code = "row";
+            break;
+         case FIELD_RAW:
+            code = "raw";
+            break;
+         case FIELD_HANDLE:
+            code = "hndl";
+            break;
+         case FIELD_BIGINT:
+            code = "bigi";
+            break;
+         case FIELD_BYTE:
+            code = "byte";
+            break;
+         case FIELD_DOUBLE:
+            code = "dbl";
+            break;
+         case FIELD_FIXCHAR:
+            code = "fixc";
+            break;
+         case FIELD_FLOAT:
+            code = "flt";
+            break;
+         case FIELD_SHORT:
+            code = "shrt";
+            break;
+         case FIELD_TIMESTAMP:
+            code = "tmst";
+            break;
+         case FIELD_TIME:
+            code = "time";
+            break;
+         default:
+            code = "unkw";
+            break;
+      }
+      
+      return (extent == null) ? code : code + "[" + extent + "]";
+   }
+   
+   /**
     * Configure the hidden filter to ensure that token types that are not
     * normally recognized by the parser, are hidden (but still accessible)
     * using the filter.
@@ -700,7 +794,11 @@
  */
 table
    :
-      { astFactory.makeASTRoot(currentAST, #[TABLE]); }
+      {
+         astFactory.makeASTRoot(currentAST, #[TABLE]);
+         String signature = "";
+         String fsig = null;
+      }
       (
          KW_ADD! tab:KW_TABLE! s:STRING! (y:KW_TYPE! d:SYMBOL!)?
          {
@@ -723,13 +821,17 @@
          (
             options { generateAmbigWarnings = false; }
             :
-              field
+              fsig=field
+              {
+                 signature += (signature.length() == 0) ? fsig : "|" + fsig;
+              }
             | index
          )*
       )
       {
          currentTable = null;
          forceLineColumnNums(#tab, ##);
+         ##.putAnnotation("signature", signature);
       }
    ;
    
@@ -804,15 +906,18 @@
  * definition would always be a child of the most recently parsed table
  * definition.
  *
- * @throws  SemanticException
- *          if this field definition reports a different parent table name
- *          from the table definition most recently parsed.
+ * @return   The signature for this field in "datatype" or "datatype[extent]" format.
+ *
+ * @throws   SemanticException
+ *           if this field definition reports a different parent table name
+ *           from the table definition most recently parsed.
  */
-field
+field returns [String sig = null]
    :
       {
          int    ftype;
          String fmt = null;
+         String ext = null;
       }
       (
          KW_ADD! f:KW_FIELD^ s:STRING!
@@ -850,7 +955,7 @@
             | field_trigger
             | help
             | help_sa
-            | extent
+            | ext=extent
             | decimals
             | length
             | viewAs
@@ -876,6 +981,9 @@
             | unknownOption!
          )*
       )
+      {
+         sig = fieldSignature(ftype, ext);
+      }
    ;
    
 /**
@@ -1736,10 +1844,12 @@
  * <p>
  * The resulting sub-tree is the <code>KW_EXTENT</code> with a child of
  * <code>NUM_LITERAL</code>.
+ *
+ * @return   The size of the extent.
  */
-extent
+extent returns [String num = null]
    :
-      KW_EXTENT^ NUM_LITERAL
+      KW_EXTENT^ n:NUM_LITERAL { num = #n.getText(); }
    ;
    
 /**

=== modified file 'src/com/goldencode/p2j/uast/ClassDefinition.java'
--- old/src/com/goldencode/p2j/uast/ClassDefinition.java	2021-03-23 11:42:56 +0000
+++ new/src/com/goldencode/p2j/uast/ClassDefinition.java	2021-04-15 14:18:30 +0000
@@ -1,5 +1,4 @@
 /*
-** Module   : ClassDefinition.java
 ** Abstract : defines the 4GL API for a class or interface definition
 **
 ** Copyright (c) 2007-2021, Golden Code Development Corporation.
@@ -100,6 +99,8 @@
 **     CA  20210305          For builtin OO method calls, save at the call the typelist for the target method
 **                           parameters.
 **     CA  20210318          Fixed conversion of extent() function/statement with builtin OO properties.
+**     GES 20210311          Rewrote exact and fuzzy method matching to support all data types and
+**                           overloading rules.  Fixed many latent problems.
 */
 
 /*
@@ -160,6 +161,7 @@
 import java.lang.reflect.*;
 import java.util.*;
 import java.util.function.*;
+import java.util.stream.*;
 import com.goldencode.ast.*;
 import com.goldencode.p2j.convert.*;
 import com.goldencode.p2j.convert.db.*;
@@ -189,11 +191,6 @@
 public class ClassDefinition
 implements ProgressParserTokenTypes
 {
-   /**
-    * Flag to indicate whether tracing is ON.
-    */
-   public static final boolean nowrapObjects = System.getProperty("nowrap.objects") != null;
-
    /** Data store identifier. */
    protected static enum DataStoreType { VAR, METHOD, TABLE, QRY, DATASET, DATASRC };
 
@@ -650,7 +647,7 @@
       this.interfaces = interfaces.isEmpty() ? null : Collections.unmodifiableSet(interfaces);
       this.parents = parents.isEmpty() 
                         ? null 
-                        : (ClassDefinition[]) parents.toArray(new ClassDefinition[0]);
+                        : parents.toArray(new ClassDefinition[0]);
    }
    
    /**
@@ -1413,12 +1410,11 @@
             {
                // this can happen if we are referencing this class via a USING from an USING defined
                // in this file... in this case, inherit the tempidx, but show an warning.
-               MemberData dat = defs.get(new SignatureKey(sig));
                
                // GES_TODO: re-enable this
                // System.out.printf("\nERROR: duplicate method definition!\n");
                // System.out.printf("EXISTING %s\n", dat.toString());
-               // System.out.printf("DUPLICATE %s\n at %s", mdat.toString(), node.dumpTree());
+               // System.out.printf("DUPLICATE %s\n at %s", prevMdat.toString(), node.dumpTree());
                
                // return;
             }
@@ -1461,17 +1457,17 @@
       if (node != null)
       {
          node.putAnnotation("name", name);
-         node.putAnnotation("return_type", new Long(type));
+         node.putAnnotation("return_type", (long) type);
          node.putAnnotation("signature", renderSignature(name, sig)); 
-         node.putAnnotation("access-mode", new Long(access));
-         node.putAnnotation("static", new Boolean(isStatic));
+         node.putAnnotation("access-mode", (long) access);
+         node.putAnnotation("static", isStatic);
          node.putAnnotation("oo-data-store", DataStoreType.METHOD.toString());
-         node.putAnnotation("tempidx", new Long(mTempIdx));
+         node.putAnnotation("tempidx", (long) mTempIdx);
          node.putAnnotation("tempidx-file", this.filename);
          
          if (extent != 0)
          {
-            node.putAnnotation("extent", new Long(extent));
+            node.putAnnotation("extent", (long) extent);
          }
          
          if (ret != null && ret.isAnnotation("qualified"))
@@ -1570,7 +1566,8 @@
       if (node != null)
       {
          // go ahead and annotate the chp_wrapper right now too
-         MemberData mdat = lookupMethodWorker(mname, signature, access, isStatic, internal, node);
+         MethodSearchResult found = lookupMethodWorker(mname, signature, access, isStatic, internal, node);
+         MemberData         mdat    = (found == null) ?  null : found.data;
          
          if (mdat != null)
          {
@@ -1593,14 +1590,14 @@
             }
             
             // the access mode won't necessarily be the same as that passed in
-            node.putAnnotation("access-mode", new Long(mdat.access));
-            node.putAnnotation("static", new Boolean(mdat.isStatic));
+            node.putAnnotation("access-mode", (long) mdat.access);
+            node.putAnnotation("static", mdat.isStatic);
             if (mdat.modes != null && !mdat.modes.isEmpty())
             {
                node.putAnnotation("param_modes", mdat.modes);
             }
             
-            node.putAnnotation("tempidx", new Long(mdat.tempIdx));
+            node.putAnnotation("tempidx", (long) mdat.tempIdx);
             node.putAnnotation("tempidx-file", mdat.container.filename);
             
             node.putAnnotation("found-in-cls", mdat.container.name);
@@ -1612,7 +1609,7 @@
             
             if (mdat.extent != 0)
             {
-               node.putAnnotation("extent", new Long(mdat.extent));
+               node.putAnnotation("extent", (long) mdat.extent);
             }
             
             if (mdat.container.builtin && !mdat.container.dotnet)
@@ -1625,7 +1622,7 @@
                }
             }
             
-            annotateCallSignature(node, signature, mdat.signature);
+            annotateCallSignature(node, signature, mdat.signature, found.getOverrides());
             node.putAnnotation("signature", renderSignature(mdat.name, mdat.signature));
             if (isBuiltIn())
             {
@@ -1658,19 +1655,9 @@
          }
          else
          {
-            // check if there are POLY arguments (BaseDataType signature); if so, mark this call
-            // with 'dynamic', so conversion rules can emit a dynamic call for it
-            boolean dynamic = false;
-            for (ParameterKey key : signature)
-            {
-               if ("BaseDataType".equals(key.type))
-               {
-                  dynamic = true;
-                  break;
-               }
-            }
-            
-            if (dynamic)
+            // dynamic cases will have a non-null result; this may be for table-handle parms, dataset-handle
+            // parms or POLY parms
+            if (found != null && found.isDynamic())
             {
                node.putAnnotation("dynamic", true);
                if (isStatic)
@@ -2426,15 +2413,11 @@
             {
                if (vdata.var.isProp())
                {
-                  String datatype = SymbolResolver.translateType(vdata.type, null);
-                  if ("object".equals(datatype))
-                  {
-                     datatype = String.format("object <? extends %s>", vdata.qname);
-                  }
-                  int extent = vdata.var.getExtent();
-                  String ext = extent == 0 
-                                 ? "" 
-                                 : "[" + (extent > 0 ? Integer.toString(extent) : "") +"]";
+                  String datatype = SymbolResolver.translateType(vdata.type, null, vdata.qname);
+                  
+                  
+                  int    extent = vdata.var.getExtent();
+                  String ext = (extent == 0) ? "" : "[" + (extent > 0 ? Integer.toString(extent) : "") +"]";
 
                   // add property methods
                   String[] prefixes = extent != 0 ? extPrefixes : propPrefixes;
@@ -2684,8 +2667,15 @@
     *           The OO_METH_* node whose parameters will be annotated.
     * @param    callSig
     *           The array of parameter types that is the method definition signature.
+    * @param    defSig
+    *           The array of parameter types that is the method definition signature.
+    * @param    overrides
+    *           Type overrides found by any fuzzy matching for this method.
     */
-   private void annotateCallSignature(Aast call, ParameterKey[] callSig, ParameterKey[] defSig)
+   private void annotateCallSignature(Aast           call,
+                                      ParameterKey[] callSig,
+                                      ParameterKey[] defSig,
+                                      String[]       overrides)
    {
       int idx = 0;
       
@@ -2884,14 +2874,13 @@
                      {
                         if (parm.getType() == PARAMETER)
                         {
-                           parm.putAnnotation("wrap_parameter", new Boolean(true));
+                           parm.putAnnotation("wrap_parameter", true);
                         }
                      }
-                     else if ((!nowrapObjects || !classname.startsWith("object<")) && 
+                     else if (!classname.startsWith("object<") && 
                               !classname.startsWith("jobject<"))
                      {
-                        // TODO: CA: what's the reas for nowrapObjects?
-                        parm.putAnnotation("wrap_parameter", Boolean.TRUE);
+                        parm.putAnnotation("wrap_parameter", true);
                      }
                      
                      parm.putAnnotation("classname", classname);
@@ -2977,7 +2966,6 @@
             {
                Iterator<MemberData> iter = defs.values().iterator();
                
-               outer:
                while (iter.hasNext())
                {
                   MemberData dat = checkAccessRights(iter.next(), access);
@@ -3047,47 +3035,56 @@
     * @return   Member data or <code>null</code> if no such variable or data
     *           member exists. 
     */
-   private MemberData lookupMethodWorker(String         name,
-                                         ParameterKey[] sig,
-                                         int            access,
-                                         boolean        isStatic,
-                                         boolean        internal,
-                                         Aast           node)
+   private MethodSearchResult lookupMethodWorker(String         name,
+                                                 ParameterKey[] sig,
+                                                 int            access,
+                                                 boolean        isStatic,
+                                                 boolean        internal,
+                                                 Aast           node)
    {
-      MemberData mdat = null;
+      MethodSearchResult result = null;
       
       synchronized (lock)
       {
-         mdat = exactMethodLookup(name, sig, access, isStatic, internal);
+         result = exactMethodLookup(name, sig, access, isStatic, internal);
 
-         if (mdat == null && !isStatic && internal)
+         if (result == null && !isStatic && internal)
          {
             // can't find an instance member, check for static
-            mdat = exactMethodLookup(name, sig, access, true, internal);
+            result = exactMethodLookup(name, sig, access, true, internal);
          }
          
-         if (mdat == null)
+         if (result == null)
          {
-            mdat = fuzzyMethodLookup(name, sig, access, isStatic, internal, node);
+            result = fuzzyMethodLookup(name, sig, access, isStatic, internal, node);
    
-            if (mdat == null && !isStatic && internal)
+            if (result == null && !isStatic && internal)
             {
                // can't find an instance member, check for static
-               mdat = fuzzyMethodLookup(name, sig, access, true, internal, node);
+               result = fuzzyMethodLookup(name, sig, access, true, internal, node);
             }
          }
       }
       
-      return mdat;
+      return result;
    }
    
    /**
-    * Find the named method based on an exact signature match. This method will search up the
-    * parent hierarchy (recursively) if no exact match is found in the current class.
+    * Find the named method based on an exact signature match. This method searches all methods in the
+    * parent hierarchy not just in the current class.  This search the hierarchy approach is needed so
+    * that some special exceptions can be honored:
+    * <p>
+    * <ul>
+    *    <li>If the caller does not specify a mode and there is an otherwise exact match, that method is
+    *        selected.  Buffers have no mode processing so this case is ignored for them.</li>
+    *    <li>A caller's table-handle or dataset-handle parameter which has an exact match is disallowed
+    *        if there are any alternatives with a table/dataset at that parameter AND that parameter type
+    *        is what determines that the signatures are different.</li>
+    * </ul>
     *
     * @param    name
     *           Resource name.
-    * @param    sig
+    * @param    caller
     *           Method call signature.
     * @param    access
     *           Access mode (<code>KW_PUBLIC</code>, <code>KW_PROTECTD</code> or
@@ -3100,56 +3097,220 @@
     *
     * @return   Resource found or <code>null</code> if no match exists.
     */
-   private MemberData exactMethodLookup(String         name,
-                                        ParameterKey[] sig,
-                                        int            access,
-                                        boolean        isStatic,
-                                        boolean        internal)
-   {
-      MemberData mdat = null;
-      
-      synchronized (lock)
-      {
-         Map<String, Map<SignatureKey, MemberData>> map = isStatic ? smethods : imethods;
-         
-         if (map != null)
-         {
-            Map<SignatureKey, MemberData> defs = map.get(name.toLowerCase());
-            
-            if (defs != null)
-            {
-               // exact signature match is based on the key lookup
-               mdat = checkAccessRights(defs.get(new SignatureKey(sig)), access);
-            }
-         }
-         
-         // ignore if the static/instance does not match
-         if (mdat != null && !((!isStatic && (!mdat.isStatic || internal)) ||
-                               (isStatic && mdat.isStatic)))
-         {
-            // clear our result
-            mdat = null;
-         }
-         
-         // only look up the parent hierarchy if we haven't got a match yet; static lookups only
-         // work for internal usage, non-static lookups always work
-         if (mdat == null && parents != null && (!isStatic || internal))
-         {
-            // change access mode since we aren't allowed to see private stuff in parent
-            int _access = (access == KW_PRIVATE) ? KW_PROTECTD : access;
-            
-            for (ClassDefinition parent : parents)
-            {
-               // recurse up inheritence hierarchy
-               mdat = parent.exactMethodLookup(name, sig, _access, isStatic, internal);
-               
-               if (mdat != null)
-                  break;
-            }
-         }
-      }
-      
-      return mdat;
+   private MethodSearchResult exactMethodLookup(final String         name,
+                                                final ParameterKey[] caller,
+                                                final int            access,
+                                                final boolean        isStatic,
+                                                final boolean        internal)
+   {
+      List<MatchMetrics> list = candidates(name, caller.length, access, isStatic, internal, false, null);
+      
+      // quick out if there are no possible matches
+      if (list.isEmpty())
+      {
+         return null;
+      }
+
+      final boolean[] ignore = new boolean[caller.length];
+      
+      Predicate<MatchMetrics> exactMatch = (mem) -> 
+      {
+         for (int i = 0; i < caller.length; i++)
+         {
+            if (!ignore[i] && !caller[i].equals(mem.data.signature[i]))
+            {
+               return false;
+            }
+         }
+          
+         return true;
+      };
+       
+      List<MatchMetrics> exact = list.stream().filter(exactMatch).collect(Collectors.toList());
+
+      Predicate<MatchMetrics> flexModeMatch = (mem) -> 
+      {
+         for (int i = 0; i < caller.length; i++)
+         {
+            if (!ignore[i])
+            {
+               if (caller[i].mode != null)
+               {
+                  if (!caller[i].equals(mem.data.signature[i]))
+                  {
+                     return false;
+                  }
+               }
+               else
+               {
+                  if (!caller[i].typeEquivalent(mem.data.signature[i]))
+                  {
+                     return false;
+                  }
+               }
+            }
+         }
+         
+         return true;
+      };
+      
+      MethodSearchResult result = null;
+      boolean       flex   = false;
+      
+      if (exact.isEmpty())
+      {
+         List<MatchMetrics> flexMode = list.stream().filter(flexModeMatch).collect(Collectors.toList());
+         
+         if (flexMode.size() == 1)
+         {
+            result = new MethodSearchResult(flexMode.get(0).data, null);
+            flex   = true;
+         }
+      }
+      else
+      {
+         if (exact.size() == 1)
+         {
+            result = new MethodSearchResult(exact.get(0).data, null);
+         }
+      }
+      
+      if (result != null)
+      {
+         // table-handle/dataset-handle matches are only valid when the only difference in signature is
+         // between a table/dataset and table-handle/dataset-handle
+
+         Iterator<MatchMetrics> iter = list.iterator();
+         
+         // calculate which parameters are both table-handles/dataset-handles and have conflicting
+         // signature (another signature that has a table/dataset in the same parameter position)
+         while (iter.hasNext())
+         {
+            MatchMetrics m = iter.next();
+            
+            for (int i = 0; i < caller.length; i++)
+            {
+               // quick iteration if we already know this parameter should be ignored 
+               if (ignore[i])
+                  continue;
+               
+               String type = m.data.signature[i].type;
+
+               if ("TABLE-HANDLE".equals(caller[i].type))
+               {
+                  if (type != null && type.startsWith("TEMP-TABLE"))
+                  {
+                     ignore[i] = true;
+                  }
+               }
+               else if ("DATASET-HANDLE".equals(caller[i].type))
+               {
+                  if (type != null && type.startsWith("DATA-SET "))
+                  {
+                     ignore[i] = true;
+                  }
+               }
+            }
+         }
+
+         // ignoring the conflicting parameters, can we still calculate find a unique match?
+         List<MatchMetrics> ignoreTH = list.stream()
+                                            .filter(flex ? flexModeMatch : exactMatch)
+                                            .collect(Collectors.toList());
+         if (ignoreTH.size() != 1)
+         {
+            result = null;
+         }
+      }
+      
+      return result;
+   }
+   
+   /**
+    * Remove any candidates from the list which do not match the caller's extent.  The following are
+    * possible matches:
+    * <p>
+    * <ul>
+    *    <li> Neither the caller nor the parameter definition have an extent specification.
+    *    <li> Both the caller and the parameter definition have an indeterminate extent.
+    *    <li> The caller has a fixed extent value and the parameter definition has the same fixed extent
+    *         value.
+    *    <li> The caller has a fixed extent value and the parameter definition has an indeterminate
+    *         extent.  This is a fuzzy match (all the above criteria are the same as exact matches). 
+    *
+    * @param    list
+    *           The list of candidates.
+    * @param    callerExtVal
+    *           Caller's extent value (-1 means indeterminate, 0 is scalar and any positive value is
+    *           the fixed extent).
+    * @param    idx
+    *           Parameter number being processed.
+    */
+   private void processExtentMatches(List<MatchMetrics> list, int callerExtVal, int idx)
+   {
+      Predicate<MatchMetrics> test = null;
+      Consumer<MatchMetrics>  mark = (m) -> m.extExact++;
+      
+      // no extent in caller
+      if (callerExtVal == 0)
+      {
+         // no extent in the parameter definition
+         test = (mem) -> mem.data.signature[idx].type.indexOf("[") == -1;
+         
+         // no marking needed here
+         mark = (m) -> {};
+      }
+      // indeterminate extent in caller
+      else if (callerExtVal == -1)
+      {
+         // indeterminate extent in the parameter definition
+         test = (mem) -> mem.data.signature[idx].type.indexOf("[") != -1 && 
+                         getExtent(mem.data.signature[idx].type) == -1;
+      }
+      // fixed extent in caller
+      else
+      {
+         test = (mem) -> 
+         {
+            int extIdx = mem.data.signature[idx].type.indexOf("[");
+            
+            if (extIdx != -1)
+            {
+               // the parameter definition is an extent
+               int parmExtVal = getExtent(mem.data.signature[idx].type);
+               
+               // fixed extents match exactly or the parm is indeterminate
+               return callerExtVal == parmExtVal || parmExtVal == -1;
+            }
+            else
+            {
+               // the parameter definition is NOT an extent
+               return false;
+            }
+         };
+
+         // this will only be used for where we already know extIdx != -1 above which means it is an
+         // extent at the parameter definition (it may be either fixed or indeterminate) 
+         mark = (mem) -> 
+         {
+            // the parameter definition is an extent
+            int parmExtVal = getExtent(mem.data.signature[idx].type);
+            
+            if (callerExtVal == parmExtVal)
+            {
+               // both are fixed extents match exactly
+               mem.extExact++;
+            }
+            else if (parmExtVal == -1) 
+            {
+               // or the parm is indeterminate
+               mem.extCvt++;
+            }
+         };
+      }
+      
+      list.removeIf(test.negate());
+      list.stream().forEach(mark);
    }
    
    /**
@@ -3206,6 +3367,87 @@
    }
    
    /**
+    * Check if there is only 1 possible match for the given predicate, if so then remove all candidates
+    * from the list except for that one possible match.
+    * 
+    * @param    list
+    *           The list to edit.
+    * @param    test
+    *           The predicate to test.
+    * @param    idx
+    *           The parameter index for error messages.
+    * @param    callerType
+    *           The caller type for error messages.
+    * @param    error
+    *           A location to store an error message for the caller.
+    */
+   private boolean allowIfOnlyOne(List<MatchMetrics>      list, 
+                                  Predicate<MatchMetrics> test,
+                                  int                      idx, 
+                                  String                   callerType,
+                                  String[]                 error)
+   {
+      boolean result = false;
+      
+      List<MatchMetrics> matches = list.stream().filter(test).collect(Collectors.toList());
+
+      int num = matches.size();
+
+      if (num == 1)
+      {
+         result = true;
+         matches.stream().forEach((m) -> m.cvt++);
+         list.removeIf(test.negate());
+      }
+      else if (num > 1)
+      {
+         error[0] = String.format("Ambiguous matches (%d) for parameter %d of type %s.",
+                               num,
+                               idx, 
+                               callerType);
+      }
+      else
+      {
+         error[0] = String.format("No possible matches for parameter %d of type %s.", idx, callerType);
+      }
+      
+      if (!result)
+      {
+         list.clear();
+      }
+      
+      return result;
+   }
+   
+   /**
+    * Remove any non-exact matches from the given list.
+    * 
+    * @param    list
+    *           The list to edit.
+    * @param    exactMatch
+    *           The test for an exact match.
+    *           
+    * @return   {@code true} if edits were made.
+    */
+   private boolean processExactMatches(List<MatchMetrics> list, Predicate<MatchMetrics> exactMatch)
+   {
+      // calculate the exact TYPE matches
+      List<MatchMetrics> exacts = list.stream().filter(exactMatch).collect(Collectors.toList());
+      
+      if (!exacts.isEmpty())
+      {
+         exacts.stream().forEach((m) -> m.exact++);
+         
+         // there is at least 1 exact match, remove everything else
+         list.removeIf(exactMatch.negate());
+         
+         return true;
+      }
+
+      return false;
+   }
+   
+   /**
     * Find the named method based on a fuzzy signature match. This method will search up the
     * parent hierarchy (recursively) if no fuzzy match is found in the current class.
     *
@@ -3226,370 +3468,468 @@
     *
     * @return   Resource found or <code>null</code> if no match exists.
     */
-   private MemberData fuzzyMethodLookup(String         name,
-                                        ParameterKey[] caller,
-                                        int            access,
-                                        boolean        isStatic,
-                                        boolean        internal,
-                                        Aast           node)
+   private MethodSearchResult fuzzyMethodLookup(String         name,
+                                                ParameterKey[] caller,
+                                                int            access,
+                                                boolean        isStatic,
+                                                boolean        internal,
+                                                Aast           node)
    {
-      MemberData mdat = null;
-      
-      boolean polyParams = false;
-      for (ParameterKey p : caller)
-      {
-         if ("BaseDataType".equals(p.type))
-         {
-            polyParams = true;
-            break;
-         }
-      }
-      
       boolean first = isSetParam(name);
       
-      if ("Progress.Json.ObjectModel.JsonObject".equalsIgnoreCase(this.name) &&
-          "write".equalsIgnoreCase(name) &&
-          caller.length == 2 &&
-          caller[0].type.equals("longchar") &&
-          caller[1].type.equals("logical"))
+      List<MatchMetrics> list = candidates(name, caller.length, access, isStatic, internal, first, null);
+      
+      // GES_TODO: remove
+      // boolean debug = "calc3".equals(name) && caller.length == 1 && caller[0].type.equals("object<? extends oo.basic.compleximplements>");;
+      boolean debug = false;
+      
+      if (debug)
       {
-         int bogus = 14;
+         System.out.println("\n\n--------------------fuzzyMethodLookup START--------------------\n");
+         
+         System.out.printf("\n   CALLER %s\n   PARAMETERS: %d\n", node.dumpTree(true), caller.length);
+         
+         for (int i = 0; i < caller.length; i++)
+         {
+            System.out.printf("      %s\n", caller[i]);
+         }
+         
+         for (MatchMetrics m : list)
+         {
+            System.out.printf("DEBUG: %s\n", m.data);
+         }
       }
-      
-      List<MemberData> list = candidates(name, caller.length, access, isStatic, internal, first, null);
             
       // quick out if there are no possible matches
       if (list.isEmpty())
       {
+         if (debug)
+            System.out.println("--------------------fuzzyMethodLookup QUICK OUT--------------------");
+         
          return null;
       }
       
       // special case for ParameterList.setParameter()
       if (first && list.size() == 1)
       {
-         return list.get(0);
+         return new MethodSearchResult(list.get(0).data, null);
       }
-      
-      // the fuzzy lookup is done in phases
-      // 1. collect all matches, regardless of parameter modes
-      // 2. if only one match found, use that
-      // 3. if multiple matches found:
-      // - if one exact match by exact parameter type, use that
-      // - if multiple matches, get by parameter modes
-      //   > if caller hasn't specified a mode for an argument, use wildcard
-      //   > only one match should be found
-      //   > if multiple matches, show warning
-      
-      List<MemberData> matches = new ArrayList<>();
-      List<MemberData> exactMatches = new ArrayList<>();
-      
-      // TODOS:
-      // - constructors
-      // - handle dynamic invocation marking here
-      // - tables/table handles
-      // - datasets/dataset handles
-      // - object fuzziness
-      // - does primitive type fuzziness have less priority than primitive type exact matches?
+
+      String[] error = new String[1];
+      
+      boolean polyArgs = false;
+      
+      // TODO: constructors
+
+      // PHASE 1: cut down the list of all matches to include only those which are potentially valid
+      
+      if (debug)
+         System.out.println("\n\n---PHASE 1 START---\n");
 
       // process each parameter from left to right; the processing will vary by the parameter type; some
-      // types (e.g. objects) have multi-phase checks and other types are a simple comparison; at each
+      // types (e.g. objects) have multi-step checks and other types are a simple comparison; at each
       // step of the way the list of possible candidates will be reduced until there are only candidates
-      // left which match all criteria; at that point if there are more than one then we either have a 
-      // dynamic invocation scenario or there is some ambiguity (which should not happen if the code
-      // compiles in the 4GL)
+      // left which COULD match all criteria; at that point if there are more than one then we have an
+      // additional disambiguation step, a dynamic invocation scenario or there is some ambiguity (which
+      // should not happen if the code compiles in the 4GL)
       
-      // OUTLINE FOR NEXT CHANGES
-      /* 
-      caller:
+      // process the caller's parameters, left to right; for each caller parameter we examine the matching
+      // parameter in the list of candidates, removing any candidates in the list that cannot match
       for (int i = 0; i < caller.length; i++)
       {
+         final int idx = i;
+         
+         final Integer callerMode = caller[i].mode;
+         
          // if the caller is passing Java types, then we match them as if they were 4GL types so the get
          // converted here; if they are already 4GL types then no change will happen
-         String callerType = fromJava(caller[i].type);
-         
-         // wildcards: unknown value or BDT (POLY cases) match anything (no cases can be excluded)
-         if (callerType == null                     ||
-             callerType.equals("BaseDataType")      ||
-             callerType.equals("unknown"))
-         {
-            continue caller;
-         }
-         
-         // primitive types
-         
-         // primitive types widening/narrowing
-         
-         // tables/table handles
-         
-         // handles passed to table
-         
-         // datasets/dataset handles
-         
-         // handles passed to datasets
-         
-         // buffers
-         
-         // extents
-         
-         // object direct match
-         
-         // object fuzziness
-         
-         // parameter modes
-      }
-      */
-      
-      Iterator<MemberData> iter = list.iterator();
-      
-      outer:
-      while (iter.hasNext())
-      {
-         MemberData dat = iter.next();
-         
-         ParameterKey[] candidate = dat.signature;
-         
-         boolean exact = true;
-         
-         inner:
+         final String  callerType = fromJava(caller[i].type);
+         
+         // these are all specific to the current parameter
+         Predicate<MatchMetrics> exactMatch = (m) -> callerType.equals(fromJava(m.data.signature[idx].type)); 
+         Predicate<MatchMetrics> isTable    = (m) -> m.data.signature[idx].type.startsWith("TEMP-TABLE");
+         Predicate<MatchMetrics> isTH       = (m) -> "TABLE-HANDLE".equals(m.data.signature[idx].type);
+         Predicate<MatchMetrics> isDataset  = (m) -> m.data.signature[idx].type.startsWith("DATASET <");
+         Predicate<MatchMetrics> isDH       = (m) -> "DATASET-HANDLE".equals(m.data.signature[idx].type);
+         Predicate<MatchMetrics> modeMatch  = (m) -> callerMode.equals(m.data.signature[idx].mode);
+
+         // exclude any candidates which cannot ever match due to a caller MODE constraint; a null for the
+         // caller's mode is a wildcard that can match any mode in the parameter definition; this means that
+         // when the caller's mode is null, no matches can be excluded
+         if (callerMode != null)
+         {
+            list.removeIf(modeMatch.negate());
+            
+            if (debug)
+               System.out.printf("PARM %d: mode %d\n", idx, list.size());
+         }
+
+         // TYPE-specific processing
+         
+         boolean poly = (callerType == null) || callerType.equals("BaseDataType");
+         
+         // TYPE wildcards: unknown value or BDT (POLY cases) match any TYPE
+         if (poly || callerType.equals("unknown"))
+         {
+            list.stream().forEach((m) -> m.wildcard++);
+            
+            if (poly)
+               polyArgs = true;
+            
+            // no cases can be excluded based on type; the list remains unchanged
+            if (debug)
+               System.out.printf("PARM %d: type wildcard %d\n", idx, list.size());
+         }
+         else
+         {
+            // table parameters
+            if (callerType.startsWith("TEMP-TABLE"))
+            {
+               // check for any exact TYPE matches
+               if (!processExactMatches(list, exactMatch))
+               {
+                  // allow a match to a table-handle if there is only 1 table-handle option
+                  allowIfOnlyOne(list, isTH, idx, callerType, error);
+               }
+               
+               if (debug)
+                  System.out.printf("PARM %d: TT %d\n", idx, list.size());
+            }
+
+            // table handles
+            else if (callerType.equals("TABLE-HANDLE"))
+            {
+               // at this point we can't know if we have a match, we can just remove anything that is not
+               // a table-handle or table; we defer the matching to the next phase once all other removals
+               // are done               
+               list.removeIf(isTH.or(isTable).negate());
+               list.stream().forEach((m) -> m.cvt++);
+               
+               if (debug)
+                  System.out.printf("PARM %d: TH %d\n", idx, list.size());
+            }
+            
+            // dataset parameters
+            else if (callerType.startsWith("DATASET <"))
+            {
+               // check for any exact TYPE matches
+               if (!processExactMatches(list, exactMatch))
+               {
+                  // allow a match to a dataset-handle if there is only 1 dataset-handle option
+                  allowIfOnlyOne(list, isDH, idx, callerType, error);
+               }
+               
+               if (debug)
+                  System.out.printf("PARM %d: DS %d\n", idx, list.size());
+            }
+            
+            // dataset handles
+            else if (callerType.equals("DATASET-HANDLE"))
+            {
+               // at this point we can't know if we have a match, we can just remove anything that is not
+               // a dataset-handle or dataset; we defer the matching to the next phase once all other
+               // removals are done               
+               list.removeIf(isDH.or(isDataset).negate());
+               list.stream().forEach((m) -> m.cvt++);
+               
+               if (debug)
+                  System.out.printf("PARM %d: DH %d\n", idx, list.size());
+            }
+
+            // buffers
+            else if (callerType.startsWith("BUFFER"))
+            {
+               // only an exact match works (handled above); no fuzzy matching here
+               list.removeIf(exactMatch.negate());
+               list.stream().forEach((m) -> m.exact++);
+               
+               if (debug)
+                  System.out.printf("PARM %d: buf %d\n", idx, list.size());
+            }
+
+            // must be one of the BaseDataType wrapper classes
+            else
+            {
+               final int     extIdx    = callerType.indexOf("[");
+               final int     extVal    = (extIdx == -1) ? 0 : getExtent(callerType);
+               final String  basicType = (extIdx == -1) ? callerType : callerType.substring(0, extIdx);
+
+               // extents
+               processExtentMatches(list, extVal, idx);
+               
+               if (debug)
+                  System.out.printf("PARM %d: after extent processing %d\n", idx, list.size());
+
+               // primitive and OO types widening/narrowing
+               // WARNING: this code is not idempotent, we modify the MemberMetrics instances of any that
+               //          are matched; this is BAD BAD BAD, but the widening/narrowing calculation is very
+               //          expensive so it makes no sense to duplicate that work
+               Predicate<MatchMetrics> isTypeMatch = (mem) -> 
+               {
+                  String base = mem.data.signature[idx].scalarType();
+                  
+                  Runnable increment = () -> mem.ooLvl[idx]++;
+                  
+                  // exact match to the base portion of the type will match for any mode (and is the only
+                  // possible match for input-output mode); for the rest of the cases we check the 
+                  // parameter mode at the definition (the caller might be unspecified (wildcard) but the
+                  // definition must be specified
+                  if (basicType.equals(base))
+                  {
+                     // yes, this is not idempotent; get over it
+                     mem.exact++;
+                     
+                     return true;
+                  }
+                  else if (mem.data.signature[idx].mode == KW_INPUT)
+                  {
+                     boolean widen = isWideningTypeMatch(basicType, base, increment);
+                     
+                     if (widen)
+                     {
+                        // yes, this is not idempotent; get over it
+                        mem.cvt++;
+                     }
+                     
+                     return widen;
+                  }
+                  else if (mem.data.signature[idx].mode == KW_OUTPUT)
+                  {
+                     boolean narrow = isNarrowingTypeMatch(basicType, base, increment);
+                     
+                     if (narrow)
+                     {
+                        // yes, this is not idempotent; get over it
+                        mem.cvt++;
+                     }
+                     
+                     return narrow;
+                  }
+                  
+                  return false;
+               };
+   
+               // WARNING: because isTypeMatch is not idempotent we make sure to only process it once
+               list.removeIf(isTypeMatch.negate());
+               
+               if (debug)
+                  System.out.printf("PARM %d: BDT type match %d\n", idx, list.size());
+            }
+         }
+      }
+
+      if (debug)
+      {
+         System.out.println("\n\n---PHASE 1 END---\n\n");
+         
+         for (MatchMetrics m : list)
+         {
+            System.out.printf("DEBUG: %s\n", m);
+         }
+      }
+      
+      // PHASE 2: if there are > 1 candidates we need to disambiguate based on priorities
+      
+      // unknown value, wildcard modes cannot be used to disambiguate
+      
+      if (list.size() > 1)
+      {
+         MethodSearchResult dyn = new MethodSearchResult();
+         
+         // POLY arguments activates runtime/dynamic mode
+         if (polyArgs)
+         {
+            if (debug)
+               System.out.println("\n\n---------fuzzyMethodLookup DYNAMIC POLY!---------\n");
+            
+            return dyn; 
+         }
+
+         // TABLE-HANDLE/DATASET-HANDLE arguments deferred processing; we don't calculate this earlier
+         // because some possible matches that would have been seen then may have been dropped by
+         // processing of later parameters; this means that we must handle the possible compile time and
+         // runtime matches here
+         final int[] numTB = new int[caller.length];
+         final int[] numTH = new int[caller.length];
+         final int[] numDS = new int[caller.length];
+         final int[] numDH = new int[caller.length];
+
          for (int i = 0; i < caller.length; i++)
          {
-            String candidateType = candidate[i].type;
-            String callerType = caller[i].type;
-            candidateType = fromJava(candidateType);
-            callerType = fromJava(callerType);
-
-            if (candidateType.equals(callerType))
-            {
-               // exact match, try the next one
-               continue inner;
-            }
-            else
-            {
-               if ("TEMP-TABLE".equals(candidateType) || 
-                   "TABLE-HANDLE".equals(candidateType))
-               {
-                  // handle can be passed to TABLE-HANDLE or DATASET-HANDLE in 4GL
-                  if ("TEMP-TABLE".equals(callerType) || 
-                      "TABLE-HANDLE".equals(callerType) ||
-                      "handle".equals(callerType))
-                  {
-                     continue inner;
-                  }
-                  
-                  continue outer;
-               }
-               else if ("DATASET".equals(candidateType) || 
-                        "DATASET-HANDLE".equals(candidateType))
-               {
-                  // handle can be passed to TABLE-HANDLE or DATASET-HANDLE in 4GL,
-                  // FWD doesn't support this at this time, so ignore it here.
-                  if ("DATASET".equals(callerType) || 
-                      "DATASET-HANDLE".equals(callerType))
-                  {
-                     continue inner;
-                  }
-                  
-                  continue outer;
-               }
-               else if (candidateType.startsWith("BUFFER"))
-               {
-                  if (callerType.equals(candidateType))
-                  {
-                     continue inner;
-                  }
-                  
-                  continue outer;
-               }
-               
-               // unknown value or BDT (POLY cases) match anything
-               if (callerType == null                     ||
-                   callerType.equals("BaseDataType")      ||
-                   callerType.equals("unknown"))
-               {
-                  exact = false;
-                  continue inner;
-               }
-               
-               // exact match on type but fuzzy extent spec match
-               int idx1 = candidateType.indexOf("[");
-               int idx2 = callerType.indexOf("[");
-               
-               boolean extentMatch = isExtentMatch(candidateType, callerType);
-               
-               if (idx1 != -1 && idx2 != -1)
-               {
-                  int ex1 = getExtent(candidateType);
-                  int ex2 = getExtent(callerType);
-                  
-                  candidateType = candidateType.substring(0, idx1);
-                  callerType = callerType.substring(0, idx2);
-                  
-                  extentMatch = extentMatch || ex1 == ex2 || ex1 == -1 || ex2 == -1;
-                  
-                  if (candidateType.equals(callerType))
-                  {
-                     if (extentMatch)
-                     {
-                        // match
-                        continue inner;
-                     }
-                     else
-                     {
-                        // not a match
-                        continue outer;
-                     }
-                  }
-               }
-               
-               exact = false;
-
-               // check the parameter mode at the definition
-               if (candidate[i].mode == KW_INPUT)
-               {
-                  // widening matches are OK, narrowing are NOT OK
-                  if (isWideningTypeMatch(callerType, candidateType) && extentMatch)
-                  {
-                     // match
-                     continue inner;
-                  }
-                  else
-                  {
-                     // not a match
-                     continue outer;
-                  }
-               }
-               else if (candidate[i].mode == KW_OUTPUT)
-               {
-                  // narrowing matches are OK, widening are NOT OK
-                  if (isNarrowingTypeMatch(callerType, candidateType) && extentMatch)
-                  {
-                     // match
-                     continue inner;
-                  }
-                  else
-                  {
-                     // not a match
-                     continue outer;
-                  }
-               }
-               else if (candidate[i].mode == KW_IN_OUT)
-               {
-                  // exact match is required
-                  if (!candidateType.equals(callerType))
-                  {
-                     // not a match
-                     continue outer;
-                  }
-               }
-            }
-         }
-         
-         // if we are here, we found a signature match; ignore not the right type
-         if (dat != null && ((!isStatic && (!dat.isStatic || internal)) ||
-                             (isStatic && dat.isStatic)))
-         {
-            matches.add(dat);
-            
-            if (exact)
-            {
-               exactMatches.add(dat);
-            }
-         }
-         else
-         {
-            // clear our result
-            dat = null;
-         }
-      }
-
-      if (matches.size() == 1)
-      {
-         mdat = matches.get(0);
-         return mdat;
-      }
-      else if (exactMatches.size() == 1)
-      {
-         mdat = exactMatches.get(0);
-         return mdat;
-      }
-      
-      // multiple fuzzy matches, get by parameter mode
-      iter = matches.iterator();
-      
-      outer:
+            final int idx = i;
+            
+            String callerType = fromJava(caller[i].type);
+         
+            if ("TABLE-HANDLE".equals(callerType))
+            {
+               Predicate<MatchMetrics> isTable = (m) -> m.data.signature[idx].type.startsWith("TEMP-TABLE");
+               Predicate<MatchMetrics> isTH    = (m) -> "TABLE-HANDLE".equals(m.data.signature[idx].type);
+
+               Consumer<MatchMetrics> trackT = (m) ->
+               {
+                  if (isTable.test(m))
+                  {
+                     numTB[idx]++;
+                  }
+                  else if (isTH.test(m))
+                  {
+                     numTH[idx]++;
+                  }
+               };
+
+               list.forEach(trackT);
+               
+               if (numTH[idx] == 1 && numTB[idx] == 0)
+               {
+                  list.removeIf(isTH.negate());
+                  break;
+               }
+               else if (numTH[idx] == 0 && numTB[idx] == 1)
+               {
+                  list.removeIf(isTable.negate());
+                  break;
+               }
+               else if ((numTH[idx] + numTB[idx]) > 1)
+               {
+                  // dynamic mode
+                  if (debug)
+                     System.out.println("\n\n---------fuzzyMethodLookup DYNAMIC TABLE-HANDLE!---------\n");
+                  
+                  return dyn;
+               }
+               else
+               {
+                  // no matches
+                  list.clear();
+                  error[0] = String.format("No possible matches for parameter %d of type %s.",
+                                           idx,
+                                           callerType);
+                  break;
+               }
+            }
+            
+            if ("DATASET-HANDLE".equals(callerType))
+            {
+               Predicate<MatchMetrics> isDataset = (m) -> m.data.signature[idx].type.startsWith("DATASET <");
+               Predicate<MatchMetrics> isDH      = (m) -> "DATASET-HANDLE".equals(m.data.signature[idx].type);
+            
+               Consumer<MatchMetrics> trackD = (m) ->
+               {
+                  if (isDataset.test(m))
+                  {
+                     numDS[idx]++;
+                  }
+                  else if (isDH.test(m))
+                  {
+                     numDH[idx]++;
+                  }
+               };
+               
+               list.forEach(trackD);
+            
+               if (numDH[idx] == 1 && numDS[idx] == 0)
+               {
+                  list.removeIf(isDH.negate());
+                  break;
+               }
+               else if (numDH[idx] == 0 && numDS[idx] == 1)
+               {
+                  list.removeIf(isDataset.negate());
+                  break;
+               }
+               else if ((numDH[idx] + numDS[idx]) > 1)
+               {
+                  // dynamic mode
+                  if (debug)
+                     System.out.println("\n\n---------fuzzyMethodLookup DYNAMIC DATASET-HANDLE!---------\n");
+                  
+                  return dyn;
+               }
+               else
+               {
+                  // no matches
+                  list.clear();
+                  error[0] = String.format("No possible matches for parameter %d of type %s.",
+                           idx,
+                           callerType);
+                  break;
+               }
+            }            
+         }
+      }
+
+      // exact vs fuzzy weighting disambiguation if needed
+      if (list.size() > 1)
+      {
+         // use the natural ordering of the metrics class to find the best match
+         Collections.sort(list, Collections.reverseOrder());
+         
+         MatchMetrics best     = list.get(0);
+         MatchMetrics runnerUp = list.get(1);
+         
+         // safety check; if it fails we report the ambiguity below
+         if (best.compareTo(runnerUp) == 1)
+         {
+            // clear everything except the best match
+            list = new LinkedList<>();
+            list.add(best);
+         }
+      }
+      
+      if (list.size() == 1)
+      {
+         // "there can be only one"
+         if (debug)
+            System.out.println("\n\n----------------fuzzyMethodLookup HIGHLANDER!----------------\n");
+         
+         MatchMetrics match = list.get(0);
+         
+         return new MethodSearchResult(match.data, match.overrides);
+      }
+      else if (list.isEmpty())
+      {
+         if (debug)
+         {
+            // if we still have ambiguous matches, display any stored error
+            if (error[0] != null)
+            {
+               System.out.println(error[0]);
+            }
+
+            System.out.println("\n\n----------------fuzzyMethodLookup NO MATCH SUCKA!----------------\n");
+         }
+         
+         return null;
+      }
+
+      System.out.println("WARNING: more than one method def found using fuzzy lookup: " + name + 
+                         " from class " + getName());
+      
+      System.out.printf("\n   CALLER %s\n   PARAMETERS: %d\n", node.dumpTree(true), caller.length);
+      
+      for (int i = 0; i < caller.length; i++)
+      {
+         System.out.printf("      %s\n", caller[i]);
+      }
+ 
+      Iterator<MatchMetrics> iter = list.iterator();
+      int num = 0;
+      
       while (iter.hasNext())
       {
-         MemberData dat = iter.next();
-         ParameterKey[] candidate = dat.signature;
-         
-         inner:
-         for (int i = 0; i < candidate.length; i++)
-         {
-            Integer candidateMode = candidate[i].mode;
-            Integer callerMode = caller[i].mode;
-            
-            if (Objects.equals(candidateMode, callerMode) || callerMode == null)
-            {
-               // either the same or the caller hasn't specified the argument mode
-               // TODO: the mode is not mandatory at the definition... assume INPUT?
-               continue inner;
-            }
-            else
-            {
-               // mismatch
-               continue outer;
-            }
-         }
-         
-         // found a match
-         if (mdat != null)
-         {
-            // add a logging that more than one definition matches this fuzzy
-            // signature
-            System.out.println("WARNING: more than one method def found " + 
-                               "using fuzzy lookup: " + mdat.name + 
-                               " from class " + getName());
-            
-            System.out.printf("\n- access %s, static %b\n" +
-                              "   PARAMETERS: %d\n", 
-                              ProgressParser.lookupTokenName(mdat.access),
-                              mdat.isStatic,
-                              mdat.signature.length);
-            for (int i = 0; i < mdat.signature.length; i++)
-            {
-               System.out.printf("      %s\n", mdat.signature[i]);
-            }
-
-            System.out.printf("\n- access %s, static %b\n" +
-                              "   PARAMETERS: %d\n", 
-                              ProgressParser.lookupTokenName(dat.access),
-                              dat.isStatic,
-                              dat.signature.length);
-            for (int i = 0; i < dat.signature.length; i++)
-            {
-               System.out.printf("      %s\n", dat.signature[i]);
-            }
-            
-            System.out.printf("\nby caller %s\n   PARAMETERS: %d\n", 
-                              node.dumpTree(true), 
-                              caller.length);
-            for (int i = 0; i < caller.length; i++)
-            {
-               System.out.printf("      %s\n", caller[i]);
-            }
-            
-            if (polyParams)
-            {
-               return null;
-            }
-            
-            break outer;
-         }
-
-         mdat = dat;
+         MatchMetrics dat = iter.next();
+         
+         System.out.printf("   MATCH %d: %s\n\n", num, dat.toString());
+         num++;
       }
       
-      return mdat;
+      if (debug)
+         System.out.println("\n\n--------------------fuzzyMethodLookup BITTER END---------------------\n");
+
+      // return the first one in the sorted list
+      MatchMetrics match = list.get(0);
+      
+      return new MethodSearchResult(match.data, match.overrides);
    }
    
    /**
@@ -3629,16 +3969,16 @@
     *
     * @return   List of possible methods that match.  This may be an empty list if no match exists.
     */
-   private List<MemberData> candidates(String            name,
-                                       int               num,
-                                       int               access,
-                                       boolean           isStatic,
-                                       boolean           internal,
-                                       boolean           first,
-                                       Set<SignatureKey> exist)
+   private List<MatchMetrics> candidates(String            name,
+                                          int               num,
+                                          int               access,
+                                          boolean           isStatic,
+                                          boolean           internal,
+                                          boolean           first,
+                                          Set<SignatureKey> exist)
    {
-      Set<SignatureKey> existing = exist == null ? new HashSet<>() : exist;
-      List<MemberData>  results  = new ArrayList<>();
+      Set<SignatureKey>   existing = exist == null ? new HashSet<>() : exist;
+      List<MatchMetrics> results  = new LinkedList<>();
       
       synchronized (lock)
       {
@@ -3652,25 +3992,32 @@
             {
                if (first)
                {
-                  results.add(defs.values().iterator().next());
+                  results.add(new MatchMetrics(defs.values().iterator().next()));
                   
                   return results;
                }
                
                Iterator<MemberData> iter = defs.values().iterator();
                
-               outer:
                while (iter.hasNext())
                {
                   MemberData dat = checkAccessRights(iter.next(), access);
                   
                   if (dat != null && dat.signature.length == num)
                   {
-                     SignatureKey key = (num > 0) ? new SignatureKey(dat.signature) : null;
-                     
-                     if (num == 0 || !existing.contains(key))
-                     {
-                        results.add(dat);
+                     // special case: no parameters; this is Highlander (i.e. "there can be only one")
+                     // and we don't have to search the parents, this is the match
+                     if (num == 0)
+                     {
+                        results.add(new MatchMetrics(dat));
+                        return results;
+                     }
+                     
+                     SignatureKey key = new SignatureKey(dat.signature);
+                     
+                     if (!existing.contains(key))
+                     {
+                        results.add(new MatchMetrics(dat));
                         existing.add(key);
                      }
                   }
@@ -3678,8 +4025,8 @@
             }
          }
          
-         // fuzzy matches are processed up the the parent hierarchy even if there are possible matches in
-         // the current class; static lookups only work for internal usage, non-static lookups always work
+         // matches are processed up the the parent hierarchy even if there are possible matches in the
+         // current class; static lookups only work for internal usage, non-static lookups always work
          if (parents != null && (!isStatic || internal))
          {
             // change access mode since we aren't allowed to see private stuff in parent
@@ -3687,7 +4034,7 @@
             
             for (ClassDefinition parent : parents)
             {
-               // recurse up inheritence hierarchy
+               // recurse up inheritance hierarchy
                results.addAll(parent.candidates(name, num, _access, isStatic, internal, first, existing));
             }
          }
@@ -3749,6 +4096,99 @@
       
       return type;
    }
+
+   /**
+    * Detect if the given class is an instance of an implemented interface of this class.
+    * 
+    * @param    cls
+    *           The class to check.
+    *           
+    * @return   {@code true} if the given class is an implemented interface in this class. 
+    */
+   private boolean isImplemented(ClassDefinition cls)
+   {
+      if (interfaces == null)
+         return false;
+      
+      return interfaces.contains(cls.getName()) || interfaces.contains(cls.getName().toLowerCase());
+   }
+   
+   /**
+    * Detect if the given class is an instance of a parent class or an implemented interface of a parent
+    * class.
+    * 
+    * @param    cls
+    *           The class to check.
+    * @param    ifaces
+    *           {@code true} if implemented interfaces should be checked.
+    * @param    incrementer
+    *           Code to be executed to increment the inheritance level.
+    *
+    * @return   {@code true} if the given class is a parent class or an implemented interface of a parent
+    *           class. 
+    */
+   private boolean isInheritedFrom(ClassDefinition cls, boolean ifaces, Runnable incrementer)
+   {
+      if (parents != null)
+      {
+         for (ClassDefinition parent : parents)
+         {
+            if (cls == parent || (ifaces && parent.isImplemented(cls)))
+            {
+               // track the current parent level
+               incrementer.run();
+
+               return true;
+            }
+            
+            if (parent.isInheritedFrom(cls, ifaces, incrementer))
+            {
+               // track our level's contribution to the parent level
+               incrementer.run();
+               
+               return true;
+            }
+         }
+      }
+      
+      return false;
+   }
+   
+   /**
+    * Parse the given object spec (in the form "object<? extends _CLS_>" or "jobject<? extends _CLS_>") and
+    * return the class definition associated with _CLS_.
+    * 
+    * @param    spec
+    *           The specification to parse.
+    *           
+    * @return   The class definition or {@code null} if the spec was malformed or if there is no such
+    *           definition.
+    */
+   private ClassDefinition parseObjectSpec(String spec)
+   {
+      String sig = "object<? extends ";
+      int    idx = spec.indexOf(sig);
+      
+      ClassDefinition cls = null;
+      
+      if (idx != -1)
+      {
+         String cname = spec.substring(idx + sig.length(), spec.length() - 1);
+         
+         cls = SymbolResolver.getClassDefinition(cname);
+         
+         if (cls == null)
+         {
+            System.err.printf("ERROR: No class found for %s (object spec %s)!\n", cname, spec);
+         }
+      }
+      else
+      {
+         System.err.printf("ERROR: Malformed object spec %s!\n", spec);
+      }
+      
+      return cls;
+   }
    
    /**
     * Check if the callee's type can be written back to a wider caller's type, when viewed from the
@@ -3766,30 +4206,47 @@
     * </pre>
     * <p>
     * Object types narrow by these rules:
-    *
+    * <ol>
+    *    <li> We check if the callee matches a child class of the caller type's specific class.  The
+    *         nearest child class is matched with highest priority.
+    * </ol>
     *
     * @param    caller
     *           The caller's passed instance's data type.
     * @param    callee
     *           The candidate callee method's parameter data type.
+    * @param    incrementer
+    *           Code to be executed to increment the inheritance level.
     *
     * @return   {@code true} if a narrowing operation is possible.
     */
-   private boolean isNarrowingTypeMatch(String caller, String callee)
+   private boolean isNarrowingTypeMatch(String caller, String callee, Runnable incrementer)
    {
-      if (("int64".startsWith(caller)      && "integer".startsWith(callee))               ||
-          ("longchar".startsWith(caller)   && "character".startsWith(callee))             ||
-          ("datetime".startsWith(caller)   && "date".startsWith(callee))                  ||
-          ("datetimetz".startsWith(caller) && ("date".startsWith(callee) || "datetime".startsWith(callee))))
+      if (("int64".equals(caller)      && "integer".equals(callee))                             ||
+          ("decimal".equals(caller)    && ("integer".equals(callee) || "int64".equals(callee))) ||
+          ("longchar".equals(caller)   && "character".equals(callee))                           ||
+          ("datetime".equals(caller)   && "date".equals(callee))                                ||
+          ("datetimetz".equals(caller) && ("date".equals(callee) || "datetime".equals(callee))))
       {
          return true;
       }
           
       if (caller.startsWith("object") && callee.startsWith("object"))
       {
-         return true;
+         // parse out the classes
+         ClassDefinition src = parseObjectSpec(caller);
+         ClassDefinition tgt = parseObjectSpec(callee);
+         
+         // check if the target parameter is a child class of the caller (this explicitly ignores
+         // implemented interfaces which is how the 4GL does it)
+         if (tgt.isInheritedFrom(src, false, incrementer))
+         {
+            return true;
+         }
+         
+         return false;
       }
-      
+                
       return false;
    }
    
@@ -3807,32 +4264,57 @@
     * datetime           datetime-tz
     * </pre>
     * <p>
-    * Object types narrow by these rules:
-    *
+    * Object types widen by these rules:
+    * <ol>
+    *    <li> We check if the callee matches any of the interfaces implemented by the caller type's specific class.
+    *    <li> We check if the callee matches any of the parent class hierarchy (and their implemented interfaces)
+    *         of the caller type's specific class.
+    *    <li> We check if the callee is Progress.Lang.Object.  This check has lowest priority.
+    * </ol>
     *
     * @param    caller
     *           The caller's passed instance's data type.
     * @param    callee
     *           The candidate callee method's parameter data type.
+    * @param    incrementer
+    *           Code to be executed to increment the inheritance level.
     *
     * @return   {@code true} if a widening operation is possible.
     */
-   private boolean isWideningTypeMatch(String caller, String callee)
+   private boolean isWideningTypeMatch(String caller, String callee, Runnable incrementer)
    {
-      if (("int64".startsWith(caller) && "decimal".startsWith(callee))                                   ||
-          ("integer".startsWith(caller) && ("int64".startsWith(callee) || "decimal".startsWith(callee))) ||
-          ("character".startsWith(caller) && "longchar".startsWith(callee))                              ||
-          ("datetime".startsWith(caller) && "datetimetz".startsWith(callee))                             ||
-          ("date".startsWith(caller) && ("datetime".startsWith(callee) || "datetimetz".startsWith(callee))))
+      if (("int64".equals(caller) && "decimal".equals(callee))                                   ||
+          ("integer".equals(caller) && ("int64".equals(callee) || "decimal".equals(callee)))     ||
+          ("character".equals(caller) && "longchar".equals(callee))                              ||
+          ("datetime".equals(caller) && "datetimetz".equals(callee))                             ||
+          ("date".equals(caller) && ("datetime".equals(callee) || "datetimetz".equals(callee))))
       {
          return true;
       }
+      
+      if (caller.startsWith("object") && callee.startsWith("object"))
+      {
+         // parse out the classes
+         ClassDefinition src = parseObjectSpec(caller);
+         ClassDefinition tgt = parseObjectSpec(callee);
+         
+         // check if the target is in the interface list of the caller type's specific class
+         if (src.isImplemented(tgt))
+         {
+            return true;
+         }
+         
+         // check if the target matches the caller type's parent class hierarchy (including implemented
+         // interfaces)
+         if (src.isInheritedFrom(tgt, true, incrementer))
+         {
+            return true;
+         }
+
+         // lowest priority/last ditch check
+         return "progress.lang.object".equals(tgt.getName());
+      }
                 
-      if (caller.startsWith("object") && callee.startsWith("object"))
-      {
-         return true;
-      }
-      
       return false;
    }
    
@@ -4155,244 +4637,55 @@
    }
    
    /**
-    * Stores a member's data.
+    * Returns the result from a method lookup.
     */
-   private static class MemberData
+   private static class MethodSearchResult
    {
-      /** Text name. */
-      private String name = null;
-      
-      /** Token type. */
-      private int type = -1;
-      
-      /** Extent value (defaults to 0, which means scalar) of the return type. */
-      private int extent = 0;
-      
-      /** Access mode. */
-      private int access = -1;
-      
-      /** Temporary ID. */
-      private int tempIdx = -1;
-      
-      /** Static or instance member. */
-      private boolean isStatic = false;
-      
-      /** Class type name for members that return an object instance. */
-      private String qname = null;
-      
-      /** Parameter signature for methods. */
-      private ParameterKey[] signature = null;
-      
-      /** Associated variable instance for data members. */
-      private Variable var = null; // used only during parsing/front phase
-      
-      /** The containing class where this was defined. */
-      private ClassDefinition container = null;
-      
-      /** The parameter modes. */
-      private String modes = null;
-      
-      /**
-       * Marker for OO vars which are gathered during pre-scan, and they might not be actual class
-       * members (as pre-scan processes DEFINE VAR statements at all levels, including methods).
-       */
-      private boolean provisional = false;
-      
-      /** The AST for this member definition. After parsing is finished, this is cleared. */
-      private Aast ast = null;
-      
-      /** The AST's ID for this member definition. */
-      private Long astId = null;
+      /** The method that was found. */
+      final private MemberData data;
+      
+      /** Type overrides for fuzzy matches. */
+      final private String[] overrides;
 
-      /** All annotations set at the method's return type. */
-      // WARNING: this is not set by initialize, in case of METHOD
-      private Map<String, Object> retType;
-      
-      /** 
-       * The converted Java name for this member. Used only for previously parsed classes, or
-       * legacy builtin classes.
-       */
-      private String javaname = null;
-      
-      /**
-       * Create an instance, setting the data members.
-       *
-       * @param    name
-       *           The name of the member.
-       * @param    var
-       *           Associated variable instance for data members or <code>null</code>.
-       * @param    signature
-       *           Parameter signature for methods or <code>null</code>.
-       * @param    type
-       *           The token type associated with the member.
-       * @param    access
-       *           Access mode (<code>KW_PUBLIC</code>,
-       *           <code>KW_PROTECTD</code> or <code>KW_PRIVATE</code>).
-       * @param    isStatic
-       *           <code>true</code> if the variable/property is static.
-       * @param    qname
-       *           Fully qualified class name where <code>type</code> equals
-       *           <code>OO_METH_CLASS</code>. <code>null</code> if this method
-       *           does not return an object instance.
-       * @param    container
-       *           The container class definition.
-       * @param    tempIdx
-       *           Temporary ID.
-       */
-      public MemberData(String          name,
-                        Variable        var,
-                        ParameterKey[]  signature,
-                        int             type,
-                        int             access,
-                        boolean         isStatic,
-                        String          qname,
-                        ClassDefinition container,
-                        int             tempIdx,
-                        Map<String, Object> retType)
-      {
-         this.name      = name;
-         this.var       = var;
-         this.signature = signature;
-         this.type      = type;
-         this.access    = access;
-         this.isStatic  = isStatic;
-         this.qname     = qname;
-         this.retType   = retType;
-         this.container = container;
-         this.tempIdx   = tempIdx;
-         
-         if (signature != null)
-         {
-            this.modes = "";
-            for (ParameterKey pk : signature)
-            {
-               if (pk.mode == null)
-               {
-                  modes = modes + "B";
-                  continue;
-               }
-               
-               switch (pk.mode)
-               {
-                  case KW_INPUT:
-                     modes = modes + "I";
-                     break;
-                  case KW_OUTPUT:
-                     modes = modes + "O";
-                     break;
-                  case KW_IN_OUT:
-                     modes = modes + "U";
-                     break;
-                  case KW_BUFFER:
-                     modes = modes + "B";
-                     break;
-               }
-            }
-         }
-      }
-      
-      /**
-       * The parse for this entire file has finished, and the AST IDs have been computed.  
-       * This is the time the capture the AST IDs or add other annotations, as is executed just
-       * before the AST is persisted.
-       */
-      public void parseFinished()
-      {
-         BiConsumer<Aast, Aast> p = (ast, parent) ->
-         {
-            if (ast.isAnnotation("access-mode"))
-            {
-               parent.putAnnotation("access-mode", (Long) ast.getAnnotation("access-mode"));
-               ast.removeAnnotation("access-mode");
-            }
-            if (ast.isAnnotation("oo-data-store"))
-            {
-               parent.putAnnotation("oo-data-store", (String) ast.getAnnotation("oo-data-store"));
-               ast.removeAnnotation("oo-data-store");
-            }
-            if (ast.isAnnotation("static"))
-            {
-               parent.putAnnotation("static", (Boolean) ast.getAnnotation("static"));
-               ast.removeAnnotation("static");
-            }
-         };
-         if (ast != null)
-         {
-            int type = ast.getType();
-            if (type == KW_TEMP_TAB ||
-                (type == SYMBOL && ast.getParent().getType() == DEFINE_BUFFER))
-            {
-               Aast parent = ast.getParent();
-               p.accept(ast, parent);
-               
-               if (type == KW_TEMP_TAB || type == KW_WORK_TAB)
-               {
-                  ast = parent;
-               }
-               else
-               {
-                  ast = parent;
-                  ast = ast.getImmediateChild(KW_FOR, null);
-                  ast = (Aast) ast.getFirstChild();
-               }
-            }
-            if (type == SYMBOL && ast.upPath("DEFINE_QUERY/KW_QUERY"))
-            {
-               Aast parent = ast.getAncestor(2);
-               p.accept(ast, parent);
-               
-               parent.putAnnotation("name", ast.getText());
-               ast = parent;
-            }
-            
-            astId = ast.getId();
-            ast = null;
-         }
-      }
-      
-      /**
-       * Set the {@link #provisional} state of this member.
-       * 
-       * @param    provisional
-       *           The new state.
-       */
-      public void setProvisional(boolean provisional)
-      {
-         this.provisional = provisional;
-      }
-      
-      /**
-       * Render the state of the instance in a text form.
-       *
-       * @return   The rendered instance.
-       */
-      public String toString()
-      {
-         StringBuilder sb = new StringBuilder();
-         
-         String qual = (qname == null) ? "" : String.format(" - %s", qname);
-         
-         sb.append(String.format("%s %b member %s (%s%s), extent %d in class %s, tempIdx %d\n",
-                                 ProgressParser.lookupTokenName(access),
-                                 isStatic,
-                                 name,
-                                 ProgressParser.lookupTokenName(type),
-                                 qual,
-                                 extent,
-                                 container.getName(),
-                                 tempIdx));
-          
-         if (signature != null)
-         {
-            sb.append(String.format("   PARAMETERS: %d\n", signature.length));
-            
-            for (int i = 0; i < signature.length; i++)
-            {
-               sb.append(String.format("      %s\n", signature[i]));
-            }
-         }
-         
-         return sb.toString();
+      /**
+       * Create an dynamic invocation instance.
+       */
+      MethodSearchResult()
+      {
+         data      = null;
+         overrides = null;
+      }
+      
+      /**
+       * Create an instance using the given result.
+       * 
+       * @param    data
+       *           The result of the search.
+       */
+      MethodSearchResult(MemberData data, String[] overrides)
+      {
+         this.data      = data;
+         this.overrides = overrides;
+      }
+      
+      /**
+       * Reports if this is a dynamic invocation.
+       * 
+       * @return   {@code true} if this is a dynamic invocation.
+       */
+      boolean isDynamic()
+      {
+         return data == null;
+      }
+      
+      /**
+       * Obtain any types overrides which are needed because of fuzzy matching.
+       *  
+       * @return   The type overrides defined for this match or {@code null} if there are no overrides.
+       */
+      String[] getOverrides()
+      {
+         return overrides;
       }
    }
 }

=== modified file 'src/com/goldencode/p2j/uast/ParameterKey.java'
--- old/src/com/goldencode/p2j/uast/ParameterKey.java	2019-05-20 17:03:26 +0000
+++ new/src/com/goldencode/p2j/uast/ParameterKey.java	2021-03-30 18:08:22 +0000
@@ -2,14 +2,14 @@
 ** Module   : ParameterKey.java
 ** Abstract : wrapper for a parameter signature in a method.
 **
-** Copyright (c) 2018-2019, Golden Code Development Corporation.
+** Copyright (c) 2018-2021, Golden Code Development Corporation.
 **
-** -#- -I- --Date-- ---------------------------------Description----------------------------------
+** -#- -I- --Date-- --------------------------------------Description---------------------------------------
 ** 001 CA  20181229 First version.
-** 002 CA  20190423 WrappedResource must be treated as a handle type.  Normalized DataSet type's
-**                  case.
-** 003 CA  20190508 Changes to solve collisions in converted Java signature collisions for 
-**                  overloaded methods.
+** 002 CA  20190423 WrappedResource must be treated as a handle type.  Normalized DataSet type's case.
+** 003 CA  20190508 Changes to solve collisions in converted Java signature collisions for overloaded 
+**                  methods.
+** 004 GES 20210305 Added typeEquivalent().
 */
 
 /*
@@ -76,8 +76,6 @@
 /**
  * Container for a parameter's key, including its data type and mode.  Used both at caller and
  * at the OO method definition.
- * 
- * TODO: table parameters are matched via the temp-table's field types
  */
 public class ParameterKey
 {
@@ -111,6 +109,31 @@
    }
    
    /**
+    * Report if the type of the two parameters is the same. The mode is not considered.
+    * 
+    * @param    other
+    *           The instance to compare against.
+    *           
+    * @return   {@code true} if the types are the same or are both {@code null}.  If they are different
+    *           (including if only one is {@code null}) then {@code false} will be returned. 
+    */
+   public boolean typeEquivalent(ParameterKey other)
+   {
+      return Objects.equals(this.type, other.type);
+   }
+   
+   /**
+    * Report the type without any encoded extent.
+    * 
+    * @return   The type with any extent portion removed. 
+    */
+   public String scalarType()
+   {
+      int idx = type.indexOf('[');
+      return (idx < 0) ? type : type.substring(0, idx);
+   }
+   
+   /**
     * Test {@code other} object for equality.
     * 
     * @param   other
@@ -125,14 +148,15 @@
       {
          return true;
       }
-      if (other == null || getClass() != other.getClass())
+      
+      if (other == null || !(other instanceof ParameterKey))
       {
          return false;
       }
       
       ParameterKey otherKey = (ParameterKey) other;
       
-      return Objects.equals(this.mode, otherKey.mode) && Objects.equals(this.type, otherKey.type);
+      return Objects.equals(this.mode, otherKey.mode) && typeEquivalent(otherKey);
    }
    
    /**
@@ -178,8 +202,6 @@
     */
    public String toJava()
    {
-      // TODO: some of these calls must be marked as 'dynamic'??
-
       if (type.indexOf('[') > 0)
       {
          switch (mode.intValue())
@@ -200,12 +222,14 @@
          return object.class.getSimpleName();
       }
       
-      if (type.equalsIgnoreCase("dataset") || type.equalsIgnoreCase("dataset-handle"))
+      // this matches both "dataset <signature>" and "dataset-handle"
+      if (type.toLowerCase().startsWith("dataset"))
       {
          return DataSetParameter.class.getSimpleName();
       }
 
-      if (type.equalsIgnoreCase("temp-table") || type.equalsIgnoreCase("table-handle"))
+      // for "temp-table <signature>" we can't just check equals because it has the field structure encoded 
+      if (type.toLowerCase().startsWith("temp-table") || type.equalsIgnoreCase("table-handle"))
       {
          return TableParameter.class.getSimpleName();
       }

=== modified file 'src/com/goldencode/p2j/uast/SymbolResolver.java'
--- old/src/com/goldencode/p2j/uast/SymbolResolver.java	2021-02-19 21:59:06 +0000
+++ new/src/com/goldencode/p2j/uast/SymbolResolver.java	2021-04-13 20:38:28 +0000
@@ -443,6 +443,9 @@
 **                           involved in dynamic or static OO calls, functions or procedure calls.  This 
 **                           includes mostly cases for OO properties/variables (static and non-static).
 **     OM  20210219          lookupWorkerExact returns -1 if null dictionary is provided instead of NPE.
+**     GES 20210413          Added lookupTableInfo() to reduce the number of name node lookups. Reworked
+**                           translateType() to properly handle buffers, tables, datasets, table-handles,
+**                           dataset-handles and objects.
 */
 
 /*
@@ -1296,14 +1299,7 @@
             {
                if (parm.getType() == PARAMETER)
                {
-                  String name = translateType((int) ((long) parm.getAnnotation("type")), parm);
-                  
-                  if ("object".equals(name) || "jobject".equals(name))
-                  {
-                     String qual = (String) parm.getAnnotation("qualified");
-                     
-                     name = String.format("%s<? extends %s>", name, qual);
-                  }
+                  String name = translateType((int) ((long) parm.getAnnotation("type")), parm, null);
                   
                   name = formatExtentName(name, readExtent(parm));
                   
@@ -1560,11 +1556,8 @@
       
       if (stype == DEFINE_PROPERTY)
       {
-         String ptype = translateType(var.getTokenType(), null);
-         if ("object".equals(ptype) && var.getClassName() != null)
-         {
-            ptype = "object<? extends " + var.getClassName() + ">";
-         }
+         String ptype = translateType(var.getTokenType(), null, var.getClassName());
+         
          if (var.isExtent())
          {
             String ext = var.isExtent() 
@@ -1709,10 +1702,12 @@
     *           The token type representing a variable/parameter type. 
     * @param    parm
     *           The PARAMETER node which is used to extract the type of this parameter.
+    * @param    qualified
+    *           Fully qualified object classname or {@code null} if no qualifier is needed. 
     *
     * @return   The wrapper class name.
     */
-   public static String translateType(int type, Aast parm)
+   public static String translateType(int type, Aast parm, String qualified)
    {
       if (parm != null)
       {
@@ -1778,22 +1773,49 @@
             break;
          case VAR_CLASS:
             cls = "object";
+            
+            String qual = qualified;
+            
             if (parm != null)
             {
                if (parm.isAnnotation("is-java") && (Boolean) parm.getAnnotation("is-java"))
                {
                   cls = "jobject";
                }
+               
+               if (qualified == null)
+               {
+                  qual = (String) parm.getAnnotation("qualified");
+               }
             }
+            
+            cls = String.format("%s<? extends %s>", cls, qual);
             break;
             
-         // TABLE and BUFFER parameters - these will have the TABLE or BUFFER prefix,
-         // followed by the schemaname.
+         // TABLE and BUFFER parameters - these will have the TEMP-TABLE or BUFFER prefix,
+         // followed by the schemaname or other details that make the table unambigous
          case KW_TABLE:
             ref = parm.getType() == KW_TABLE ? parm : parm.getImmediateChild(KW_TABLE, null);
             ref = ref.getImmediateChild(TEMP_TABLE, null);
+            
+            if (ref == null)
+            {
+               // this is unexpected since permanent tables and work-tables should not be possible here
+               // and buffers are handled below
+               throw new RuntimeException(String.format("TABLE parameters should be temp-tables: %s",
+                                                        parm.dumpTree()));
+            }
+            
+            String signature = (String) ref.getAnnotation("signature");
+            
+            if (signature == null)
+            {
+               throw new RuntimeException(String.format("Missing 'signature' annotation in: %s",
+                                                        ref.dumpTree()));
+            }
+            
             // for temp-tables, the compile time mapping is done via the table's field signature
-            cls = "TEMP-TABLE";
+            cls = String.format("TEMP-TABLE <%s>", signature);
             break;
          case KW_BUFFER:
             ref = parm.getType() == KW_BUFFER ? parm : parm.getImmediateChild(KW_BUFFER, null);
@@ -1805,18 +1827,51 @@
             if (ref.getType() == TEMP_TABLE || 
                 (ref.getType() == BUFFER && "".equals(ref.getAnnotation("dbname"))))
             {
-               cls = "BUFFER <temp-table>";
+               String tabname = (String) ref.getAnnotation("schemaname");
+               String tabsig  = (String) ref.getAnnotation("signature");
+               
+               if (tabsig == null)
+               {
+                  throw new RuntimeException(String.format("Missing 'signature' annotation in: %s",
+                                                           ref.dumpTree()));
+               }
+               
+               cls = String.format("BUFFER <temp-table %s <%s>>", tabname.toLowerCase(), tabsig);
             }
             else
             {
-               cls = String.format("BUFFER %s", ref.getAnnotation("schemaname"));
+               cls = String.format("BUFFER <%s>", ref.getAnnotation("schemaname"));
             }
             break;
          case KW_DATASET:
-            cls = "DATASET";
+            ref = parm.getType() == KW_DATASET ? parm : parm.getImmediateChild(KW_DATASET, null);
+            
+            if (ref == null)
+            {
+               throw new RuntimeException(String.format("Missing KW_DATASET in: %s", parm.dumpTree()));
+            }
+            
+            ref = (Aast) ref.getFirstChild();
+            
+            if (ref == null || ref.getType() != DATA_SET)
+            {
+               throw new RuntimeException(String.format("Missing DATA_SET in: %s", parm.dumpTree()));
+            }
+            
+            String sig = (String) ref.getAnnotation("signature");
+            
+            if (sig == null)
+            {
+               throw new RuntimeException(String.format("Missing 'signature' annotation in: %s",
+                                                        ref.dumpTree()));
+            }
+            
+            cls = String.format("DATASET %s", sig);
             break;
          case KW_DATA_SRC:
-            // TODO: example with this as parameter and argument?
+            // TODO: as far as I know, this is not possible; dump some data so we can generate an example 
+            //       with this as parameter and argument
+            System.err.printf("WARNING: UNEXPECTED DATA-SOURCE PARAMETER %s\n", parm.dumpTree());
             cls = "DATA-SOURCE";
             break;
       }
@@ -1870,10 +1925,11 @@
                 type != KW_BY_VALUE)
             {
                String cls = ExpressionConversionWorker.expressionType(parm, false, false);
-               
-               if ("Buffer".equalsIgnoreCase(cls))
+
+               // this is not just buffers, it is also any form of table reference except for table-handle
+               if ("Buffer".equalsIgnoreCase(cls) || "DATASET".equalsIgnoreCase(cls))
                {
-                  cls = translateType(parm.getType(), parm);
+                  cls = translateType(parm.getType(), parm, null);
                }
                else
                {
@@ -4426,7 +4482,8 @@
       // annotateClassRef, but left here in case there are other situations added.
       if (qname == null)
       {
-         System.out.println("FAILURE: Retruning from annotateClassRef due null qname " + node.dumpTree(true));
+         System.out.println("FAILURE: Returning from annotateClassRef because of null qname " +
+                            node.dumpTree(true));
          return;
       }
       ClassDefinition cls = lookupClass(qname);
@@ -6797,7 +6854,7 @@
     * given the field's name and an optional table name-node.
     * 
     * @param   table
-    *          Name node asssociated with the field reference. If not {@code null}, only the
+    *          Name node associated with the field reference. If not {@code null}, only the
     *          private namespace of the given {@code table} is searched (for an exact match)
     *          of {@code fieldName}. If {@code null}, a lenient search (i.e., abbreviations
     *          permitted) is performed across all scopes in the schema dictionary.
@@ -6833,6 +6890,45 @@
    }
    
    /**
+    * Look up and return the table info instance which describes a database table in a schema.
+    *
+    * @param   name
+    *          Text string to match, exactly as found in the original source code
+    *          (abbreviated, qualified or unqualified). 
+    * @param   forceTemp
+    *          If <code>true</code>, force a temp-table lookup.
+    * @param   forcePersistent
+    *          If <code>true</code>, force a persistent table lookup.
+    *
+    * @return  The table info instance or <code>null</code> if no match was found.
+    */
+   public TableInfo lookupTableInfo(String name, boolean forceTemp, boolean forcePersistent)
+   {
+      TableInfo result = null;
+      
+      try
+      {
+         if (schemaDict != null)
+         {
+            SchemaHelper<TableInfo> info = (SchemaDictionary dict) -> 
+            {
+               return dict.findTableInfo(name, forceTemp, forcePersistent);
+            };
+               
+            Predicate<TableInfo> notNull = (TableInfo val) -> { return (val == null); };
+               
+            result = processHierarchy(info, notNull);
+         }
+      }
+      catch (SchemaException exc)
+      {
+         exc.printStackTrace();
+      }
+      
+      return result;
+   }
+   
+   /**
     * Look up and return the AST which represents a database field in a schema, given the field's
     * name.
     * 

=== modified file 'src/com/goldencode/p2j/uast/Variable.java'
--- old/src/com/goldencode/p2j/uast/Variable.java	2021-03-23 11:42:56 +0000
+++ new/src/com/goldencode/p2j/uast/Variable.java	2021-04-13 14:17:05 +0000
@@ -4,7 +4,7 @@
 **
 ** Copyright (c) 2005-2021, Golden Code Development Corporation.
 **
-** -#- -I- --Date-- --JPRM-- ----------------Description-----------------
+** -#- -I- --Date-- --JPRM-- ----------------------------------Description----------------------------------
 ** 001 GES 20050210   @19752 First version, simple bean-like approach.
 ** 002 GES 20050330   @20563 Added support for a large number of options
 **                           and helper methods to copy options and write
@@ -80,6 +80,7 @@
 ** 216 CA  20200429          Legacy DEFINE ENUM conversion support.
 ** 217 CA  20200804          Added getExtent().
 ** 218 CA  20210318          Fixed conversion of extent() function/statement with builtin OO properties.
+**     GES 20210413          Added dataset signature annotation processing.  Cleaned up some warnings.
 */
 
 /*
@@ -341,8 +342,6 @@
          Aast kwInit = copy.getImmediateChild(KW_INIT, null);
          if (kwInit != null && kwInit.getNumImmediateChildren() > 1)
          {
-            int size = kwInit.getNumImmediateChildren();
-            
             // Due to a bug in Progress 9.1c (and possibly other versions),
             // a temp-table field or var like some specific element of any
             // extent field/var always picks up the initial value of the
@@ -356,6 +355,8 @@
             Aast initTgt = kwInit.getChildAt(extent);
             if (initTgt == null)
             {
+               int size = kwInit.getNumImmediateChildren();
+               
                // We have more than one initializer value, but not enough
                // to match the index we were given, so use the last
                // available.
@@ -771,9 +772,10 @@
       }
       
       boolean isEnum = ast.getType() == ENUM_VALUE;
+      
       if (tempIdx != -1 && !builtin)
       {
-         ast.putAnnotation("tempidx", new Long(tempIdx));
+         ast.putAnnotation("tempidx", (long) tempIdx);
          if (clsDef != null)
          {
             ast.putAnnotation("tempidx-file", clsDef.getFilename());
@@ -804,7 +806,7 @@
       
       if (builtin)
       {
-         ast.putAnnotation("builtin", new Boolean(true));
+         ast.putAnnotation("builtin", true);
       }
       
       if (!original && clsDef != null)
@@ -815,7 +817,7 @@
       
       if (isProp)
       {
-         ast.putAnnotation("cls-property", new Boolean(true));
+         ast.putAnnotation("cls-property", true);
          ast.putAnnotation("cls-container", clsDef.getFilename());
       }
 
@@ -828,30 +830,51 @@
          // set default access mode (it will be overridden below if there is an
          // explicit mode specified
          access = isProp || isEnum || ast.getType() == DEFINE_EVENT ? KW_PUBLIC : KW_PRIVATE;
-         ast.putAnnotation("access-mode", new Long(access));
+         ast.putAnnotation("access-mode", (long) access);
+      }
+      
+      if (!original && ast.getType() == DATA_SET)
+      {
+         Aast ds = def.getImmediateChild(SYMBOL, null);
+         
+         if (ds == null)
+         {
+            throw new RuntimeException(String.format("Malformed dataset definition %s.", def.dumpTree()));
+         }
+         
+         String sig = (String) ds.getAnnotation("signature");
+         
+         if (sig == null)
+         {
+            throw new RuntimeException(String.format("Missing 'signature' annotation in dataset " +
+                                                     "definition %s.",
+                                                     def.dumpTree()));
+         }
+         
+         ast.putAnnotation("signature", sig);
       }
       
       if (!original && access != -1)
       {
          // access mode matters on the original definition, propagate it here
-         ast.putAnnotation("access-mode", new Long(access));
+         ast.putAnnotation("access-mode", (long) access);
       }
       
       if (isStatic || isEnum)
       {
-         ast.putAnnotation("static", new Boolean(true));
+         ast.putAnnotation("static", true);
       }
       
       if (extent != 0)
       {
-         ast.putAnnotation("extent", new Long(extent));
+         ast.putAnnotation("extent", (long) extent);
       }
       
       if (isEnum)
       {
          def = ast;
          ast.putAnnotation("name", name);
-         ast.putAnnotation("type", new Long(tokenType));
+         ast.putAnnotation("type", (long) tokenType);
          return;
       }
       
@@ -872,7 +895,7 @@
       
       if (tokenType != -1)
       {
-         ast.putAnnotation("type", new Long(tokenType));
+         ast.putAnnotation("type", (long) tokenType);
       }
       
       Aast child        = (Aast) def.getFirstChild();
@@ -908,23 +931,23 @@
             case KW_INPUT:
             case KW_IN_OUT:
             case KW_OUTPUT:
-               ast.putAnnotation("parmtype", new Long(ctype));
+               ast.putAnnotation("parmtype", (long) ctype);
                break;
             case KW_PUBLIC:
             case KW_PROTECTD:
             case KW_PRIVATE:
                // remember the explicit type
                access = ctype;
-               ast.putAnnotation("access-mode", new Long(ctype));
+               ast.putAnnotation("access-mode", (long) ctype);
                break;
             case KW_STATIC:
-               ast.putAnnotation("static", new Boolean(true));
+               ast.putAnnotation("static", true);
                break;
             case KW_ABSTRACT:
-               ast.putAnnotation("abstract", new Boolean(true));
+               ast.putAnnotation("abstract", true);
                break;
             case KW_OVERRIDE:
-               ast.putAnnotation("override", new Boolean(true));
+               ast.putAnnotation("override", true);
                break;
             case KW_FORMAT:
                grandchild = (Aast) child.getFirstChild();
@@ -978,7 +1001,7 @@
                               // save our changes
                               grandchild.setText(gtxt);
                               
-                              d = new Double(gtxt);
+                              d = Double.parseDouble(gtxt);
                            }
                            else if (gtype == HEX_LITERAL)
                            {
@@ -990,11 +1013,11 @@
                               
                               if (gtxt.length() == 0)
                               {
-                                 d = new Double(0);
+                                 d = (double) 0;
                               }
                               else
                               {
-                                 d = new Double(gtxt);
+                                 d = Double.parseDouble(gtxt);
                               }
                            }
                            else if (gtype == BOOL_TRUE || gtype == BOOL_FALSE)
@@ -1002,7 +1025,7 @@
                               // Progress silently accepts this initializer, we
                               // assume it is the same as 0 (no init), but this
                               // is untested at this time.
-                              d = new Double(0);
+                              d = (double) 0;
                            }
                         }
                         
@@ -1019,12 +1042,12 @@
                               // Progress allows decimal initializers for
                               // integers, in this case we must round to the
                               // nearest integer...
-                              Double dd = new Double(gtxt);
-                              l = new Long(Math.round(dd.doubleValue()));
+                              double dd = Double.parseDouble(gtxt);
+                              l = (long) Math.round(dd);
                            }
                            else if (gtype == NUM_LITERAL)
                            {
-                              l = new Long(gtxt);
+                              l = Long.parseLong(gtxt);
                            }
                            else if (gtype == HEX_LITERAL)
                            {
@@ -1036,11 +1059,11 @@
                               
                               if (gtxt.length() == 0)
                               {
-                                 l = new Long(0);
+                                 l = (long) 0;
                               }
                               else
                               {
-                                 l = new Long(gtxt);
+                                 l = Long.parseLong(gtxt);
                               }
                            }
                            else if (gtype == BOOL_TRUE ||
@@ -1049,7 +1072,7 @@
                               // Progress silently accepts this initializer,
                               // we assume it is the same as 0 (no init), but
                               // this is untested at this time.
-                              l = new Long(0);
+                              l = (long) 0;
                            }
                         }
                         
@@ -1142,7 +1165,7 @@
                int prec = ExpressionConversionWorker.literalToInt(child.getChildAt(0));
                if (tokenType == VAR_DEC && prec != 10)
                {
-                  ast.putAnnotation("decimals", new Long(prec));
+                  ast.putAnnotation("decimals", (long) prec);
                }
                break;
             case KW_EXTENT:
@@ -1189,7 +1212,7 @@
          if (ext != 0)
          {
             this.extent = ext;
-            ast.putAnnotation("extent", new Long(ext));
+            ast.putAnnotation("extent", (long) ext);
          }
       }
          
@@ -1290,6 +1313,21 @@
                                  ", format string = " + fmt + ") ");
       }
    }
+
+   /**
+    * Render the instance as text.
+    * 
+    * @return   The details of this instance as text.
+    */
+   public String toString()
+   {
+      return String.format("VARIABLE name %s; token type %d; extent %d; classname %s; AST %s",
+                           name,
+                           tokenType,
+                           extent,
+                           cls,
+                           def == null ? "null" : def.dumpTree());
+   }
    
    /**
     * Helper to copy a child subtree of a specific type from the source to

=== modified file 'src/com/goldencode/p2j/uast/progress.g'
--- old/src/com/goldencode/p2j/uast/progress.g	2021-03-18 19:11:30 +0000
+++ new/src/com/goldencode/p2j/uast/progress.g	2021-04-13 20:41:25 +0000
@@ -2183,6 +2183,9 @@
 **                           ENTITY-EXPANSION-LIMIT and XML-ENTITY-EXPANSION-LIMIT.
 **     EVL 20210305          Adding conversion support for rtf/docx related reports export.
 **     GES 20210318          Added undocumented abbreviation for SERVER-CONNECTION-CONTEXT.
+**     GES 20210413          Added signature calc/annotation to def_dataset_stmt. Reworked the record
+**                           annotations to be more efficient (to use lookupTableInfo()) and to add the
+**                           table signature annotation.
 */
 
 /*
@@ -13714,13 +13717,46 @@
          | KW_SERIALZH
          | KW_REF_ONLY
       )*
-      for_record_spec[false, false, false]
+      f:for_record_spec[false, false, false]
       ( 
          options { generateAmbigWarnings = false; }
          :
            data_relation[idx]       { idx++; }
          | parent_id_relation[pidx] { pidx++; }
       )*
+      {
+         Aast   child     = (Aast) #f.getFirstChild();
+         String signature = "";
+         
+         while (child != null)
+         {
+            int type = child.getType();
+         
+            if (type == BUFFER || type == TEMP_TABLE)
+            {
+               String s = (String) child.getAnnotation("signature");
+               
+               if (s == null)
+               {
+                  String err = String.format("Missing 'signature' for record: %s", child.dumpTree());
+                  throw new RuntimeException(err);
+               }
+               
+               signature += "<" + s + ">";
+            }
+            
+            child = (Aast) child.getNextSibling();
+         }
+         
+         if (signature.length() == 0)
+         {
+            String err2 = String.format("Missing 'signature' for dataset: %s", #ds.dumpTree());
+            throw new RuntimeException(err2);
+         }
+         
+         // annotate the SYMBOL node in the definition (will be the first child of DEFINE_DATASET) 
+         #d.putAnnotation("signature", signature);
+      }
    ;
    
 /**
@@ -26815,27 +26851,33 @@
       {
          // now store an annotation with the fully qualified name and the
          // shorted unabbreviated unique buffer name
-         String fullname = sym.lookupFullTableName(tablename, forceTemp, forcePersistent);
-         String bufname  = sym.lookupBufferName(tablename, false, forceTemp, forcePersistent);
-         String dbname   = sym.lookupDatabaseName(tablename, forceTemp, forcePersistent);
+         TableInfo info = sym.lookupTableInfo(tablename, forceTemp, forcePersistent);
                   
-         if (fullname != null)         
-            ##.putAnnotation("schemaname", fullname);
-            
-         if (bufname != null)
-            ##.putAnnotation("bufname", bufname);
-            
-         if (dbname != null)
-            ##.putAnnotation("dbname", dbname);
-            
-         // store the backing construct (this will be the same as the token
-         // type for every record type except buffers, in which case it will
-         // be TABLE, TEMP_TABLE or WORK_TABLE)
-         int backing = sym.lookupTableRecordType(tablename, forceTemp, forcePersistent);
-         
-         if (backing != -1)
+         if (info != null)
          {
-            ##.putAnnotation("recordtype", new Long(backing));
+            String sig      = info.getSignature();
+            String fullname = info.getQualified();
+            String bufname  = info.getBuffer();
+            String dbname   = info.getDatabase();
+            int    backing  = info.getRecordType();
+            
+            if (sig != null)
+               ##.putAnnotation("signature", sig);
+            
+            if (fullname != null)
+               ##.putAnnotation("schemaname", fullname);
+            
+            if (bufname != null)
+               ##.putAnnotation("bufname", bufname);
+               
+            if (dbname != null)
+               ##.putAnnotation("dbname", dbname);
+               
+            // store the backing construct (this will be the same as the token
+            // type for every record type except buffers, in which case it will
+            // be TABLE, TEMP_TABLE or WORK_TABLE)
+            if (backing != -1)
+               ##.putAnnotation("recordtype", new Long(backing));
          }
       }
    ;

