unified TypeInfo API access to it uses exposed TypeInfo members instead of inner raw Class reflection calls

This commit is contained in:
bigguy345 committed 2026-03-13 17:47:29 +02:00
1 parent 57e6934fe5
commit 691a7fb2c7
5 files changed
+508 -310

No files matched your search

@@ -1,15 +1,16 @@
package noppes.npcs.client.gui.util.script.autocomplete;
import noppes.npcs.client.gui.util.script.interpreter.js_parser.*;
import noppes.npcs.client.gui.util.script.interpreter.field.EnumConstantInfo;
import noppes.npcs.client.gui.util.script.interpreter.field.FieldInfo;
import noppes.npcs.client.gui.util.script.interpreter.method.MethodInfo;
import noppes.npcs.client.gui.util.script.interpreter.type.synthetic.*;
import noppes.npcs.client.gui.util.script.interpreter.type.ScriptTypeInfo;
import noppes.npcs.client.gui.util.script.interpreter.type.TypeChecker;
import noppes.npcs.client.gui.util.script.interpreter.type.TypeInfo;
import noppes.npcs.client.gui.util.script.interpreter.type.TypeResolver;
import noppes.npcs.config.ConfigScript;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
@@ -37,6 +38,15 @@ public class JSAutocompleteProvider extends JavaAutocompleteProvider {
/**
* Add suggestions for member access (after dot).
* Uses ScriptDocument's unified type resolution system.
*
* Fixes applied vs original implementation:
* - Single getMemberAccessResolvePosition() call (was duplicated)
* - Single getMergeRegistryType() call (was duplicated)
* - Shared addedMethods/addedFields sets across ALL sources for cross-source deduplication
* - Unified nested type lookup via getAllNestedTypes() instead of raw reflection
* - Single Object method injection path (registry-based via fromJSMethod)
* - Name-based Object.class guard instead of fragile Class identity comparison
* - Logged SecurityException instead of silently swallowed
*/
protected void addMemberSuggestions(Context context, List<AutocompleteItem> items) {
String receiverExpr = context.receiverExpression;
@@ -44,113 +54,105 @@ public class JSAutocompleteProvider extends JavaAutocompleteProvider {
return;
}
// Resolve position once — used for both type resolution and static context detection
int resolvePos = getMemberAccessResolvePosition(context);
// Use ScriptDocument's resolveExpressionType - handles both Java and JS
TypeInfo receiverType = document.resolveExpressionType(receiverExpr, getMemberAccessResolvePosition(context));
TypeInfo receiverType = document.resolveExpressionType(receiverExpr, resolvePos);
if (receiverType == null || !receiverType.isResolved()) {
return;
}
// Check for synthetic types first (e.g., Nashorn Java object, custom types)
SyntheticType syntheticType =
document.getTypeResolver().getSyntheticType(receiverType.getSimpleName());
// --- JS-SPECIFIC: synthetic type early return ---
SyntheticType syntheticType = document.getTypeResolver().getSyntheticType(receiverType.getSimpleName());
if (syntheticType != null) {
addSyntheticTypeSuggestions(syntheticType, items, context.methodsOnly);
return;
}
boolean isStaticContext = isStaticAccess(receiverExpr, getMemberAccessResolvePosition(context));
boolean isStaticContext = isStaticAccess(receiverExpr, resolvePos);
// Resolve merge-registry type once — used for both method and field fallback
Class<?> javaClass = receiverType.getJavaClass();
if (javaClass != null) {
Set<String> addedMethods = new HashSet<>();
for (Method method : javaClass.getMethods()) {
if (!Modifier.isPublic(method.getModifiers())) continue;
if (isStaticContext && !Modifier.isStatic(method.getModifiers())) continue;
String sig = method.getName() + "(" + method.getParameterCount() + ")";
if (addedMethods.add(sig)) {
items.add(AutocompleteItem.fromMethod(MethodInfo.fromReflection(method, receiverType), context.methodsOnly));
}
JSTypeInfo mergeRegistryType = (javaClass != null
&& receiverType.getSyntheticMethods().isEmpty()
&& receiverType.getSyntheticFields().isEmpty())
? getMergeRegistryType(javaClass) : null;
// Shared deduplication sets — prevent the same method/field appearing from multiple sources
Set<String> addedMethods = new HashSet<>();
Set<String> addedFields = new HashSet<>();
// ---- SHARED: unified method enumeration (TypeInfo.getAllMethods covers reflection + synthetics) ----
for (MethodInfo method : receiverType.getAllMethods()) {
if (isStaticContext && !Modifier.isStatic(method.getModifiers()))
continue;
String sig = method.getName() + "(" + method.getParameterCount() + ")";
if (addedMethods.add(sig))
items.add(AutocompleteItem.fromMethod(method, context.methodsOnly));
}
// --- JS-SPECIFIC: merge registry type fallback for methods ---
if (mergeRegistryType != null) {
addMethodsFromType(mergeRegistryType, receiverType, items, addedMethods, context.methodsOnly,
isStaticContext);
}
if (!context.methodsOnly) {
// ---- SHARED: unified field enumeration with deduplication ----
for (FieldInfo field : receiverType.getAllFields()) {
if (isStaticContext && !Modifier.isStatic(field.getModifiers()))
continue;
if (addedFields.add(field.getName()))
items.add(AutocompleteItem.fromField(field));
}
List<MethodInfo> effectiveSyntheticMethods = receiverType.getSyntheticMethods();
if (!effectiveSyntheticMethods.isEmpty()) {
for (MethodInfo method : effectiveSyntheticMethods) {
if (isStaticContext && !method.isStatic()) continue;
items.add(AutocompleteItem.fromMethod(method, context.methodsOnly));
}
} else {
JSTypeInfo jsType = getMergeRegistryType(javaClass);
if (jsType != null) {
addMethodsFromType(jsType, receiverType, items, new HashSet<>(), context.methodsOnly, isStaticContext);
}
// --- JS-SPECIFIC: merge registry type fallback for fields ---
if (mergeRegistryType != null) {
addFieldsFromType(mergeRegistryType, receiverType, items, addedFields, isStaticContext);
}
if (!context.methodsOnly) {
for (Field field : javaClass.getFields()) {
if (!Modifier.isPublic(field.getModifiers())) continue;
if (isStaticContext && !Modifier.isStatic(field.getModifiers())) continue;
items.add(AutocompleteItem.fromField(FieldInfo.fromReflection(field, receiverType)));
}
if (!receiverType.getSyntheticFields().isEmpty()) {
for (FieldInfo field : receiverType.getSyntheticFields()) {
if (isStaticContext && !field.isStatic()) continue;
items.add(AutocompleteItem.fromField(field));
}
} else {
JSTypeInfo jsType = getMergeRegistryType(javaClass);
if (jsType != null) {
addFieldsFromType(jsType, receiverType, items, new HashSet<>(), isStaticContext);
}
}
}
// ---- SHARED: nested types (inner classes) in static context via getAllNestedTypes() ----
if (isStaticContext && !context.methodsOnly) {
// ScriptTypeInfo.getAllNestedTypes() returns script inner classes directly;
// TypeInfo.getAllNestedTypes() uses reflection. No duplicate logic needed.
for (TypeInfo nestedType : receiverType.getAllNestedTypes()) {
items.add(AutocompleteItem.fromType(nestedType));
}
if (!isStaticContext && javaClass != Object.class) {
JSTypeInfo objJSType = registry.getType("Object");
if (objJSType != null) {
for (JSMethodInfo m : objJSType.getMethods().values()) {
if (!m.isStatic()) {
}
// Enum constants — polymorphic via TypeInfo.getEnumConstants()
if (!context.methodsOnly) {
for (EnumConstantInfo enumConstant : receiverType.getEnumConstants().values()) {
items.add(AutocompleteItem.fromField(enumConstant.getFieldInfo()));
}
}
// --- JS-SPECIFIC: Object methods from JS registry (single unified path) ---
// Add Object.class JS methods for instance access, unless receiver IS Object itself.
// Uses name comparison instead of Class identity for robustness across classloaders.
if (!isStaticContext && (javaClass == null || !"java.lang.Object".equals(javaClass.getName()))) {
JSTypeInfo objJSType = registry.getType("Object");
if (objJSType != null) {
for (JSMethodInfo m : objJSType.getMethods().values()) {
if (!m.isStatic()) {
String sig = m.getName() + "(" + m.getParameterCount() + ")";
if (addedMethods.add(sig)) {
items.add(AutocompleteItem.fromJSMethod(m, receiverType, 1, context.methodsOnly));
}
}
}
}
return;
}
Set<String> syntheticMethodNames = new HashSet<>();
for (MethodInfo method : receiverType.getSyntheticMethods()) {
syntheticMethodNames.add(method.getName());
if (isStaticContext && !method.isStatic()) continue;
items.add(AutocompleteItem.fromMethod(method, context.methodsOnly));
}
for (FieldInfo field : receiverType.getSyntheticFields()) {
if (!context.methodsOnly) {
if (isStaticContext && !field.isStatic()) continue;
if (syntheticMethodNames.contains(field.getName())) continue;
items.add(AutocompleteItem.fromField(field));
}
}
JSTypeInfo jsTypeInfo = receiverType.getJSTypeInfo();
if (jsTypeInfo != null) {
addMethodsFromType(jsTypeInfo, receiverType, items, new HashSet<>(), context.methodsOnly, isStaticContext);
if (!context.methodsOnly && ConfigScript.ShowImplementationFieldsInAutocomplete) {
addFieldsFromType(jsTypeInfo, receiverType, items, new HashSet<>(), isStaticContext);
}
}
if (!isStaticContext) {
Set<String> addedObjMethods = new HashSet<>();
for (Method m : Object.class.getMethods()) {
if (!Modifier.isPublic(m.getModifiers()) || Modifier.isStatic(m.getModifiers())) continue;
String sig = m.getName() + "(" + m.getParameterCount() + ")";
if (addedObjMethods.add(sig)) {
items.add(AutocompleteItem.fromMethod(MethodInfo.fromReflection(m, receiverType), context.methodsOnly));
}
}
JSTypeInfo objJSType = registry.getType("Object");
if (objJSType != null) {
for (JSMethodInfo m : objJSType.getMethods().values()) {
if (!m.isStatic()) {
items.add(AutocompleteItem.fromJSMethod(m, receiverType, 1, context.methodsOnly));
}
// --- JS-SPECIFIC: pure JS type fallback (javaClass == null, not ScriptTypeInfo) ---
if (javaClass == null && !(receiverType instanceof ScriptTypeInfo)) {
JSTypeInfo jsTypeInfo = receiverType.getJSTypeInfo();
if (jsTypeInfo != null) {
addMethodsFromType(jsTypeInfo, receiverType, items, addedMethods, context.methodsOnly, isStaticContext);
if (!context.methodsOnly && ConfigScript.ShowImplementationFieldsInAutocomplete) {
addFieldsFromType(jsTypeInfo, receiverType, items, addedFields, isStaticContext);
}
}
}
@@ -10,8 +10,6 @@ import noppes.npcs.client.gui.util.script.interpreter.type.TypeChecker;
import noppes.npcs.client.gui.util.script.interpreter.type.TypeInfo;
import noppes.npcs.client.gui.util.script.interpreter.type.TypeResolver;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
@@ -100,92 +98,52 @@ public class JavaAutocompleteProvider implements AutocompleteProvider {
if (receiverExpr == null || receiverExpr.isEmpty()) {
return;
}
// Resolve position once — used for both type resolution and static context detection
int resolvePos = getMemberAccessResolvePosition(context);
// Resolve the type of the receiver expression
TypeInfo receiverType = document.resolveExpressionType(receiverExpr, getMemberAccessResolvePosition(context));
TypeInfo receiverType = document.resolveExpressionType(receiverExpr, resolvePos);
if (receiverType == null || !receiverType.isResolved()) {
return;
}
// Determine if this is a static context (accessing a class type)
boolean isStaticContext = isStaticAccess(receiverExpr, getMemberAccessResolvePosition(context));
boolean isStaticContext = isStaticAccess(receiverExpr, resolvePos);
Class<?> clazz = receiverType.getJavaClass();
if (clazz == null) {
// Try ScriptTypeInfo
if (receiverType instanceof ScriptTypeInfo) {
addScriptTypeMembers((ScriptTypeInfo) receiverType, items, isStaticContext, context.methodsOnly);
}
return;
}
// Add methods
// Shared deduplication sets across all member sources
Set<String> addedMethods = new HashSet<>();
for (Method method : clazz.getMethods()) {
if (Modifier.isPublic(method.getModifiers())) {
// Filter by static context
if (isStaticContext && !Modifier.isStatic(method.getModifiers())) {
continue;
}
String sig = method.getName() + "(" + method.getParameterCount() + ")";
if (!addedMethods.contains(sig)) {
addedMethods.add(sig);
MethodInfo methodInfo = MethodInfo.fromReflection(method, receiverType);
items.add(AutocompleteItem.fromMethod(methodInfo, context.methodsOnly));
}
}
}
Set<String> addedFields = new HashSet<>();
for (MethodInfo method : receiverType.getSyntheticMethods()) {
if (isStaticContext && !Modifier.isStatic(method.getModifiers()))
continue;
items.add(AutocompleteItem.fromMethod(method, context.methodsOnly));
// Unified method enumeration — works for Java types, ScriptTypeInfo, and intersection bounds
for (MethodInfo method : receiverType.getAllMethods()) {
if (isStaticContext && !Modifier.isStatic(method.getModifiers())) continue;
String sig = method.getName() + "(" + method.getParameterCount() + ")";
if (addedMethods.add(sig))
items.add(AutocompleteItem.fromMethod(method, context.methodsOnly));
}
// Add fields (skip if methodsOnly is true - but the filtering will be done in getSuggestions)
if (!context.methodsOnly) {
for (Field field : clazz.getFields()) {
if (Modifier.isPublic(field.getModifiers())) {
// Filter by static context
if (isStaticContext && !Modifier.isStatic(field.getModifiers())) {
continue;
}
FieldInfo fieldInfo = FieldInfo.fromReflection(field, receiverType);
items.add(AutocompleteItem.fromField(fieldInfo));
}
}
for (FieldInfo field : receiverType.getSyntheticFields()) {
if (isStaticContext && !Modifier.isStatic(field.getModifiers()))
continue;
items.add(AutocompleteItem.fromField(field));
// Unified field enumeration with deduplication
for (FieldInfo field : receiverType.getAllFields()) {
if (isStaticContext && !Modifier.isStatic(field.getModifiers())) continue;
if (addedFields.add(field.getName()))
items.add(AutocompleteItem.fromField(field));
}
}
// Add nested types (inner classes/interfaces) in static context
// Nested types (inner classes) in static context via getAllNestedTypes()
if (isStaticContext && !context.methodsOnly) {
try {
for (Class<?> nested : clazz.getDeclaredClasses()) {
if (!Modifier.isPublic(nested.getModifiers())) {
continue;
}
// Convert $ notation to dot notation for resolution
String nestedFullName = nested.getName().replace('$', '.');
TypeInfo nestedType = TypeResolver.getInstance().resolveFullName(nestedFullName);
if (nestedType != null && nestedType.isResolved()) {
items.add(AutocompleteItem.fromType(nestedType));
}
}
} catch (SecurityException ignored) {
// Some runtimes may restrict getDeclaredClasses()
for (TypeInfo nestedType : receiverType.getAllNestedTypes()) {
items.add(AutocompleteItem.fromType(nestedType));
}
}
// Enum constants — polymorphic via TypeInfo.getEnumConstants()
if (!context.methodsOnly) {
for (EnumConstantInfo enumConstant : receiverType.getEnumConstants().values()) {
items.add(AutocompleteItem.fromField(enumConstant.getFieldInfo()));
}
}
}
/**
@@ -218,76 +176,6 @@ public class JavaAutocompleteProvider implements AutocompleteProvider {
return Math.max(0, Math.min(context.prefixStart, context.text.length()));
}
/**
* Add members from a script-defined type.
*/
protected void addScriptTypeMembers(ScriptTypeInfo scriptType, List<AutocompleteItem> items, boolean isStaticContext) {
addScriptTypeMembers(scriptType, items, isStaticContext, false);
}
/**
* Add members from a script-defined type.
* @param scriptType The script type to get members from
* @param items The list to add items to
* @param isStaticContext Whether we're in a static context
* @param forMethodReference If true, only add methods with no parentheses in insert text
*/
protected void addScriptTypeMembers(ScriptTypeInfo scriptType, List<AutocompleteItem> items, boolean isStaticContext, boolean forMethodReference) {
// Add methods (getMethods returns Map<String, List<MethodInfo>>)
for (List<MethodInfo> overloads : scriptType.getMethods().values()) {
for (MethodInfo method : overloads) {
// Filter by static context
if (isStaticContext && !method.isStatic()) {
continue;
}
items.add(AutocompleteItem.fromMethod(method, forMethodReference));
}
}
// Add fields (skip if methodsOnly/forMethodReference is true)
if (!forMethodReference) {
// Add fields (getFields returns Map<String, FieldInfo>)
for (FieldInfo field : scriptType.getFields().values()) {
// Filter by static context
if (isStaticContext && !field.isStatic()) {
continue;
}
items.add(AutocompleteItem.fromField(field));
}
// Add enum constants (getEnumConstants returns Map<String, EnumConstantInfo>)
for (EnumConstantInfo enumConstant : scriptType.getEnumConstants().values()) {
items.add(AutocompleteItem.fromField(enumConstant.getFieldInfo()));
}
// Add nested types (inner classes/interfaces) in static context
if (isStaticContext) {
for (ScriptTypeInfo inner : scriptType.getInnerClasses()) {
items.add(AutocompleteItem.fromType(inner));
}
}
}
// Add parent class members
if (scriptType.hasSuperClass()) {
TypeInfo superType = scriptType.getSuperClass();
if (superType != null && superType.getJavaClass() != null) {
Class<?> superClazz = superType.getJavaClass();
Set<String> addedMethods = new HashSet<>();
for (Method method : superClazz.getMethods()) {
if (Modifier.isPublic(method.getModifiers())) {
String sig = method.getName() + "(" + method.getParameterCount() + ")";
if (!addedMethods.contains(sig)) {
addedMethods.add(sig);
MethodInfo methodInfo = MethodInfo.fromReflection(method, superType);
items.add(AutocompleteItem.fromMethod(methodInfo, forMethodReference));
}
}
}
}
}
}
/**
* Add suggestions based on current scope (not after a dot).
*/
@@ -4308,6 +4308,37 @@ public class ScriptDocument {
return resolveTypeAndTrackUsage(typeName);
}
private TypeInfo substituteTypeParams(TypeInfo type, int position) {
if (type == null)
return null;
if (!type.isResolved() && type.getSimpleName() != null
&& !type.getSimpleName().contains(".")) {
TypeInfo sub = resolveType(type.getSimpleName(), position);
return (sub != null && sub.isResolved()) ? sub : type;
}
if (type.isParameterized()) {
List<TypeInfo> args = type.getAppliedTypeArgs();
if (args != null && !args.isEmpty()) {
List<TypeInfo> substituted = new ArrayList<>(args.size());
boolean anyChanged = false;
for (TypeInfo arg : args) {
TypeInfo sub = substituteTypeParams(arg, position);
if (sub != arg)
anyChanged = true;
substituted.add(sub);
}
if (anyChanged) {
TypeInfo raw = type.getRawType();
return raw != null ? raw.parameterize(substituted) : type.parameterize(substituted);
}
}
}
return type;
}
/**
* Position-aware type resolution overload.
* Resolves a type name with positional context so that an unqualified inner class name
@@ -4330,7 +4361,7 @@ public class ScriptDocument {
// If already resolved, no need for positional fallback
if (resolved != null && resolved.isResolved()) {
return resolved;
return substituteTypeParams(resolved, position);
}
if (typeName != null && !typeName.contains(".")) {
@@ -9967,7 +9998,7 @@ for (ScriptTypeInfo type:scriptTypes.values()) {
TokenType type = isClassTypeInfo ? varInfo.isGlobal() ? TokenType.GLOBAL_FIELD
: TokenType.LOCAL_FIELD : info.getTokenType();
: TokenType.LOCAL_FIELD : TokenType.METHOD_CALL;
marks.add(new ScriptLine.Mark(start, end, type, isClassTypeInfo ? varInfo : ctorCall));
continue;
}
@@ -10,6 +10,7 @@ import noppes.npcs.client.gui.util.script.interpreter.token.TokenType;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -176,8 +177,6 @@ public class ScriptTypeInfo extends TypeInfo {
Set<ScriptTypeInfo> active = PARAMETERIZING.get();
if (!active.add(this)) {
// Re-entrant call on the same raw type — fall back to shallow parameterization
// (no method/field substitution) to break the infinite recursion.
return super.parameterize(typeArgs);
}
@@ -310,7 +309,11 @@ public class ScriptTypeInfo extends TypeInfo {
if (enumConstants.containsKey(fieldName))
return true;
for (FieldInfo f : getSyntheticFields())
if (f.getName().equals(fieldName))
return true;
return false;
}
@@ -325,6 +328,10 @@ public class ScriptTypeInfo extends TypeInfo {
return enumConst.getFieldInfo();
}
for (FieldInfo f : getSyntheticFields())
if (f.getName().equals(fieldName))
return f;
return null;
}
@@ -361,6 +368,7 @@ public class ScriptTypeInfo extends TypeInfo {
/**
* Get all enum constants.
*/
@Override
public Map<String, EnumConstantInfo> getEnumConstants() {
return new HashMap<>(enumConstants);
}
@@ -380,32 +388,51 @@ public class ScriptTypeInfo extends TypeInfo {
@Override
public boolean hasMethod(String methodName) {
return methods.containsKey(methodName);
if (methods.containsKey(methodName))
return true;
for (MethodInfo m : getSyntheticMethods())
if (m.getName().equals(methodName))
return true;
return false;
}
@Override
public boolean hasMethod(String methodName, int paramCount) {
List<MethodInfo> overloads = methods.get(methodName);
if (overloads == null) return false;
for (MethodInfo m : overloads) {
if (m.getParameterCount() == paramCount) {
return true;
if (overloads != null) {
for (MethodInfo m : overloads) {
if (m.getParameterCount() == paramCount) {
return true;
}
}
}
for (MethodInfo m : getSyntheticMethods())
if (m.getName().equals(methodName) && m.getParameterCount() == paramCount)
return true;
return false;
}
@Override
public MethodInfo getMethodInfo(String methodName) {
List<MethodInfo> overloads = methods.get(methodName);
return (overloads != null && !overloads.isEmpty()) ? overloads.get(0) : null;
if (overloads != null && !overloads.isEmpty())
return overloads.get(0);
for (MethodInfo m : getSyntheticMethods())
if (m.getName().equals(methodName))
return m;
return null;
}
/**
* Get all method overloads with the given name.
*/
@Override
public List<MethodInfo> getAllMethodOverloads(String methodName) {
return methods.getOrDefault(methodName, new ArrayList<>());
List<MethodInfo> result = new ArrayList<>(methods.getOrDefault(methodName, Collections.emptyList()));
for (MethodInfo m : getSyntheticMethods())
if (m.getName().equals(methodName))
result.add(m);
return result;
}
/**
@@ -432,7 +459,26 @@ public class ScriptTypeInfo extends TypeInfo {
allMethods.addAll(overloads);
}
return allMethods;
}
}
@Override
public List<MethodInfo> getAllMethods() {
List<MethodInfo> result = getAllMethodsFlat();
result.addAll(getSyntheticMethods());
return result;
}
@Override
public List<FieldInfo> getAllFields() {
List<FieldInfo> result = new ArrayList<>(getFields().values());
result.addAll(getSyntheticFields());
return result;
}
@Override
public List<TypeInfo> getAllNestedTypes() {
return new ArrayList<>(innerClasses);
}
// ==================== CONSTRUCTOR MANAGEMENT ====================
@@ -771,6 +771,149 @@ public class TypeInfo {
}
}
/**
* Returns all public instance members of this type for autocomplete/resolution.
* Override in subclasses to extend or replace the default Java reflection path.
*/
public List<MethodInfo> getAllMethods() {
if (javaClass != null) {
// Java reflection path: enumerate public methods + synthetics
List<MethodInfo> result = new ArrayList<>();
try {
for (java.lang.reflect.Method m : javaClass.getMethods()) {
try {
result.add(MethodInfo.fromReflection(m, this));
} catch (Exception e) {
// Skip individual methods that fail (e.g., linkage errors on obscure return types)
}
}
} catch (Exception ignored) {}
result.addAll(getSyntheticMethods());
return result;
}
// javaClass == null: check rawType, jsTypeInfo, synthetics in priority order
if (rawType != null && rawType != this) {
List<MethodInfo> rawMethods = rawType.getAllMethods();
if (!rawMethods.isEmpty() && isParameterized()) {
Map<String, TypeInfo> bindings = TypeSubstitutor.createBindingsFromReceiver(this);
if (!bindings.isEmpty()) {
List<MethodInfo> substituted = new ArrayList<>(rawMethods.size());
for (MethodInfo m : rawMethods) {
substituted.add(m.substituteTypeParams(bindings));
}
return substituted;
}
}
return rawMethods;
}
// No javaClass, no rawType — collect from jsTypeInfo and synthetics
List<MethodInfo> result = new ArrayList<>();
if (jsTypeInfo != null) {
for (JSMethodInfo m : jsTypeInfo.getMethods().values()) {
result.add(MethodInfo.fromJSMethod(m, this));
}
}
result.addAll(getSyntheticMethods());
return result;
}
public List<FieldInfo> getAllFields() {
if (javaClass != null) {
// Java reflection path: enumerate public fields + synthetics
List<FieldInfo> result = new ArrayList<>();
try {
for (java.lang.reflect.Field f : javaClass.getFields()) {
try {
result.add(FieldInfo.fromReflection(f, this));
} catch (Exception e) {
// Skip individual fields that fail rather than losing ALL fields.
}
}
} catch (Exception ignored) {}
result.addAll(getSyntheticFields());
return result;
}
// javaClass == null: check rawType, jsTypeInfo, synthetics in priority order
if (rawType != null && rawType != this) {
List<FieldInfo> rawFields = rawType.getAllFields();
if (!rawFields.isEmpty() && isParameterized()) {
Map<String, TypeInfo> bindings = TypeSubstitutor.createBindingsFromReceiver(this);
if (!bindings.isEmpty()) {
List<FieldInfo> substituted = new ArrayList<>(rawFields.size());
for (FieldInfo f : rawFields) {
substituted.add(f.substituteTypeParams(bindings));
}
return substituted;
}
}
return rawFields;
}
// No javaClass, no rawType — collect from jsTypeInfo and synthetics
List<FieldInfo> result = new ArrayList<>();
if (jsTypeInfo != null) {
for (JSFieldInfo f : jsTypeInfo.getFields().values()) {
result.add(FieldInfo.fromJSField(f, this));
}
}
result.addAll(getSyntheticFields());
return result;
}
/**
* Returns all public nested types (inner classes/interfaces/enums) of this type.
* Uses reflection to discover nested types from the Java class, filters to public only,
* converts internal '$' separators to '.' for name resolution, and resolves each via TypeResolver.
*
* Override in subclasses (e.g., ScriptTypeInfo) to return script-defined inner classes.
*
* @return List of resolved nested TypeInfo instances (may be empty, never null)
*/
public List<TypeInfo> getAllNestedTypes() {
if (javaClass != null) {
List<TypeInfo> result = new ArrayList<>();
try {
for (Class<?> nested : javaClass.getDeclaredClasses()) {
if (!Modifier.isPublic(nested.getModifiers())) {
continue;
}
// Convert JVM internal '$' separator to '.' for TypeResolver lookup
String nestedFullName = nested.getName().replace('$', '.');
TypeInfo nestedType = TypeResolver.getInstance().resolveFullName(nestedFullName);
if (nestedType != null && nestedType.isResolved()) {
result.add(nestedType);
}
}
} catch (SecurityException ignored) {
// Some runtimes may restrict getDeclaredClasses()
}
return result;
}
if (rawType != null && rawType != this) {
return rawType.getAllNestedTypes();
}
// No javaClass, no rawType — check jsTypeInfo for inner types
if (jsTypeInfo != null) {
Map<String, JSTypeInfo> innerTypes = jsTypeInfo.getInnerTypes();
if (!innerTypes.isEmpty()) {
List<TypeInfo> result = new ArrayList<>(innerTypes.size());
for (JSTypeInfo inner : innerTypes.values()) {
TypeInfo ti = TypeInfo.fromJSTypeInfo(inner);
if (ti != null) {
result.add(ti);
}
}
return result;
}
}
return Collections.emptyList();
}
/**
* Check if this type has a method with the given name.
*/
@@ -789,6 +932,8 @@ public class TypeInfo {
} catch (Exception e) {
// Security or linkage error
}
} else if (rawType != null && rawType != this) {
return rawType.hasMethod(methodName);
}
TypeInfo obj = TypeInfo.object();
@@ -817,6 +962,8 @@ public class TypeInfo {
} catch (Exception e) {
// Security or linkage error
}
} else if (rawType != null && rawType != this) {
return rawType.hasMethod(methodName, paramCount);
}
TypeInfo obj = TypeInfo.object();
if (obj != this) return obj.hasMethod(methodName);
@@ -828,13 +975,15 @@ public class TypeInfo {
* Override in ScriptTypeInfo for script-defined types.
*/
public boolean hasConstructors() {
if (javaClass == null) return false;
try {
return javaClass.getConstructors().length > 0;
} catch (Exception e) {
// Security or linkage error
return false;
if (javaClass != null) {
try {
return javaClass.getConstructors().length > 0;
} catch (Exception e) {
return false;
}
}
if (rawType != null && rawType != this) return rawType.hasConstructors();
return false;
}
/**
@@ -843,16 +992,18 @@ public class TypeInfo {
*/
public List<MethodInfo> getConstructors() {
List<MethodInfo> result = new ArrayList<>();
if (javaClass == null) return result;
try {
java.lang.reflect.Constructor<?>[] constructors = javaClass.getConstructors();
for (java.lang.reflect.Constructor<?> ctor : constructors) {
result.add(MethodInfo.fromReflectionConstructor(ctor, this));
if (javaClass != null) {
try {
java.lang.reflect.Constructor<?>[] constructors = javaClass.getConstructors();
for (java.lang.reflect.Constructor<?> ctor : constructors) {
result.add(MethodInfo.fromReflectionConstructor(ctor, this));
}
} catch (Exception e) {
// Security or linkage error
}
} catch (Exception e) {
// Security or linkage error
return result;
}
if (rawType != null && rawType != this) return rawType.getConstructors();
return result;
}
@@ -861,45 +1012,49 @@ public class TypeInfo {
* Override in ScriptTypeInfo for script-defined types.
*/
public MethodInfo findConstructor(int argCount) {
if (javaClass == null) return null;
try {
Constructor<?>[] constructors = javaClass.getConstructors();
for (Constructor<?> ctor : constructors) {
if (ctor.getParameterCount() == argCount) {
return MethodInfo.fromReflectionConstructor(ctor, this);
if (javaClass != null) {
try {
Constructor<?>[] constructors = javaClass.getConstructors();
for (Constructor<?> ctor : constructors) {
if (ctor.getParameterCount() == argCount) {
return MethodInfo.fromReflectionConstructor(ctor, this);
}
}
} catch (Exception e) {
// Security or linkage error
}
} catch (Exception e) {
// Security or linkage error
return null;
}
if (rawType != null && rawType != this) return rawType.findConstructor(argCount);
return null;
}
public MethodInfo findConstructor(TypeInfo[] argTypes) {
if (javaClass == null) return null;
try {
Constructor<?>[] constructors = javaClass.getConstructors();
for (Constructor<?> ctor : constructors) {
if (ctor.getParameterCount() == argTypes.length) {
Class<?>[] paramTypes = ctor.getParameterTypes();
boolean match = true;
for (int i = 0; i < argTypes.length; i++) {
TypeInfo paramTypeInfo = TypeInfo.fromClass(paramTypes[i]);
if (!TypeChecker.isTypeCompatible(paramTypeInfo, argTypes[i])) {
match = false;
break;
if (javaClass != null) {
try {
Constructor<?>[] constructors = javaClass.getConstructors();
for (Constructor<?> ctor : constructors) {
if (ctor.getParameterCount() == argTypes.length) {
Class<?>[] paramTypes = ctor.getParameterTypes();
boolean match = true;
for (int i = 0; i < argTypes.length; i++) {
TypeInfo paramTypeInfo = TypeInfo.fromClass(paramTypes[i]);
if (!TypeChecker.isTypeCompatible(paramTypeInfo, argTypes[i])) {
match = false;
break;
}
}
if (match) {
return MethodInfo.fromReflectionConstructor(ctor, this);
}
}
if (match) {
return MethodInfo.fromReflectionConstructor(ctor, this);
}
}
} catch (Exception e) {
// Security or linkage error
}
} catch (Exception e) {
// Security or linkage error
return null;
}
if (rawType != null && rawType != this) return rawType.findConstructor(argTypes);
return null;
}
@@ -913,30 +1068,63 @@ public class TypeInfo {
return jsTypeInfo.hasField(fieldName);
}
if (javaClass == null) return false;
try {
for (java.lang.reflect.Field f : javaClass.getFields()) {
if (f.getName().equals(fieldName)) {
return true;
if (javaClass != null) {
try {
for (java.lang.reflect.Field f : javaClass.getFields()) {
if (f.getName().equals(fieldName)) {
return true;
}
}
} catch (Exception e) {
// Security or linkage error
}
} catch (Exception e) {
// Security or linkage error
return false;
}
if (rawType != null && rawType != this) return rawType.hasField(fieldName);
return false;
}
public boolean hasEnumConstant(String constantName) {
if (javaClass == null || !javaClass.isEnum()) return false;
/**
* Returns all enum constants for this type as a name-to-EnumConstantInfo map.
* For Java enum types, builds the map via reflection. For script-defined enums,
* ScriptTypeInfo overrides this to return its stored constants directly.
*
* @return Map of constant name to EnumConstantInfo (empty if not an enum or no constants)
*/
public Map<String, EnumConstantInfo> getEnumConstants() {
if (javaClass == null || !javaClass.isEnum()) {
if (rawType != null && rawType != this) {
return rawType.getEnumConstants();
}
return Collections.emptyMap();
}
Map<String, EnumConstantInfo> result = new LinkedHashMap<>();
try {
Object[] constants = javaClass.getEnumConstants();
for (Object constant : constants) {
if (constant.toString().equals(constantName))
return true;
String name = constant.toString();
result.put(name, EnumConstantInfo.fromReflection(name, this, null));
}
} catch (Exception e) {
// Security or linkage error
// Security or linkage error — return whatever we have so far
}
return result;
}
public boolean hasEnumConstant(String constantName) {
if (javaClass != null && javaClass.isEnum()) {
try {
Object[] constants = javaClass.getEnumConstants();
for (Object constant : constants) {
if (constant.toString().equals(constantName))
return true;
}
} catch (Exception e) {
// Security or linkage error
}
return false;
}
if (rawType != null && rawType != this) return rawType.hasEnumConstant(constantName);
return false;
}
@@ -944,16 +1132,19 @@ public class TypeInfo {
* Get an enum constant by name.
*/
public EnumConstantInfo getEnumConstant(String constantName) {
if (javaClass == null || !javaClass.isEnum()) return null;
try {
Object[] constants = javaClass.getEnumConstants();
for (Object constant : constants) {
if (constant.toString().equals(constantName))
return EnumConstantInfo.fromReflection(constantName, this, null);
if (javaClass != null && javaClass.isEnum()) {
try {
Object[] constants = javaClass.getEnumConstants();
for (Object constant : constants) {
if (constant.toString().equals(constantName))
return EnumConstantInfo.fromReflection(constantName, this, null);
}
} catch (Exception e) {
// Security or linkage error
}
} catch (Exception e) {
// Security or linkage error
return null;
}
if (rawType != null && rawType != this) return rawType.getEnumConstant(constantName);
return null;
}
@@ -976,6 +1167,13 @@ public class TypeInfo {
} catch (Exception e) {
// Security or linkage error
}
} else if (rawType != null && rawType != this) {
MethodInfo rawMethod = rawType.getMethodInfo(methodName);
if (rawMethod != null && isParameterized()) {
Map<String, TypeInfo> bindings = TypeSubstitutor.createBindingsFromReceiver(this);
if (!bindings.isEmpty()) return rawMethod.substituteTypeParams(bindings);
}
return rawMethod;
}
TypeInfo obj = TypeInfo.object();
if (obj != this) return obj.getMethodInfo(methodName);
@@ -1012,6 +1210,18 @@ public class TypeInfo {
// Security or linkage error
}
if (!overloads.isEmpty()) return overloads;
} else if (rawType != null && rawType != this) {
java.util.List<MethodInfo> rawOverloads = rawType.getAllMethodOverloads(methodName);
if (!rawOverloads.isEmpty() && isParameterized()) {
Map<String, TypeInfo> bindings = TypeSubstitutor.createBindingsFromReceiver(this);
if (!bindings.isEmpty()) {
for (MethodInfo m : rawOverloads) {
overloads.add(m.substituteTypeParams(bindings));
}
return overloads;
}
}
return rawOverloads;
}
TypeInfo obj = TypeInfo.object();
if (obj != this) return obj.getAllMethodOverloads(methodName);
@@ -1078,16 +1288,25 @@ public class TypeInfo {
return null;
}
if (javaClass == null) return null;
try {
for (java.lang.reflect.Field f : javaClass.getFields()) {
if (f.getName().equals(fieldName)) {
// Create a synthetic FieldInfo from reflection
return FieldInfo.fromReflection(f, this);
if (javaClass != null) {
try {
for (java.lang.reflect.Field f : javaClass.getFields()) {
if (f.getName().equals(fieldName)) {
return FieldInfo.fromReflection(f, this);
}
}
} catch (Exception e) {
// Security or linkage error
}
} catch (Exception e) {
// Security or linkage error
return null;
}
if (rawType != null && rawType != this) {
FieldInfo rawField = rawType.getFieldInfo(fieldName);
if (rawField != null && isParameterized()) {
Map<String, TypeInfo> bindings = TypeSubstitutor.createBindingsFromReceiver(this);
if (!bindings.isEmpty()) return rawField.substituteTypeParams(bindings);
}
return rawField;
}
return null;
}
@@ -1206,7 +1425,19 @@ public class TypeInfo {
return null;
}
// Not a functional interface
// Not a functional interface — try rawType for parameterized script types
if (rawType != null && rawType != this) {
MethodInfo rawSAM = rawType.getSingleAbstractMethod();
if (rawSAM != null && isParameterized()) {
Map<String, TypeInfo> bindings = TypeSubstitutor.createBindingsFromReceiver(this);
if (!bindings.isEmpty()) {
cachedSAM = rawSAM.substituteTypeParams(bindings);
return cachedSAM;
}
}
cachedSAM = rawSAM;
return cachedSAM;
}
cachedSAM = null;
return null;