4350_fuzzy_matching_changes_20210415.patch
new/src/com/goldencode/p2j/schema/SchemaDictionary.java 2021-04-13 20:30:32 +0000 | ||
---|---|---|
499 | 499 |
** CA 20201011 Scope.promoted changed to an identity hash set. |
500 | 500 |
** CA 20201015 Replaced java.util.Stack with a non-synchronized custom implementation. |
501 | 501 |
** AIL 20210309 Clean local schema dictionary instances. |
502 |
** GES 20210208 Added table-level signature calculation (saved as an annotation). Added |
|
503 |
** findTableInfo() to provide a set of related data about a matched record. This |
|
504 |
** saves multiple calls each having to resolve the same name node lookup. |
|
502 | 505 |
*/ |
503 | 506 | |
504 | 507 |
/* |
... | ... | |
1765 | 1768 |
{ |
1766 | 1769 |
copyTableProperties(ast, table); |
1767 | 1770 |
} |
1771 |
|
|
1772 |
// calculate and save the table signature |
|
1773 |
calculateSignature(table); |
|
1768 | 1774 | |
1769 | 1775 |
if (modelTable == null) |
1770 | 1776 |
{ |
... | ... | |
1916 | 1922 |
} |
1917 | 1923 |
|
1918 | 1924 |
/** |
1925 |
* Get the Progress parser token type of a table, temp-table, work-table, |
|
1926 |
* or buffer. If <code>name</code> represents a buffer, the type of the |
|
1927 |
* backing record is returned. That is, a type of <code>BUFFER</code> is |
|
1928 |
* never returned, only the type of the record which backs the buffer. |
|
1929 |
* <p> |
|
1930 |
* If both forceTemp and forcePersistent are <code>false</code>, then the lookup is performed |
|
1931 |
* with no preference for temp-table or persistent table. |
|
1932 |
* |
|
1933 |
* @param name |
|
1934 |
* Case insensitive record name; may be qualified or not. If |
|
1935 |
* qualified, only the table portion of the name may be |
|
1936 |
* abbreviated. |
|
1937 |
* @param forceTemp |
|
1938 |
* If <code>true</code>, force a temp-table lookup. |
|
1939 |
* @param forcePersistent |
|
1940 |
* If <code>true</code>, force a persistent table lookup. |
|
1941 |
* |
|
1942 |
* @return One of the Progress parser token types <code>TABLE</code>, |
|
1943 |
* <code>TEMP_TABLE</code>, or <code>WORK_TABLE</code>, or |
|
1944 |
* <code>UNKNOWN</code> if <code>name</code> cannot be found. |
|
1945 |
* |
|
1946 |
* @throws AmbiguousSchemaNameException |
|
1947 |
* if <code>name</code> is an ambiguous identifier for the |
|
1948 |
* target record. |
|
1949 |
*/ |
|
1950 |
public TableInfo findTableInfo(String name, boolean forceTemp, boolean forcePersistent) |
|
1951 |
throws AmbiguousSchemaNameException |
|
1952 |
{ |
|
1953 |
// TODO: The recordType lookup used scopes.size() - 1 instead of nearestEnclosingInternal(), is this |
|
1954 |
// an issue? It seems like it should have been consistent with the other lookups, so this is |
|
1955 |
// being made consistent here. |
|
1956 |
int l1 = computeLevel(forceTemp, forcePersistent, true, nearestEnclosingInternal()); |
|
1957 |
int l2 = computeLevel(forceTemp, forcePersistent, false, SCHEMA_GLOBAL_SCOPE); |
|
1958 | ||
1959 |
// Look up record name node. |
|
1960 |
NameNode node = findEntry(name, EntityName.TABLE, l1, l2, false); |
|
1961 |
|
|
1962 |
TableInfo info = null; |
|
1963 |
|
|
1964 |
if (node != null) |
|
1965 |
{ |
|
1966 |
NameNode dbNode = node.getParent(); |
|
1967 |
|
|
1968 |
// don't just use getAst() here, we need the AST for the backing table if this is a buffer |
|
1969 |
// (this will work for both the simple case and the buffer case) |
|
1970 |
Aast ast = getTable(node); |
|
1971 |
String sig = ast != null ? (String) ast.getAnnotation("signature") : null; |
|
1972 |
String sname = getQualifiedName(node); |
|
1973 |
String bname = getRecordName(node, EntityName.TABLE, name); |
|
1974 |
String dname = (dbNode != null) ? dbNode.getName() : ""; |
|
1975 |
int ttype = getTable(node).getType(); |
|
1976 |
|
|
1977 |
info = new TableInfo(sig, sname, bname, dname, ttype); |
|
1978 |
} |
|
1979 |
|
|
1980 |
return info; |
|
1981 |
} |
|
1982 |
|
|
1983 |
/** |
|
1919 | 1984 |
* Get an object which exposes schema-related information about a field which is referenced in |
1920 | 1985 |
* source code. |
1921 | 1986 |
* |
... | ... | |
4908 | 4973 |
} |
4909 | 4974 |
|
4910 | 4975 |
/** |
4976 |
* Calculate and save the signature for the table. Each field is represented by "datatype" or |
|
4977 |
* "datatype[extent]" format. The table level signature is all of the field signatures separated |
|
4978 |
* by "|" delimiter with the number and order of the fields matching the order in the AST. |
|
4979 |
* |
|
4980 |
* @param table |
|
4981 |
* The table AST substree. |
|
4982 |
*/ |
|
4983 |
private void calculateSignature(Aast table) |
|
4984 |
{ |
|
4985 |
String signature = ""; |
|
4986 |
|
|
4987 |
Aast next = (Aast) table.getFirstChild(); |
|
4988 | ||
4989 |
// iterate through all fields |
|
4990 |
while (next != null) |
|
4991 |
{ |
|
4992 |
int type = next.getType(); |
|
4993 |
|
|
4994 |
// if this is a field, calculate the field's portion of the signature |
|
4995 |
if (type >= BEGIN_FIELDTYPES && type <= END_FIELDTYPES || |
|
4996 |
type >= BEGIN_METATYPES && type <= END_METATYPES) |
|
4997 |
{ |
|
4998 |
Aast ext = next.getImmediateChild(KW_EXTENT, null); |
|
4999 |
|
|
5000 |
String extent = null; |
|
5001 |
|
|
5002 |
// read the extent value if any |
|
5003 |
if (ext != null) |
|
5004 |
{ |
|
5005 |
if (ext.getNumImmediateChildren() == 0) |
|
5006 |
{ |
|
5007 |
String err = String.format("Indeterminate EXTENT in a temp-table definition for %s", |
|
5008 |
next.dumpTree()); |
|
5009 |
|
|
5010 |
// indeterminate extent does not make sense for a temp-table, should not happen |
|
5011 |
throw new RuntimeException(err); |
|
5012 |
} |
|
5013 |
else |
|
5014 |
{ |
|
5015 |
// the only child is a NUM_LITERAL |
|
5016 |
Aast num = (Aast) ext.getFirstChild(); |
|
5017 |
extent = num.getText(); |
|
5018 |
} |
|
5019 |
} |
|
5020 |
|
|
5021 |
// create the field portion of the signature |
|
5022 |
String fsig = SchemaParser.fieldSignature(type, extent); |
|
5023 |
|
|
5024 |
// add it in to the table signature |
|
5025 |
signature += (signature.length() == 0) ? fsig : "|" + fsig; |
|
5026 |
} |
|
5027 | ||
5028 |
next = (Aast) next.getNextSibling(); |
|
5029 |
} |
|
5030 | ||
5031 |
// save the result |
|
5032 |
if (signature.isEmpty()) |
|
5033 |
{ |
|
5034 |
// there should always be at least 1 field |
|
5035 |
throw new RuntimeException(String.format("Invalid temp-table, no fields!\n%s", table.dumpTree())); |
|
5036 |
} |
|
5037 |
else |
|
5038 |
{ |
|
5039 |
table.putAnnotation("signature", signature); |
|
5040 |
} |
|
5041 |
} |
|
5042 |
|
|
5043 |
/** |
|
4911 | 5044 |
* Copy TABLE properties from a source AST to a destination AST. The set |
4912 | 5045 |
* of properties (essentially child AST nodes) copied will be limited to |
4913 | 5046 |
* those that are valid in a table (filtered by token type). It is assumed |
new/src/com/goldencode/p2j/schema/TableInfo.java 2021-04-09 17:19:41 +0000 | ||
---|---|---|
1 |
/* |
|
2 |
** Module : TableInfo.java |
|
3 |
** Abstract : Schema information about a database field. |
|
4 |
** |
|
5 |
** Copyright (c) 2021, Golden Code Development Corporation. |
|
6 |
** |
|
7 |
** -#- -I- --Date-- --------------------------------Description---------------------------------- |
|
8 |
** 001 GES 20210224 Created initial version. |
|
9 |
*/ |
|
10 | ||
11 |
/* |
|
12 |
** This program is free software: you can redistribute it and/or modify |
|
13 |
** it under the terms of the GNU Affero General Public License as |
|
14 |
** published by the Free Software Foundation, either version 3 of the |
|
15 |
** License, or (at your option) any later version. |
|
16 |
** |
|
17 |
** This program is distributed in the hope that it will be useful, |
|
18 |
** but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
19 |
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
20 |
** GNU Affero General Public License for more details. |
|
21 |
** |
|
22 |
** You may find a copy of the GNU Affero GPL version 3 at the following |
|
23 |
** location: https://www.gnu.org/licenses/agpl-3.0.en.html |
|
24 |
** |
|
25 |
** Additional terms under GNU Affero GPL version 3 section 7: |
|
26 |
** |
|
27 |
** Under Section 7 of the GNU Affero GPL version 3, the following additional |
|
28 |
** terms apply to the works covered under the License. These additional terms |
|
29 |
** are non-permissive additional terms allowed under Section 7 of the GNU |
|
30 |
** Affero GPL version 3 and may not be removed by you. |
|
31 |
** |
|
32 |
** 0. Attribution Requirement. |
|
33 |
** |
|
34 |
** You must preserve all legal notices or author attributions in the covered |
|
35 |
** work or Appropriate Legal Notices displayed by works containing the covered |
|
36 |
** work. You may not remove from the covered work any author or developer |
|
37 |
** credit already included within the covered work. |
|
38 |
** |
|
39 |
** 1. No License To Use Trademarks. |
|
40 |
** |
|
41 |
** This license does not grant any license or rights to use the trademarks |
|
42 |
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks |
|
43 |
** of Golden Code Development Corporation. You are not authorized to use the |
|
44 |
** name Golden Code, FWD, or the names of any author or contributor, for |
|
45 |
** publicity purposes without written authorization. |
|
46 |
** |
|
47 |
** 2. No Misrepresentation of Affiliation. |
|
48 |
** |
|
49 |
** You may not represent yourself as Golden Code Development Corporation or FWD. |
|
50 |
** |
|
51 |
** You may not represent yourself for publicity purposes as associated with |
|
52 |
** Golden Code Development Corporation, FWD, or any author or contributor to |
|
53 |
** the covered work, without written authorization. |
|
54 |
** |
|
55 |
** 3. No Misrepresentation of Source or Origin. |
|
56 |
** |
|
57 |
** You may not represent the covered work as solely your work. All modified |
|
58 |
** versions of the covered work must be marked in a reasonable way to make it |
|
59 |
** clear that the modified work is not originating from Golden Code Development |
|
60 |
** Corporation or FWD. All modified versions must contain the notices of |
|
61 |
** attribution required in this license. |
|
62 |
*/ |
|
63 | ||
64 |
package com.goldencode.p2j.schema; |
|
65 | ||
66 |
/** |
|
67 |
* A read-only bean which exposes schema-related information associated with a database table |
|
68 |
* which is referenced in source code. |
|
69 |
*/ |
|
70 |
public class TableInfo |
|
71 |
{ |
|
72 |
/** Table signature associated with the table. */ |
|
73 |
private final String signature; |
|
74 |
|
|
75 |
/** Fully qualified schema name of the table. */ |
|
76 |
private final String qualified; |
|
77 |
|
|
78 |
/** Table's buffer name as indicated in source. */ |
|
79 |
private final String buffer; |
|
80 |
|
|
81 |
/** Table's database logical name or alias. */ |
|
82 |
private final String database; |
|
83 |
|
|
84 |
/** Table's token type. */ |
|
85 |
private final int recordType; |
|
86 |
|
|
87 |
/** |
|
88 |
* Constructor. |
|
89 |
* |
|
90 |
* @param signature |
|
91 |
* Structural signature for the table. |
|
92 |
* @param qualified |
|
93 |
* Fully qualified schema name of the table. |
|
94 |
* @param buffer |
|
95 |
* Name of the buffer indicated by the table reference in source code. |
|
96 |
* @param database |
|
97 |
* Name of the database which contains the table, as specified by the table reference |
|
98 |
* in source code, or looked up if the source reference was unqualified. |
|
99 |
* @param recordType |
|
100 |
* Token type of the table backing the buffer indicated by the table reference in |
|
101 |
* source code. |
|
102 |
*/ |
|
103 |
TableInfo(String signature, String qualified, String buffer, String database, int recordType) |
|
104 |
{ |
|
105 |
this.signature = signature; |
|
106 |
this.qualified = qualified; |
|
107 |
this.buffer = buffer; |
|
108 |
this.database = database; |
|
109 |
this.recordType = recordType; |
|
110 |
} |
|
111 |
|
|
112 |
/** |
|
113 |
* Get the signature of the table in the schema. |
|
114 |
* |
|
115 |
* @return Table signature. |
|
116 |
*/ |
|
117 |
public String getSignature() |
|
118 |
{ |
|
119 |
return signature; |
|
120 |
} |
|
121 |
|
|
122 |
/** |
|
123 |
* Get the fully qualified schema name of the field. |
|
124 |
* |
|
125 |
* @return Qualified field name. |
|
126 |
*/ |
|
127 |
public String getQualified() |
|
128 |
{ |
|
129 |
return qualified; |
|
130 |
} |
|
131 |
|
|
132 |
/** |
|
133 |
* Get the buffer name associated with the field. |
|
134 |
* |
|
135 |
* @return Buffer name. |
|
136 |
*/ |
|
137 |
public String getBuffer() |
|
138 |
{ |
|
139 |
return buffer; |
|
140 |
} |
|
141 |
|
|
142 |
/** |
|
143 |
* Get the logical database name or alias, if any, associated with the field. |
|
144 |
* |
|
145 |
* @return The logical name of the database associated with the buffer associated with the |
|
146 |
* field. An empty string is returned if the containing table is a temp- or |
|
147 |
* work-table. |
|
148 |
*/ |
|
149 |
public String getDatabase() |
|
150 |
{ |
|
151 |
return database; |
|
152 |
} |
|
153 |
|
|
154 |
/** |
|
155 |
* Get the Progress parser token type of a table, temp-table, work-table, or buffer which |
|
156 |
* contains the field. If the record containing the field is a buffer, the type of the backing |
|
157 |
* record is returned. That is, a type of {@code BUFFER} is never returned, only the type of |
|
158 |
* the record which backs the buffer. |
|
159 |
* |
|
160 |
* @return Record token type. |
|
161 |
*/ |
|
162 |
public int getRecordType() |
|
163 |
{ |
|
164 |
return recordType; |
|
165 |
} |
|
166 |
} |
new/src/com/goldencode/p2j/schema/schema.g 2021-02-08 21:46:29 +0000 | ||
---|---|---|
2 | 2 |
** Module : schema.g |
3 | 3 |
** Abstract : Progress database schema dump file (.df) parser |
4 | 4 |
** |
5 |
** Copyright (c) 2004-2019, Golden Code Development Corporation.
|
|
5 |
** Copyright (c) 2004-2021, Golden Code Development Corporation.
|
|
6 | 6 |
** |
7 | 7 |
** -#- -I- --Date-- --JPRM-- ----------------------------Description----------------------------- |
8 | 8 |
** 001 ECF 20041130 @18769 First version implementing a complete lexer, |
... | ... | |
99 | 99 |
** 037 OM 20200530 Fix for datetime[tz]. |
100 | 100 |
** 038 CA 20200930 Use factory.setASTNodeClass(Class) instead of setASTNodeClass(String), to avoid |
101 | 101 |
** loadClass call. |
102 |
** GES 20210208 Added table-level signature calculation and it is saved as an annotation. |
|
102 | 103 |
*/ |
103 | 104 | |
104 | 105 |
/* |
... | ... | |
161 | 162 |
** SchemaParserTokenTypes.java |
162 | 163 |
** Abstract : Progress database schema dump file (.df) parser |
163 | 164 |
** |
164 |
** Copyright (c) 2004-2016, Golden Code Development Corporation.
|
|
165 |
** Copyright (c) 2004-2021, Golden Code Development Corporation.
|
|
165 | 166 |
** |
166 | 167 |
** -#- -I- --Date-- --JPRM-- ----------------------------Description----------------------------- |
167 | 168 |
** 001 ECF 20041130 @18769 WARNING, THIS IS A GENERATED FILE!!! DO NOT EDIT THIS FILE. The |
... | ... | |
241 | 242 |
} |
242 | 243 |
|
243 | 244 |
/** |
245 |
* Calculate the signature string for a field. |
|
246 |
* |
|
247 |
* @param type |
|
248 |
* The field's data type. |
|
249 |
* @param extent |
|
250 |
* The extent size (as a num literal) or {@code null} if the field is scalar. |
|
251 |
* |
|
252 |
* @return The signature in "datatype" or "datatype[extent]" format. |
|
253 |
*/ |
|
254 |
public static String fieldSignature(int type, String extent) |
|
255 |
{ |
|
256 |
String code = null; |
|
257 |
|
|
258 |
switch (type) |
|
259 |
{ |
|
260 |
case FIELD_BLOB: |
|
261 |
code = "blob"; |
|
262 |
break; |
|
263 |
case FIELD_CHAR: |
|
264 |
code = "char"; |
|
265 |
break; |
|
266 |
case FIELD_CLOB: |
|
267 |
code = "clob"; |
|
268 |
break; |
|
269 |
case FIELD_COM_HANDLE: |
|
270 |
code = "comh"; |
|
271 |
break; |
|
272 |
case FIELD_DATE: |
|
273 |
code = "date"; |
|
274 |
break; |
|
275 |
case FIELD_DATETIME: |
|
276 |
code = "dt"; |
|
277 |
break; |
|
278 |
case FIELD_DATETIME_TZ: |
|
279 |
code = "dttz"; |
|
280 |
break; |
|
281 |
case FIELD_DEC: |
|
282 |
code = "dec"; |
|
283 |
break; |
|
284 |
case FIELD_INT: |
|
285 |
code = "int"; |
|
286 |
break; |
|
287 |
case FIELD_INT64: |
|
288 |
code = "i64"; |
|
289 |
break; |
|
290 |
case FIELD_LOGICAL: |
|
291 |
code = "log"; |
|
292 |
break; |
|
293 |
case FIELD_RECID: |
|
294 |
code = "rec"; |
|
295 |
break; |
|
296 |
case FIELD_ROWID: |
|
297 |
code = "row"; |
|
298 |
break; |
|
299 |
case FIELD_RAW: |
|
300 |
code = "raw"; |
|
301 |
break; |
|
302 |
case FIELD_HANDLE: |
|
303 |
code = "hndl"; |
|
304 |
break; |
|
305 |
case FIELD_BIGINT: |
|
306 |
code = "bigi"; |
|
307 |
break; |
|
308 |
case FIELD_BYTE: |
|
309 |
code = "byte"; |
|
310 |
break; |
|
311 |
case FIELD_DOUBLE: |
|
312 |
code = "dbl"; |
|
313 |
break; |
|
314 |
case FIELD_FIXCHAR: |
|
315 |
code = "fixc"; |
|
316 |
break; |
|
317 |
case FIELD_FLOAT: |
|
318 |
code = "flt"; |
|
319 |
break; |
|
320 |
case FIELD_SHORT: |
|
321 |
code = "shrt"; |
|
322 |
break; |
|
323 |
case FIELD_TIMESTAMP: |
|
324 |
code = "tmst"; |
|
325 |
break; |
|
326 |
case FIELD_TIME: |
|
327 |
code = "time"; |
|
328 |
break; |
|
329 |
default: |
|
330 |
code = "unkw"; |
|
331 |
break; |
|
332 |
} |
|
333 |
|
|
334 |
return (extent == null) ? code : code + "[" + extent + "]"; |
|
335 |
} |
|
336 |
|
|
337 |
/** |
|
244 | 338 |
* Configure the hidden filter to ensure that token types that are not |
245 | 339 |
* normally recognized by the parser, are hidden (but still accessible) |
246 | 340 |
* using the filter. |
... | ... | |
700 | 794 |
*/ |
701 | 795 |
table |
702 | 796 |
: |
703 |
{ astFactory.makeASTRoot(currentAST, #[TABLE]); } |
|
797 |
{ |
|
798 |
astFactory.makeASTRoot(currentAST, #[TABLE]); |
|
799 |
String signature = ""; |
|
800 |
String fsig = null; |
|
801 |
} |
|
704 | 802 |
( |
705 | 803 |
KW_ADD! tab:KW_TABLE! s:STRING! (y:KW_TYPE! d:SYMBOL!)? |
706 | 804 |
{ |
... | ... | |
723 | 821 |
( |
724 | 822 |
options { generateAmbigWarnings = false; } |
725 | 823 |
: |
726 |
field |
|
824 |
fsig=field |
|
825 |
{ |
|
826 |
signature += (signature.length() == 0) ? fsig : "|" + fsig; |
|
827 |
} |
|
727 | 828 |
| index |
728 | 829 |
)* |
729 | 830 |
) |
730 | 831 |
{ |
731 | 832 |
currentTable = null; |
732 | 833 |
forceLineColumnNums(#tab, ##); |
834 |
##.putAnnotation("signature", signature); |
|
733 | 835 |
} |
734 | 836 |
; |
735 | 837 |
|
... | ... | |
804 | 906 |
* definition would always be a child of the most recently parsed table |
805 | 907 |
* definition. |
806 | 908 |
* |
807 |
* @throws SemanticException |
|
808 |
* if this field definition reports a different parent table name |
|
809 |
* from the table definition most recently parsed. |
|
909 |
* @return The signature for this field in "datatype" or "datatype[extent]" format. |
|
910 |
* |
|
911 |
* @throws SemanticException |
|
912 |
* if this field definition reports a different parent table name |
|
913 |
* from the table definition most recently parsed. |
|
810 | 914 |
*/ |
811 |
field |
|
915 |
field returns [String sig = null]
|
|
812 | 916 |
: |
813 | 917 |
{ |
814 | 918 |
int ftype; |
815 | 919 |
String fmt = null; |
920 |
String ext = null; |
|
816 | 921 |
} |
817 | 922 |
( |
818 | 923 |
KW_ADD! f:KW_FIELD^ s:STRING! |
... | ... | |
850 | 955 |
| field_trigger |
851 | 956 |
| help |
852 | 957 |
| help_sa |
853 |
| extent |
|
958 |
| ext=extent
|
|
854 | 959 |
| decimals |
855 | 960 |
| length |
856 | 961 |
| viewAs |
... | ... | |
876 | 981 |
| unknownOption! |
877 | 982 |
)* |
878 | 983 |
) |
984 |
{ |
|
985 |
sig = fieldSignature(ftype, ext); |
|
986 |
} |
|
879 | 987 |
; |
880 | 988 |
|
881 | 989 |
/** |
... | ... | |
1736 | 1844 |
* <p> |
1737 | 1845 |
* The resulting sub-tree is the <code>KW_EXTENT</code> with a child of |
1738 | 1846 |
* <code>NUM_LITERAL</code>. |
1847 |
* |
|
1848 |
* @return The size of the extent. |
|
1739 | 1849 |
*/ |
1740 |
extent |
|
1850 |
extent returns [String num = null]
|
|
1741 | 1851 |
: |
1742 |
KW_EXTENT^ NUM_LITERAL
|
|
1852 |
KW_EXTENT^ n:NUM_LITERAL { num = #n.getText(); }
|
|
1743 | 1853 |
; |
1744 | 1854 |
|
1745 | 1855 |
/** |
new/src/com/goldencode/p2j/uast/ClassDefinition.java 2021-04-15 14:18:30 +0000 | ||
---|---|---|
1 | 1 |
/* |
2 |
** Module : ClassDefinition.java |
|
3 | 2 |
** Abstract : defines the 4GL API for a class or interface definition |
4 | 3 |
** |
5 | 4 |
** Copyright (c) 2007-2021, Golden Code Development Corporation. |
... | ... | |
100 | 99 |
** CA 20210305 For builtin OO method calls, save at the call the typelist for the target method |
101 | 100 |
** parameters. |
102 | 101 |
** CA 20210318 Fixed conversion of extent() function/statement with builtin OO properties. |
102 |
** GES 20210311 Rewrote exact and fuzzy method matching to support all data types and |
|
103 |
** overloading rules. Fixed many latent problems. |
|
103 | 104 |
*/ |
104 | 105 | |
105 | 106 |
/* |
... | ... | |
160 | 161 |
import java.lang.reflect.*; |
161 | 162 |
import java.util.*; |
162 | 163 |
import java.util.function.*; |
164 |
import java.util.stream.*; |
|
163 | 165 |
import com.goldencode.ast.*; |
164 | 166 |
import com.goldencode.p2j.convert.*; |
165 | 167 |
import com.goldencode.p2j.convert.db.*; |
... | ... | |
189 | 191 |
public class ClassDefinition |
190 | 192 |
implements ProgressParserTokenTypes |
191 | 193 |
{ |
192 |
/** |
|
193 |
* Flag to indicate whether tracing is ON. |
|
194 |
*/ |
|
195 |
public static final boolean nowrapObjects = System.getProperty("nowrap.objects") != null; |
|
196 | ||
197 | 194 |
/** Data store identifier. */ |
198 | 195 |
protected static enum DataStoreType { VAR, METHOD, TABLE, QRY, DATASET, DATASRC }; |
199 | 196 | |
... | ... | |
650 | 647 |
this.interfaces = interfaces.isEmpty() ? null : Collections.unmodifiableSet(interfaces); |
651 | 648 |
this.parents = parents.isEmpty() |
652 | 649 |
? null |
653 |
: (ClassDefinition[]) parents.toArray(new ClassDefinition[0]);
|
|
650 |
: parents.toArray(new ClassDefinition[0]); |
|
654 | 651 |
} |
655 | 652 |
|
656 | 653 |
/** |
... | ... | |
1413 | 1410 |
{ |
1414 | 1411 |
// this can happen if we are referencing this class via a USING from an USING defined |
1415 | 1412 |
// in this file... in this case, inherit the tempidx, but show an warning. |
1416 |
MemberData dat = defs.get(new SignatureKey(sig)); |
|
1417 | 1413 |
|
1418 | 1414 |
// GES_TODO: re-enable this |
1419 | 1415 |
// System.out.printf("\nERROR: duplicate method definition!\n"); |
1420 | 1416 |
// System.out.printf("EXISTING %s\n", dat.toString()); |
1421 |
// System.out.printf("DUPLICATE %s\n at %s", mdat.toString(), node.dumpTree());
|
|
1417 |
// System.out.printf("DUPLICATE %s\n at %s", prevMdat.toString(), node.dumpTree());
|
|
1422 | 1418 |
|
1423 | 1419 |
// return; |
1424 | 1420 |
} |
... | ... | |
1461 | 1457 |
if (node != null) |
1462 | 1458 |
{ |
1463 | 1459 |
node.putAnnotation("name", name); |
1464 |
node.putAnnotation("return_type", new Long(type));
|
|
1460 |
node.putAnnotation("return_type", (long) type);
|
|
1465 | 1461 |
node.putAnnotation("signature", renderSignature(name, sig)); |
1466 |
node.putAnnotation("access-mode", new Long(access));
|
|
1467 |
node.putAnnotation("static", new Boolean(isStatic));
|
|
1462 |
node.putAnnotation("access-mode", (long) access);
|
|
1463 |
node.putAnnotation("static", isStatic);
|
|
1468 | 1464 |
node.putAnnotation("oo-data-store", DataStoreType.METHOD.toString()); |
1469 |
node.putAnnotation("tempidx", new Long(mTempIdx));
|
|
1465 |
node.putAnnotation("tempidx", (long) mTempIdx);
|
|
1470 | 1466 |
node.putAnnotation("tempidx-file", this.filename); |
1471 | 1467 |
|
1472 | 1468 |
if (extent != 0) |
1473 | 1469 |
{ |
1474 |
node.putAnnotation("extent", new Long(extent));
|
|
1470 |
node.putAnnotation("extent", (long) extent);
|
|
1475 | 1471 |
} |
1476 | 1472 |
|
1477 | 1473 |
if (ret != null && ret.isAnnotation("qualified")) |
... | ... | |
1570 | 1566 |
if (node != null) |
1571 | 1567 |
{ |
1572 | 1568 |
// go ahead and annotate the chp_wrapper right now too |
1573 |
MemberData mdat = lookupMethodWorker(mname, signature, access, isStatic, internal, node); |
|
1569 |
MethodSearchResult found = lookupMethodWorker(mname, signature, access, isStatic, internal, node); |
|
1570 |
MemberData mdat = (found == null) ? null : found.data; |
|
1574 | 1571 |
|
1575 | 1572 |
if (mdat != null) |
1576 | 1573 |
{ |
... | ... | |
1593 | 1590 |
} |
1594 | 1591 |
|
1595 | 1592 |
// the access mode won't necessarily be the same as that passed in |
1596 |
node.putAnnotation("access-mode", new Long(mdat.access));
|
|
1597 |
node.putAnnotation("static", new Boolean(mdat.isStatic));
|
|
1593 |
node.putAnnotation("access-mode", (long) mdat.access);
|
|
1594 |
node.putAnnotation("static", mdat.isStatic);
|
|
1598 | 1595 |
if (mdat.modes != null && !mdat.modes.isEmpty()) |
1599 | 1596 |
{ |
1600 | 1597 |
node.putAnnotation("param_modes", mdat.modes); |
1601 | 1598 |
} |
1602 | 1599 |
|
1603 |
node.putAnnotation("tempidx", new Long(mdat.tempIdx));
|
|
1600 |
node.putAnnotation("tempidx", (long) mdat.tempIdx);
|
|
1604 | 1601 |
node.putAnnotation("tempidx-file", mdat.container.filename); |
1605 | 1602 |
|
1606 | 1603 |
node.putAnnotation("found-in-cls", mdat.container.name); |
... | ... | |
1612 | 1609 |
|
1613 | 1610 |
if (mdat.extent != 0) |
1614 | 1611 |
{ |
1615 |
node.putAnnotation("extent", new Long(mdat.extent));
|
|
1612 |
node.putAnnotation("extent", (long) mdat.extent);
|
|
1616 | 1613 |
} |
1617 | 1614 |
|
1618 | 1615 |
if (mdat.container.builtin && !mdat.container.dotnet) |
... | ... | |
1625 | 1622 |
} |
1626 | 1623 |
} |
1627 | 1624 |
|
1628 |
annotateCallSignature(node, signature, mdat.signature); |
|
1625 |
annotateCallSignature(node, signature, mdat.signature, found.getOverrides());
|
|
1629 | 1626 |
node.putAnnotation("signature", renderSignature(mdat.name, mdat.signature)); |
1630 | 1627 |
if (isBuiltIn()) |
1631 | 1628 |
{ |
... | ... | |
1658 | 1655 |
} |
1659 | 1656 |
else |
1660 | 1657 |
{ |
1661 |
// check if there are POLY arguments (BaseDataType signature); if so, mark this call |
|
1662 |
// with 'dynamic', so conversion rules can emit a dynamic call for it |
|
1663 |
boolean dynamic = false; |
|
1664 |
for (ParameterKey key : signature) |
|
1665 |
{ |
|
1666 |
if ("BaseDataType".equals(key.type)) |
|
1667 |
{ |
|
1668 |
dynamic = true; |
|
1669 |
break; |
|
1670 |
} |
|
1671 |
} |
|
1672 |
|
|
1673 |
if (dynamic) |
|
1658 |
// dynamic cases will have a non-null result; this may be for table-handle parms, dataset-handle |
|
1659 |
// parms or POLY parms |
|
1660 |
if (found != null && found.isDynamic()) |
|
1674 | 1661 |
{ |
1675 | 1662 |
node.putAnnotation("dynamic", true); |
1676 | 1663 |
if (isStatic) |
... | ... | |
2426 | 2413 |
{ |
2427 | 2414 |
if (vdata.var.isProp()) |
2428 | 2415 |
{ |
2429 |
String datatype = SymbolResolver.translateType(vdata.type, null); |
|
2430 |
if ("object".equals(datatype)) |
|
2431 |
{ |
|
2432 |
datatype = String.format("object <? extends %s>", vdata.qname); |
|
2433 |
} |
|
2434 |
int extent = vdata.var.getExtent(); |
|
2435 |
String ext = extent == 0 |
|
2436 |
? "" |
|
2437 |
: "[" + (extent > 0 ? Integer.toString(extent) : "") +"]"; |
|
2416 |
String datatype = SymbolResolver.translateType(vdata.type, null, vdata.qname); |
|
2417 |
|
|
2418 |
|
|
2419 |
int extent = vdata.var.getExtent(); |
|
2420 |
String ext = (extent == 0) ? "" : "[" + (extent > 0 ? Integer.toString(extent) : "") +"]"; |
|
2438 | 2421 | |
2439 | 2422 |
// add property methods |
2440 | 2423 |
String[] prefixes = extent != 0 ? extPrefixes : propPrefixes; |
... | ... | |
2684 | 2667 |
* The OO_METH_* node whose parameters will be annotated. |
2685 | 2668 |
* @param callSig |
2686 | 2669 |
* The array of parameter types that is the method definition signature. |
2670 |
* @param defSig |
|
2671 |
* The array of parameter types that is the method definition signature. |
|
2672 |
* @param overrides |
|
2673 |
* Type overrides found by any fuzzy matching for this method. |
|
2687 | 2674 |
*/ |
2688 |
private void annotateCallSignature(Aast call, ParameterKey[] callSig, ParameterKey[] defSig) |
|
2675 |
private void annotateCallSignature(Aast call, |
|
2676 |
ParameterKey[] callSig, |
|
2677 |
ParameterKey[] defSig, |
|
2678 |
String[] overrides) |
|
2689 | 2679 |
{ |
2690 | 2680 |
int idx = 0; |
2691 | 2681 |
|
... | ... | |
2884 | 2874 |
{ |
2885 | 2875 |
if (parm.getType() == PARAMETER) |
2886 | 2876 |
{ |
2887 |
parm.putAnnotation("wrap_parameter", new Boolean(true));
|
|
2877 |
parm.putAnnotation("wrap_parameter", true);
|
|
2888 | 2878 |
} |
2889 | 2879 |
} |
2890 |
else if ((!nowrapObjects || !classname.startsWith("object<")) &&
|
|
2880 |
else if (!classname.startsWith("object<") &&
|
|
2891 | 2881 |
!classname.startsWith("jobject<")) |
2892 | 2882 |
{ |
2893 |
// TODO: CA: what's the reas for nowrapObjects? |
|
2894 |
parm.putAnnotation("wrap_parameter", Boolean.TRUE); |
|
2883 |
parm.putAnnotation("wrap_parameter", true); |
|
2895 | 2884 |
} |
2896 | 2885 |
|
2897 | 2886 |
parm.putAnnotation("classname", classname); |
... | ... | |
2977 | 2966 |
{ |
2978 | 2967 |
Iterator<MemberData> iter = defs.values().iterator(); |
2979 | 2968 |
|
2980 |
outer: |
|
2981 | 2969 |
while (iter.hasNext()) |
2982 | 2970 |
{ |
2983 | 2971 |
MemberData dat = checkAccessRights(iter.next(), access); |
... | ... | |
3047 | 3035 |
* @return Member data or <code>null</code> if no such variable or data |
3048 | 3036 |
* member exists. |
3049 | 3037 |
*/ |
3050 |
private MemberData lookupMethodWorker(String name,
|
|
3051 |
ParameterKey[] sig, |
|
3052 |
int access, |
|
3053 |
boolean isStatic, |
|
3054 |
boolean internal, |
|
3055 |
Aast node) |
|
3038 |
private MethodSearchResult lookupMethodWorker(String name,
|
|
3039 |
ParameterKey[] sig,
|
|
3040 |
int access,
|
|
3041 |
boolean isStatic,
|
|
3042 |
boolean internal,
|
|
3043 |
Aast node)
|
|
3056 | 3044 |
{ |
3057 |
MemberData mdat = null;
|
|
3045 |
MethodSearchResult result = null;
|
|
3058 | 3046 |
|
3059 | 3047 |
synchronized (lock) |
3060 | 3048 |
{ |
3061 |
mdat = exactMethodLookup(name, sig, access, isStatic, internal);
|
|
3049 |
result = exactMethodLookup(name, sig, access, isStatic, internal);
|
|
3062 | 3050 | |
3063 |
if (mdat == null && !isStatic && internal)
|
|
3051 |
if (result == null && !isStatic && internal)
|
|
3064 | 3052 |
{ |
3065 | 3053 |
// can't find an instance member, check for static |
3066 |
mdat = exactMethodLookup(name, sig, access, true, internal);
|
|
3054 |
result = exactMethodLookup(name, sig, access, true, internal);
|
|
3067 | 3055 |
} |
3068 | 3056 |
|
3069 |
if (mdat == null)
|
|
3057 |
if (result == null)
|
|
3070 | 3058 |
{ |
3071 |
mdat = fuzzyMethodLookup(name, sig, access, isStatic, internal, node);
|
|
3059 |
result = fuzzyMethodLookup(name, sig, access, isStatic, internal, node);
|
|
3072 | 3060 |
|
3073 |
if (mdat == null && !isStatic && internal)
|
|
3061 |
if (result == null && !isStatic && internal)
|
|
3074 | 3062 |
{ |
3075 | 3063 |
// can't find an instance member, check for static |
3076 |
mdat = fuzzyMethodLookup(name, sig, access, true, internal, node);
|
|
3064 |
result = fuzzyMethodLookup(name, sig, access, true, internal, node);
|
|
3077 | 3065 |
} |
3078 | 3066 |
} |
3079 | 3067 |
} |
3080 | 3068 |
|
3081 |
return mdat;
|
|
3069 |
return result;
|
|
3082 | 3070 |
} |
3083 | 3071 |
|
3084 | 3072 |
/** |
3085 |
* Find the named method based on an exact signature match. This method will search up the |
|
3086 |
* parent hierarchy (recursively) if no exact match is found in the current class. |
|
3073 |
* Find the named method based on an exact signature match. This method searches all methods in the |
|
3074 |
* parent hierarchy not just in the current class. This search the hierarchy approach is needed so |
|
3075 |
* that some special exceptions can be honored: |
|
3076 |
* <p> |
|
3077 |
* <ul> |
|
3078 |
* <li>If the caller does not specify a mode and there is an otherwise exact match, that method is |
|
3079 |
* selected. Buffers have no mode processing so this case is ignored for them.</li> |
|
3080 |
* <li>A caller's table-handle or dataset-handle parameter which has an exact match is disallowed |
|
3081 |
* if there are any alternatives with a table/dataset at that parameter AND that parameter type |
|
3082 |
* is what determines that the signatures are different.</li> |
|
3083 |
* </ul> |
|
3087 | 3084 |
* |
3088 | 3085 |
* @param name |
3089 | 3086 |
* Resource name. |
3090 |
* @param sig
|
|
3087 |
* @param caller
|
|
3091 | 3088 |
* Method call signature. |
3092 | 3089 |
* @param access |
3093 | 3090 |
* Access mode (<code>KW_PUBLIC</code>, <code>KW_PROTECTD</code> or |
... | ... | |
3100 | 3097 |
* |
3101 | 3098 |
* @return Resource found or <code>null</code> if no match exists. |
3102 | 3099 |
*/ |
3103 |
private MemberData exactMethodLookup(String name, |
|
3104 |
ParameterKey[] sig, |
|
3105 |
int access, |
|
3106 |
boolean isStatic, |
|
3107 |
boolean internal) |
|
3108 |
{ |
|
3109 |
MemberData mdat = null; |
|
3110 |
|
|
3111 |
synchronized (lock) |
|
3112 |
{ |
|
3113 |
Map<String, Map<SignatureKey, MemberData>> map = isStatic ? smethods : imethods; |
|
3114 |
|
|
3115 |
if (map != null) |
|
3116 |
{ |
|
3117 |
Map<SignatureKey, MemberData> defs = map.get(name.toLowerCase()); |
|
3118 |
|
|
3119 |
if (defs != null) |
|
3120 |
{ |
|
3121 |
// exact signature match is based on the key lookup |
|
3122 |
mdat = checkAccessRights(defs.get(new SignatureKey(sig)), access); |
|
3123 |
} |
|
3124 |
} |
|
3125 |
|
|
3126 |
// ignore if the static/instance does not match |
|
3127 |
if (mdat != null && !((!isStatic && (!mdat.isStatic || internal)) || |
|
3128 |
(isStatic && mdat.isStatic))) |
|
3129 |
{ |
|
3130 |
// clear our result |
|
3131 |
mdat = null; |
|
3132 |
} |
|
3133 |
|
|
3134 |
// only look up the parent hierarchy if we haven't got a match yet; static lookups only |
|
3135 |
// work for internal usage, non-static lookups always work |
|
3136 |
if (mdat == null && parents != null && (!isStatic || internal)) |
|
3137 |
{ |
|
3138 |
// change access mode since we aren't allowed to see private stuff in parent |
|
3139 |
int _access = (access == KW_PRIVATE) ? KW_PROTECTD : access; |
|
3140 |
|
|
3141 |
for (ClassDefinition parent : parents) |
|
3142 |
{ |
|
3143 |
// recurse up inheritence hierarchy |
|
3144 |
mdat = parent.exactMethodLookup(name, sig, _access, isStatic, internal); |
|
3145 |
|
|
3146 |
if (mdat != null) |
|
3147 |
break; |
|
3148 |
} |
|
3149 |
} |
|
3150 |
} |
|
3151 |
|
|
3152 |
return mdat; |
|
3100 |
private MethodSearchResult exactMethodLookup(final String name, |
|
3101 |
final ParameterKey[] caller, |
|
3102 |
final int access, |
|
3103 |
final boolean isStatic, |
|
3104 |
final boolean internal) |
|
3105 |
{ |
|
3106 |
List<MatchMetrics> list = candidates(name, caller.length, access, isStatic, internal, false, null); |
|
3107 |
|
|
3108 |
// quick out if there are no possible matches |
|
3109 |
if (list.isEmpty()) |
|
3110 |
{ |
|
3111 |
return null; |
|
3112 |
} |
|
3113 | ||
3114 |
final boolean[] ignore = new boolean[caller.length]; |
|
3115 |
|
|
3116 |
Predicate<MatchMetrics> exactMatch = (mem) -> |
|
3117 |
{ |
|
3118 |
for (int i = 0; i < caller.length; i++) |
|
3119 |
{ |
|
3120 |
if (!ignore[i] && !caller[i].equals(mem.data.signature[i])) |
|
3121 |
{ |
|
3122 |
return false; |
|
3123 |
} |
|
3124 |
} |
|
3125 |
|
|
3126 |
return true; |
|
3127 |
}; |
|
3128 |
|
|
3129 |
List<MatchMetrics> exact = list.stream().filter(exactMatch).collect(Collectors.toList()); |
|
3130 | ||
3131 |
Predicate<MatchMetrics> flexModeMatch = (mem) -> |
|
3132 |
{ |
|
3133 |
for (int i = 0; i < caller.length; i++) |
|
3134 |
{ |
|
3135 |
if (!ignore[i]) |
|
3136 |
{ |
|
3137 |
if (caller[i].mode != null) |
|
3138 |
{ |
|
3139 |
if (!caller[i].equals(mem.data.signature[i])) |
|
3140 |
{ |
|
3141 |
return false; |
|
3142 |
} |
|
3143 |
} |
|
3144 |
else |
|
3145 |
{ |
|
3146 |
if (!caller[i].typeEquivalent(mem.data.signature[i])) |
|
3147 |
{ |
|
3148 |
return false; |
|
3149 |
} |
|
3150 |
} |
|
3151 |
} |
|
3152 |
} |
|
3153 |
|
|
3154 |
return true; |
|
3155 |
}; |
|
3156 |
|
|
3157 |
MethodSearchResult result = null; |
|
3158 |
boolean flex = false; |
|
3159 |
|
|
3160 |
if (exact.isEmpty()) |
|
3161 |
{ |
|
3162 |
List<MatchMetrics> flexMode = list.stream().filter(flexModeMatch).collect(Collectors.toList()); |
|
3163 |
|
|
3164 |
if (flexMode.size() == 1) |
|
3165 |
{ |
|
3166 |
result = new MethodSearchResult(flexMode.get(0).data, null); |
|
3167 |
flex = true; |
|
3168 |
} |
|
3169 |
} |
|
3170 |
else |
|
3171 |
{ |
|
3172 |
if (exact.size() == 1) |
|
3173 |
{ |
|
3174 |
result = new MethodSearchResult(exact.get(0).data, null); |
|
3175 |
} |
|
3176 |
} |
|
3177 |
|
|
3178 |
if (result != null) |
|
3179 |
{ |
|
3180 |
// table-handle/dataset-handle matches are only valid when the only difference in signature is |
|
3181 |
// between a table/dataset and table-handle/dataset-handle |
|
3182 | ||
3183 |
Iterator<MatchMetrics> iter = list.iterator(); |
|
3184 |
|
|
3185 |
// calculate which parameters are both table-handles/dataset-handles and have conflicting |
|
3186 |
// signature (another signature that has a table/dataset in the same parameter position) |
|
3187 |
while (iter.hasNext()) |
|
3188 |
{ |
|
3189 |
MatchMetrics m = iter.next(); |
|
3190 |
|
|
3191 |
for (int i = 0; i < caller.length; i++) |
|
3192 |
{ |
|
3193 |
// quick iteration if we already know this parameter should be ignored |
|
3194 |
if (ignore[i]) |
|
3195 |
continue; |
|
3196 |
|
|
3197 |
String type = m.data.signature[i].type; |
|
3198 | ||
3199 |
if ("TABLE-HANDLE".equals(caller[i].type)) |
|
3200 |
{ |
|
3201 |
if (type != null && type.startsWith("TEMP-TABLE")) |
|
3202 |
{ |
|
3203 |
ignore[i] = true; |
|
3204 |
} |
|
3205 |
} |
|
3206 |
else if ("DATASET-HANDLE".equals(caller[i].type)) |
|
3207 |
{ |
|
3208 |
if (type != null && type.startsWith("DATA-SET ")) |
|
3209 |
{ |
|
3210 |
ignore[i] = true; |
|
3211 |
} |
|
3212 |
} |
|
3213 |
} |
|
3214 |
} |
|
3215 | ||
3216 |
// ignoring the conflicting parameters, can we still calculate find a unique match? |
|
3217 |
List<MatchMetrics> ignoreTH = list.stream() |
|
3218 |
.filter(flex ? flexModeMatch : exactMatch) |
|
3219 |
.collect(Collectors.toList()); |
|
3220 |
if (ignoreTH.size() != 1) |
|
3221 |
{ |
|
3222 |
result = null; |
|
3223 |
} |
|
3224 |
} |
|
3225 |
|
|
3226 |
return result; |
|
3227 |
} |
|
3228 |
|
|
3229 |
/** |
|
3230 |
* Remove any candidates from the list which do not match the caller's extent. The following are |
|
3231 |
* possible matches: |
|
3232 |
* <p> |
|
3233 |
* <ul> |
|
3234 |
* <li> Neither the caller nor the parameter definition have an extent specification. |
|
3235 |
* <li> Both the caller and the parameter definition have an indeterminate extent. |
|
3236 |
* <li> The caller has a fixed extent value and the parameter definition has the same fixed extent |
|
3237 |
* value. |
|
3238 |
* <li> The caller has a fixed extent value and the parameter definition has an indeterminate |
|
3239 |
* extent. This is a fuzzy match (all the above criteria are the same as exact matches). |
|
3240 |
* |
|
3241 |
* @param list |
|
3242 |
* The list of candidates. |
|
3243 |
* @param callerExtVal |
|
3244 |
* Caller's extent value (-1 means indeterminate, 0 is scalar and any positive value is |
|
3245 |
* the fixed extent). |
|
3246 |
* @param idx |
|
3247 |
* Parameter number being processed. |
|
3248 |
*/ |
|
3249 |
private void processExtentMatches(List<MatchMetrics> list, int callerExtVal, int idx) |
|
3250 |
{ |
|
3251 |
Predicate<MatchMetrics> test = null; |
|
3252 |
Consumer<MatchMetrics> mark = (m) -> m.extExact++; |
|
3253 |
|
|
3254 |
// no extent in caller |
|
3255 |
if (callerExtVal == 0) |
|
3256 |
{ |
|
3257 |
// no extent in the parameter definition |
|
3258 |
test = (mem) -> mem.data.signature[idx].type.indexOf("[") == -1; |
|
3259 |
|
|
3260 |
// no marking needed here |
|
3261 |
mark = (m) -> {}; |
|
3262 |
} |
|
3263 |
// indeterminate extent in caller |
|
3264 |
else if (callerExtVal == -1) |
|
3265 |
{ |
|
3266 |
// indeterminate extent in the parameter definition |
|
3267 |
test = (mem) -> mem.data.signature[idx].type.indexOf("[") != -1 && |
|
3268 |
getExtent(mem.data.signature[idx].type) == -1; |
|
3269 |
} |
|
3270 |
// fixed extent in caller |
|
3271 |
else |
|
3272 |
{ |
|
3273 |
test = (mem) -> |
|
3274 |
{ |
|
3275 |
int extIdx = mem.data.signature[idx].type.indexOf("["); |
|
3276 |
|
|
3277 |
if (extIdx != -1) |
|
3278 |
{ |
|
3279 |
// the parameter definition is an extent |
|
3280 |
int parmExtVal = getExtent(mem.data.signature[idx].type); |
|
3281 |
|
|
3282 |
// fixed extents match exactly or the parm is indeterminate |
|
3283 |
return callerExtVal == parmExtVal || parmExtVal == -1; |
|
3284 |
} |
|
3285 |
else |
|
3286 |
{ |
|
3287 |
// the parameter definition is NOT an extent |
|
3288 |
return false; |
|
3289 |
} |
|
3290 |
}; |
|
3291 | ||
3292 |
// this will only be used for where we already know extIdx != -1 above which means it is an |
|
3293 |
// extent at the parameter definition (it may be either fixed or indeterminate) |
|
3294 |
mark = (mem) -> |
|
3295 |
{ |
|
3296 |
// the parameter definition is an extent |
|
3297 |
int parmExtVal = getExtent(mem.data.signature[idx].type); |
|
3298 |
|
|
3299 |
if (callerExtVal == parmExtVal) |
|
3300 |
{ |
|
3301 |
// both are fixed extents match exactly |
|
3302 |
mem.extExact++; |
|
3303 |
} |
|
3304 |
else if (parmExtVal == -1) |
|
3305 |
{ |
|
3306 |
// or the parm is indeterminate |
|
3307 |
mem.extCvt++; |
|
3308 |
} |
|
3309 |
}; |
|
3310 |
} |
|
3311 |
|
|
3312 |
list.removeIf(test.negate()); |
|
3313 |
list.stream().forEach(mark); |
|
3153 | 3314 |
} |
3154 | 3315 |
|
3155 | 3316 |
/** |
... | ... | |
3206 | 3367 |
} |
3207 | 3368 |
|
3208 | 3369 |
/** |
3370 |
* Check if there is only 1 possible match for the given predicate, if so then remove all candidates |
|
3371 |
* from the list except for that one possible match. |
|
3372 |
* |
|
3373 |
* @param list |
|
3374 |
* The list to edit. |
|
3375 |
* @param test |
|
3376 |
* The predicate to test. |
|
3377 |
* @param idx |
|
3378 |
* The parameter index for error messages. |
|
3379 |
* @param callerType |
|
3380 |
* The caller type for error messages. |
|
3381 |
* @param error |
|
3382 |
* A location to store an error message for the caller. |
|
3383 |
*/ |
|
3384 |
private boolean allowIfOnlyOne(List<MatchMetrics> list, |
|
3385 |
Predicate<MatchMetrics> test, |
|
3386 |
int idx, |
|
3387 |
String callerType, |
|
3388 |
String[] error) |
|
3389 |
{ |
|
3390 |
boolean result = false; |
|
3391 |
|
|
3392 |
List<MatchMetrics> matches = list.stream().filter(test).collect(Collectors.toList()); |
|
3393 | ||
3394 |
int num = matches.size(); |
|
3395 | ||
3396 |
if (num == 1) |
|
3397 |
{ |
|
3398 |
result = true; |
|
3399 |
matches.stream().forEach((m) -> m.cvt++); |
|
3400 |
list.removeIf(test.negate()); |
|
3401 |
} |
|
3402 |
else if (num > 1) |
|
3403 |
{ |
|
3404 |
error[0] = String.format("Ambiguous matches (%d) for parameter %d of type %s.", |
|
3405 |
num, |
|
3406 |
idx, |
|
3407 |
callerType); |
|
3408 |
} |
|
3409 |
else |
|
3410 |
{ |
|
3411 |
error[0] = String.format("No possible matches for parameter %d of type %s.", idx, callerType); |
|
3412 |
} |
|
3413 |
|
|
3414 |
if (!result) |
|
3415 |
{ |
|
3416 |
list.clear(); |
|
3417 |
} |
|
3418 |
|
|
3419 |
return result; |
|
3420 |
} |
|
3421 |
|
|
3422 |
/** |
|
3423 |
* Remove any non-exact matches from the given list. |
|
3424 |
* |
|
3425 |
* @param list |
|
3426 |
* The list to edit. |
|
3427 |
* @param exactMatch |
|
3428 |
* The test for an exact match. |
|
3429 |
* |
|
3430 |
* @return {@code true} if edits were made. |
|
3431 |
*/ |
|
3432 |
private boolean processExactMatches(List<MatchMetrics> list, Predicate<MatchMetrics> exactMatch) |
|
3433 |
{ |
|
3434 |
// calculate the exact TYPE matches |
|
3435 |
List<MatchMetrics> exacts = list.stream().filter(exactMatch).collect(Collectors.toList()); |
|
3436 |
|
|
3437 |
if (!exacts.isEmpty()) |
|
3438 |
{ |
|
3439 |
exacts.stream().forEach((m) -> m.exact++); |
|
3440 |
|
|
3441 |
// there is at least 1 exact match, remove everything else |
|
3442 |
list.removeIf(exactMatch.negate()); |
|
3443 |
|
|
3444 |
return true; |
|
3445 |
} |
|
3446 | ||
3447 |
return false; |
|
3448 |
} |
|
3449 |
|
|
3450 |
/** |
|
3209 | 3451 |
* Find the named method based on a fuzzy signature match. This method will search up the |
3210 | 3452 |
* parent hierarchy (recursively) if no fuzzy match is found in the current class. |
3211 | 3453 |
* |
... | ... | |
3226 | 3468 |
* |
3227 | 3469 |
* @return Resource found or <code>null</code> if no match exists. |
3228 | 3470 |
*/ |
3229 |
private MemberData fuzzyMethodLookup(String name,
|
|
3230 |
ParameterKey[] caller, |
|
3231 |
int access, |
|
3232 |
boolean isStatic, |
|
3233 |
boolean internal, |
|
3234 |
Aast node) |
|
3471 |
private MethodSearchResult fuzzyMethodLookup(String name,
|
|
3472 |
ParameterKey[] caller,
|
|
3473 |
int access,
|
|
3474 |
boolean isStatic,
|
|
3475 |
boolean internal,
|
|
3476 |
Aast node)
|
|
3235 | 3477 |
{ |
3236 |
MemberData mdat = null; |
|
3237 |
|
|
3238 |
boolean polyParams = false; |
|
3239 |
for (ParameterKey p : caller) |
|
3240 |
{ |
|
3241 |
if ("BaseDataType".equals(p.type)) |
|
3242 |
{ |
|
3243 |
polyParams = true; |
|
3244 |
break; |
|
3245 |
} |
|
3246 |
} |
|
3247 |
|
|
3248 | 3478 |
boolean first = isSetParam(name); |
3249 | 3479 |
|
3250 |
if ("Progress.Json.ObjectModel.JsonObject".equalsIgnoreCase(this.name) && |
|
3251 |
"write".equalsIgnoreCase(name) && |
|
3252 |
caller.length == 2 && |
|
3253 |
caller[0].type.equals("longchar") && |
|
3254 |
caller[1].type.equals("logical")) |
|
3480 |
List<MatchMetrics> list = candidates(name, caller.length, access, isStatic, internal, first, null); |
|
3481 |
|
|
3482 |
// GES_TODO: remove |
|
3483 |
// boolean debug = "calc3".equals(name) && caller.length == 1 && caller[0].type.equals("object<? extends oo.basic.compleximplements>");; |
|
3484 |
boolean debug = false; |
|
3485 |
|
|
3486 |
if (debug) |
|
3255 | 3487 |
{ |
3256 |
int bogus = 14; |
|
3488 |
System.out.println("\n\n--------------------fuzzyMethodLookup START--------------------\n"); |
|
3489 |
|
|
3490 |
System.out.printf("\n CALLER %s\n PARAMETERS: %d\n", node.dumpTree(true), caller.length); |
|
3491 |
|
|
3492 |
for (int i = 0; i < caller.length; i++) |
|
3493 |
{ |
|
3494 |
System.out.printf(" %s\n", caller[i]); |
|
3495 |
} |
|
3496 |
|
|
3497 |
for (MatchMetrics m : list) |
|
3498 |
{ |
|
3499 |
System.out.printf("DEBUG: %s\n", m.data); |
|
3500 |
} |
|
3257 | 3501 |
} |
3258 |
|
|
3259 |
List<MemberData> list = candidates(name, caller.length, access, isStatic, internal, first, null); |
|
3260 | 3502 |
|
3261 | 3503 |
// quick out if there are no possible matches |
3262 | 3504 |
if (list.isEmpty()) |
3263 | 3505 |
{ |
3506 |
if (debug) |
|
3507 |
System.out.println("--------------------fuzzyMethodLookup QUICK OUT--------------------"); |
|
3508 |
|
|
3264 | 3509 |
return null; |
3265 | 3510 |
} |
3266 | 3511 |
|
3267 | 3512 |
// special case for ParameterList.setParameter() |
3268 | 3513 |
if (first && list.size() == 1) |
3269 | 3514 |
{ |
3270 |
return list.get(0);
|
|
3515 |
return new MethodSearchResult(list.get(0).data, null);
|
|
3271 | 3516 |
} |
3272 |
|
|
3273 |
// the fuzzy lookup is done in phases |
|
3274 |
// 1. collect all matches, regardless of parameter modes |
|
3275 |
// 2. if only one match found, use that |
|
3276 |
// 3. if multiple matches found: |
|
3277 |
// - if one exact match by exact parameter type, use that |
|
3278 |
// - if multiple matches, get by parameter modes |
|
3279 |
// > if caller hasn't specified a mode for an argument, use wildcard |
|
3280 |
// > only one match should be found |
|
3281 |
// > if multiple matches, show warning |
|
3282 |
|
|
3283 |
List<MemberData> matches = new ArrayList<>(); |
|
3284 |
List<MemberData> exactMatches = new ArrayList<>(); |
|
3285 |
|
|
3286 |
// TODOS: |
|
3287 |
// - constructors |
|
3288 |
// - handle dynamic invocation marking here |
|
3289 |
// - tables/table handles |
|
3290 |
// - datasets/dataset handles |
|
3291 |
// - object fuzziness |
|
3292 |
// - does primitive type fuzziness have less priority than primitive type exact matches? |
|
3517 | ||
3518 |
String[] error = new String[1]; |
|
3519 |
|
|
3520 |
boolean polyArgs = false; |
|
3521 |
|
|
3522 |
// TODO: constructors |
|
3523 | ||
3524 |
// PHASE 1: cut down the list of all matches to include only those which are potentially valid |
|
3525 |
|
|
3526 |
if (debug) |
|
3527 |
System.out.println("\n\n---PHASE 1 START---\n"); |
|
3293 | 3528 | |
3294 | 3529 |
// process each parameter from left to right; the processing will vary by the parameter type; some |
3295 |
// types (e.g. objects) have multi-phase checks and other types are a simple comparison; at each
|
|
3530 |
// types (e.g. objects) have multi-step checks and other types are a simple comparison; at each
|
|
3296 | 3531 |
// step of the way the list of possible candidates will be reduced until there are only candidates |
3297 |
// left which match all criteria; at that point if there are more than one then we either have a
|
|
3298 |
// dynamic invocation scenario or there is some ambiguity (which should not happen if the code
|
|
3299 |
// compiles in the 4GL) |
|
3532 |
// left which COULD match all criteria; at that point if there are more than one then we have an
|
|
3533 |
// additional disambiguation step, a dynamic invocation scenario or there is some ambiguity (which
|
|
3534 |
// should not happen if the code compiles in the 4GL)
|
|
3300 | 3535 |
|
3301 |
// OUTLINE FOR NEXT CHANGES |
|
3302 |
/* |
|
3303 |
caller: |
|
3536 |
// process the caller's parameters, left to right; for each caller parameter we examine the matching |
|
3537 |
// parameter in the list of candidates, removing any candidates in the list that cannot match |
|
3304 | 3538 |
for (int i = 0; i < caller.length; i++) |
3305 | 3539 |
{ |
3540 |
final int idx = i; |
|
3541 |
|
|
3542 |
final Integer callerMode = caller[i].mode; |
|
3543 |
|
|
3306 | 3544 |
// if the caller is passing Java types, then we match them as if they were 4GL types so the get |
3307 | 3545 |
// converted here; if they are already 4GL types then no change will happen |
3308 |
String callerType = fromJava(caller[i].type); |
|
3309 |
|
|
3310 |
// wildcards: unknown value or BDT (POLY cases) match anything (no cases can be excluded) |
|
3311 |
if (callerType == null || |
|
3312 |
callerType.equals("BaseDataType") || |
|
3313 |
callerType.equals("unknown")) |
|
3314 |
{ |
|
3315 |
continue caller; |
|
3316 |
} |
|
3317 |
|
|
3318 |
// primitive types |
|
3319 |
|
|
3320 |
// primitive types widening/narrowing |
|
3321 |
|
|
3322 |
// tables/table handles |
|
3323 |
|
|
3324 |
// handles passed to table |
|
3325 |
|
|
3326 |
// datasets/dataset handles |
|
3327 |
|
|
3328 |
// handles passed to datasets |
|
3329 |
|
|
3330 |
// buffers |
|
3331 |
|
|
3332 |
// extents |
|
3333 |
|
|
3334 |
// object direct match |
|
3335 |
|
|
3336 |
// object fuzziness |
|
3337 |
|
|
3338 |
// parameter modes |
|
3339 |
} |
|
3340 |
*/ |
|
3341 |
|
|
3342 |
Iterator<MemberData> iter = list.iterator(); |
|
3343 |
|
|
3344 |
outer: |
|
3345 |
while (iter.hasNext()) |
|
3346 |
{ |
|
3347 |
MemberData dat = iter.next(); |
|
3348 |
|
|
3349 |
ParameterKey[] candidate = dat.signature; |
|
3350 |
|
|
3351 |
boolean exact = true; |
|
3352 |
|
|
3353 |
inner: |
|
3546 |
final String callerType = fromJava(caller[i].type); |
|
3547 |
|
|
3548 |
// these are all specific to the current parameter |
|
3549 |
Predicate<MatchMetrics> exactMatch = (m) -> callerType.equals(fromJava(m.data.signature[idx].type)); |
|
3550 |
Predicate<MatchMetrics> isTable = (m) -> m.data.signature[idx].type.startsWith("TEMP-TABLE"); |
|
3551 |
Predicate<MatchMetrics> isTH = (m) -> "TABLE-HANDLE".equals(m.data.signature[idx].type); |
|
3552 |
Predicate<MatchMetrics> isDataset = (m) -> m.data.signature[idx].type.startsWith("DATASET <"); |
|
3553 |
Predicate<MatchMetrics> isDH = (m) -> "DATASET-HANDLE".equals(m.data.signature[idx].type); |
|
3554 |
Predicate<MatchMetrics> modeMatch = (m) -> callerMode.equals(m.data.signature[idx].mode); |
|
3555 | ||
3556 |
// exclude any candidates which cannot ever match due to a caller MODE constraint; a null for the |
|
3557 |
// caller's mode is a wildcard that can match any mode in the parameter definition; this means that |
|
3558 |
// when the caller's mode is null, no matches can be excluded |
|
3559 |
if (callerMode != null) |
|
3560 |
{ |
|
3561 |
list.removeIf(modeMatch.negate()); |
|
3562 |
|
|
3563 |
if (debug) |
|
3564 |
System.out.printf("PARM %d: mode %d\n", idx, list.size()); |
|
3565 |
} |
|
3566 | ||
3567 |
// TYPE-specific processing |
|
3568 |
|
|
3569 |
boolean poly = (callerType == null) || callerType.equals("BaseDataType"); |
|
3570 |
|
|
3571 |
// TYPE wildcards: unknown value or BDT (POLY cases) match any TYPE |
|
3572 |
if (poly || callerType.equals("unknown")) |
|
3573 |
{ |
|
3574 |
list.stream().forEach((m) -> m.wildcard++); |
|
3575 |
|
|
3576 |
if (poly) |
|
3577 |
polyArgs = true; |
|
3578 |
|
|
3579 |
// no cases can be excluded based on type; the list remains unchanged |
|
3580 |
if (debug) |
|
3581 |
System.out.printf("PARM %d: type wildcard %d\n", idx, list.size()); |
|
3582 |
} |
|
3583 |
else |
|
3584 |
{ |
|
3585 |
// table parameters |
|
3586 |
if (callerType.startsWith("TEMP-TABLE")) |
|
3587 |
{ |
|
3588 |
// check for any exact TYPE matches |
|
3589 |
if (!processExactMatches(list, exactMatch)) |
|
3590 |
{ |
|
3591 |
// allow a match to a table-handle if there is only 1 table-handle option |
|
3592 |
allowIfOnlyOne(list, isTH, idx, callerType, error); |
|
3593 |
} |
|
3594 |
|
|
3595 |
if (debug) |
|
3596 |
System.out.printf("PARM %d: TT %d\n", idx, list.size()); |
|
3597 |
} |
|
3598 | ||
3599 |
// table handles |
|
3600 |
else if (callerType.equals("TABLE-HANDLE")) |
|
3601 |
{ |
|
3602 |
// at this point we can't know if we have a match, we can just remove anything that is not |
|
3603 |
// a table-handle or table; we defer the matching to the next phase once all other removals |
|
3604 |
// are done |
|
3605 |
list.removeIf(isTH.or(isTable).negate()); |
|
3606 |
list.stream().forEach((m) -> m.cvt++); |
|
3607 |
|
|
3608 |
if (debug) |
|
3609 |
System.out.printf("PARM %d: TH %d\n", idx, list.size()); |
|
3610 |
} |
|
3611 |
|
|
3612 |
// dataset parameters |
|
3613 |
else if (callerType.startsWith("DATASET <")) |
|
3614 |
{ |
|
3615 |
// check for any exact TYPE matches |
|
3616 |
if (!processExactMatches(list, exactMatch)) |
|
3617 |
{ |
|
3618 |
// allow a match to a dataset-handle if there is only 1 dataset-handle option |
|
3619 |
allowIfOnlyOne(list, isDH, idx, callerType, error); |
|
3620 |
} |
|
3621 |
|
|
3622 |
if (debug) |
|
3623 |
System.out.printf("PARM %d: DS %d\n", idx, list.size()); |
|
3624 |
} |
|
3625 |
|
|
3626 |
// dataset handles |
|
3627 |
else if (callerType.equals("DATASET-HANDLE")) |
|
3628 |
{ |
|
3629 |
// at this point we can't know if we have a match, we can just remove anything that is not |
|
3630 |
// a dataset-handle or dataset; we defer the matching to the next phase once all other |
|
3631 |
// removals are done |
|
3632 |
list.removeIf(isDH.or(isDataset).negate()); |
|
3633 |
list.stream().forEach((m) -> m.cvt++); |
|
3634 |
|
|
3635 |
if (debug) |
|
3636 |
System.out.printf("PARM %d: DH %d\n", idx, list.size()); |
|
3637 |
} |
|
3638 | ||
3639 |
// buffers |
|
3640 |
else if (callerType.startsWith("BUFFER")) |
|
3641 |
{ |
|
3642 |
// only an exact match works (handled above); no fuzzy matching here |
|
3643 |
list.removeIf(exactMatch.negate()); |
|
3644 |
list.stream().forEach((m) -> m.exact++); |
|
3645 |
|
|
3646 |
if (debug) |
|
3647 |
System.out.printf("PARM %d: buf %d\n", idx, list.size()); |
|
3648 |
} |
|
3649 | ||
3650 |
// must be one of the BaseDataType wrapper classes |
|
3651 |
else |
|
3652 |
{ |
|
3653 |
final int extIdx = callerType.indexOf("["); |
|
3654 |
final int extVal = (extIdx == -1) ? 0 : getExtent(callerType); |
|
3655 |
final String basicType = (extIdx == -1) ? callerType : callerType.substring(0, extIdx); |
|
3656 | ||
3657 |
// extents |
|
3658 |
processExtentMatches(list, extVal, idx); |
|
3659 |
|