- Added Java.type("") for JS API

- Added synthetic types (non-existant in java classloader and JS API, but custom defined like Nashorn's Java.type/extends/etc..
This commit is contained in:
bigguy345 committed 2026-01-08 06:56:41 +02:00
1 parent 5790679048
commit 176a3b6d50
15 files changed
+1131 -6

No files matched your search

@@ -5,6 +5,9 @@ import noppes.npcs.client.gui.util.script.interpreter.js_parser.JSFieldInfo;
import noppes.npcs.client.gui.util.script.interpreter.js_parser.JSMethodInfo;
import noppes.npcs.client.gui.util.script.interpreter.method.MethodInfo;
import noppes.npcs.client.gui.util.script.interpreter.type.TypeInfo;
import noppes.npcs.client.gui.util.script.interpreter.type.synthetic.SyntheticField;
import noppes.npcs.client.gui.util.script.interpreter.type.synthetic.SyntheticMethod;
import noppes.npcs.client.gui.util.script.interpreter.type.synthetic.SyntheticParameter;
/**
* Represents a single autocomplete suggestion.
@@ -321,6 +324,79 @@ public class AutocompleteItem implements Comparable<AutocompleteItem> {
);
}
/**
* Create from a synthetic method (e.g., Nashorn built-in like Java.type).
*/
public static AutocompleteItem fromSyntheticMethod(
SyntheticMethod method,
TypeInfo containingType) {
String name = method.name;
StringBuilder insertText = new StringBuilder(name);
insertText.append("(");
insertText.append(")");
// Build display name with parameters - use simple names for types
StringBuilder displayName = new StringBuilder(name);
displayName.append("(");
for (int i = 0; i < method.parameters.size(); i++) {
if (i > 0) displayName.append(", ");
SyntheticParameter param =
method.parameters.get(i);
// Use simple name for display (e.g., "Class" instead of "java.lang.Class")
String simpleTypeName = getSimpleTypeName(param.typeName);
displayName.append(simpleTypeName).append(" ").append(param.name);
}
displayName.append(")");
// Use simple name for return type display too
String simpleReturnType = getSimpleTypeName(method.returnType);
return new AutocompleteItem(
displayName.toString(),
name, // Search against just the method name
insertText.toString(),
Kind.METHOD,
simpleReturnType,
method.getSignature(),
method.documentation,
method,
false,
false,
null,
0 // Synthetic types are at depth 0
);
}
/**
* Get simple name from a fully-qualified type name.
*/
private static String getSimpleTypeName(String typeName) {
if (typeName == null) return "void";
int lastDot = typeName.lastIndexOf('.');
return lastDot >= 0 ? typeName.substring(lastDot + 1) : typeName;
}
/**
* Create from a synthetic field (e.g., Nashorn built-in).
*/
public static AutocompleteItem fromSyntheticField(
SyntheticField field) {
return new AutocompleteItem(
field.name,
field.name, // searchName same as display name
field.name,
Kind.FIELD,
field.typeName,
field.typeName + " " + field.name,
field.documentation,
field,
false,
false,
null,
0 // Synthetic types are at depth 0
);
}
// ==================== HELPER METHODS ====================
private static String buildMethodSignature(MethodInfo method) {
@@ -1,9 +1,7 @@
package noppes.npcs.client.gui.util.script.autocomplete;
import noppes.npcs.client.gui.util.script.interpreter.ScriptDocument;
import noppes.npcs.client.gui.util.script.interpreter.field.FieldInfo;
import noppes.npcs.client.gui.util.script.interpreter.js_parser.*;
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.TypeChecker;
import noppes.npcs.client.gui.util.script.interpreter.type.TypeInfo;
@@ -46,6 +44,14 @@ public class JSAutocompleteProvider extends JavaAutocompleteProvider {
return;
}
// Check for synthetic types first (e.g., Nashorn Java object, custom types)
SyntheticType syntheticType =
document.getTypeResolver().getSyntheticType(receiverType.getSimpleName());
if (syntheticType != null) {
addSyntheticTypeSuggestions(syntheticType, items);
return;
}
// If this is a Java type (has a Java class), delegate to parent's Java handling
// This preserves static/instance context, modifiers, and all Java-specific behavior
Class<?> javaClass = receiverType.getJavaClass();
@@ -63,6 +69,23 @@ public class JSAutocompleteProvider extends JavaAutocompleteProvider {
}
}
/**
* Add autocomplete suggestions for a synthetic type's methods and fields.
*/
private void addSyntheticTypeSuggestions(SyntheticType syntheticType, List<AutocompleteItem> items) {
// Add methods
for (SyntheticMethod method : syntheticType.getMethods()) {
AutocompleteItem item = AutocompleteItem.fromSyntheticMethod(method, syntheticType.getTypeInfo());
items.add(item);
}
// Add fields
for (SyntheticField field : syntheticType.getFields()) {
AutocompleteItem item = AutocompleteItem.fromSyntheticField(field);
items.add(item);
}
}
/**
* Recursively add methods from a type and its parents.
* @param type The current JS type to get methods from (changes as we walk up inheritance)
@@ -20,6 +20,9 @@ import noppes.npcs.client.gui.util.script.interpreter.token.Token;
import noppes.npcs.client.gui.util.script.interpreter.token.TokenErrorMessage;
import noppes.npcs.client.gui.util.script.interpreter.token.TokenType;
import noppes.npcs.client.gui.util.script.interpreter.type.*;
import noppes.npcs.client.gui.util.script.interpreter.type.synthetic.SyntheticField;
import noppes.npcs.client.gui.util.script.interpreter.type.synthetic.SyntheticMethod;
import noppes.npcs.client.gui.util.script.interpreter.type.synthetic.SyntheticType;
import java.util.*;
import java.util.regex.Matcher;
@@ -2661,7 +2664,39 @@ public class ScriptDocument {
MethodInfo resolvedMethod = null;
if (receiverType != null) {
// Check for method existence using hierarchy search if it's a ScriptTypeInfo
// Check for synthetic types first (JavaScript only)
SyntheticType syntheticType = null;
if (isJavaScript()) {
syntheticType = typeResolver.getSyntheticType(receiverType.getSimpleName());
}
if (syntheticType != null && syntheticType.hasMethod(methodName)) {
// Synthetic type method call (e.g., Java.type())
resolvedMethod = syntheticType.getMethodInfo(methodName);
// Check for dynamic return type resolution (like Java.type("className"))
SyntheticMethod synMethod = syntheticType.getMethod(methodName);
TypeInfo dynamicReturnType = null;
if (synMethod != null && synMethod.returnTypeResolver != null) {
String argsText = text.substring(openParen + 1, closeParen);
String[] strArgs = TypeResolver.parseStringArguments(argsText);
dynamicReturnType = synMethod.returnTypeResolver.resolve(strArgs);
}
MethodCallInfo callInfo = new MethodCallInfo(methodName, nameStart, nameEnd, openParen,
closeParen, arguments, receiverType, resolvedMethod, false);
// Set the actual resolved return type for downstream type resolution
if (dynamicReturnType != null) {
callInfo.setResolvedReturnType(dynamicReturnType);
}
callInfo.validate();
methodCalls.add(callInfo);
marks.add(new ScriptLine.Mark(nameStart, nameEnd, TokenType.METHOD_CALL, callInfo));
continue;
}
// Regular type - check for method existence using hierarchy search if it's a ScriptTypeInfo
boolean hasMethod = false;
if (receiverType instanceof ScriptTypeInfo) {
hasMethod = ((ScriptTypeInfo) receiverType).hasMethodInHierarchy(methodName);
@@ -3496,6 +3531,42 @@ public class ScriptDocument {
}
}
/**
* Resolve a chain segment on a synthetic type (like Nashorn's Java object).
*/
/**
* Resolve a chain segment for a synthetic type (method call or field access).
* Handles dynamic return type resolution for methods like Java.type().
*/
private TypeInfo resolveSyntheticChainSegment(SyntheticType syntheticType, ChainSegment segment) {
if (segment.isMethodCall) {
SyntheticMethod method = syntheticType.getMethod(segment.name);
if (method != null) {
// For methods with dynamic return type resolvers (like Java.type),
// extract string arguments and resolve
if (method.returnTypeResolver != null && segment.arguments != null) {
String[] args = TypeResolver.parseStringArguments(segment.arguments);
TypeInfo resolved = method.returnTypeResolver.resolve(args);
if (resolved != null) {
return resolved;
}
}
// Fall back to static return type
TypeInfo returnType = typeResolver.resolve(method.returnType);
return returnType != null ? returnType : TypeInfo.unresolved(method.returnType, method.returnType);
}
} else {
SyntheticField field = syntheticType.getField(segment.name);
if (field != null) {
TypeInfo fieldType = typeResolver.resolve(field.typeName);
return fieldType != null ? fieldType : TypeInfo.unresolved(field.typeName, field.typeName);
}
}
return null;
}
// ==================== OPERATOR EXPRESSION RESOLUTION ====================
/**
@@ -5023,6 +5094,16 @@ public class ScriptDocument {
if (isInImportOrPackage(start))
continue;
// For JavaScript, first check synthetic types (Nashorn built-ins like Java, print, etc.)
if (isJavaScript() && typeResolver.isSyntheticType(className)) {
SyntheticType syntheticType = typeResolver.getSyntheticType(className);
// Mark as IMPORTED_CLASS since it's a type reference
// Pass the TypeInfo (which the hover system can display)
marks.add(new ScriptLine.Mark(m.start(1), m.end(1), TokenType.IMPORTED_CLASS,
syntheticType.getTypeInfo()));
continue;
}
// Try to resolve the class
TypeInfo info = resolveType(className);
if (info != null && info.isResolved()) {
@@ -135,6 +135,14 @@ public final class FieldInfo {
return new FieldInfo(name, Scope.PARAMETER, type, -1, true, null, null, -1, -1, 0, null);
}
/**
* Create a FieldInfo for a synthetic/external field.
* Used for built-in types like Nashorn's Java object.
*/
public static FieldInfo external(String name, TypeInfo type, String documentation, int modifiers) {
return new FieldInfo(name, Scope.GLOBAL, type, -1, true, null, documentation, -1, -1, modifiers, null);
}
/**
* Create a FieldInfo from reflection data for a class field.
*/
@@ -197,6 +197,29 @@ public class MethodCallInfo {
this.expectedType = expectedType;
}
// Dynamically resolved return type (e.g., for Java.type() which returns ClassTypeInfo)
private TypeInfo resolvedReturnType;
/**
* Get the resolved return type of this method call.
* This may differ from resolvedMethod.getReturnType() for methods with dynamic return types.
* @return The resolved return type, or the method's declared return type if not dynamically resolved
*/
public TypeInfo getResolvedReturnType() {
if (resolvedReturnType != null) {
return resolvedReturnType;
}
return resolvedMethod != null ? resolvedMethod.getReturnType() : null;
}
/**
* Set a dynamically resolved return type.
* Used for methods like Java.type() where the return type depends on the arguments.
*/
public void setResolvedReturnType(TypeInfo returnType) {
this.resolvedReturnType = returnType;
}
public boolean isConstructor() {
return isConstructor;
}
@@ -127,6 +127,15 @@ public final class MethodInfo {
return new MethodInfo(name, null, null, params, -1, -1, -1, -1, -1, false, false, 0, null, null);
}
/**
* Create a MethodInfo for a synthetic/external method.
* Used for built-in types like Nashorn's Java object.
*/
public static MethodInfo external(String name, TypeInfo returnType, TypeInfo containingType,
List<FieldInfo> params, int modifiers, String documentation) {
return new MethodInfo(name, returnType, containingType, params, -1, -1, -1, -1, -1, true, false, modifiers, documentation, null);
}
/**
* Create a MethodInfo from reflection data.
* Used when resolving method calls on known types.
@@ -0,0 +1,73 @@
package noppes.npcs.client.gui.util.script.interpreter.type;
/**
* Represents a reference to a Java Class itself (not an instance).
* Used for patterns like: var File = Java.type("java.io.File");
*
* When you have a ClassTypeInfo:
* - Accessing members should show ONLY static members (File.listRoots())
* - Using 'new' should return an instance of the wrapped class (new File("path"))
*
* This is distinct from a regular TypeInfo which represents an instance of that type.
*/
public class ClassTypeInfo extends TypeInfo {
private final TypeInfo instanceType; // The type you get when you do 'new' on this class
/**
* Create a ClassTypeInfo wrapping a Java class.
*
* @param javaClass The Java class this represents (e.g., java.io.File.class)
*/
public ClassTypeInfo(Class<?> javaClass) {
super(
javaClass.getSimpleName(),
javaClass.getName(),
javaClass.getPackage() != null ? javaClass.getPackage().getName() : "",
javaClass.isInterface() ? Kind.INTERFACE : (javaClass.isEnum() ? Kind.ENUM : Kind.CLASS),
javaClass,
true,
null,
true // subclass marker
);
this.instanceType = TypeInfo.fromClass(javaClass);
}
/**
* Create a ClassTypeInfo from an existing TypeInfo.
*/
public ClassTypeInfo(TypeInfo instanceType) {
super(
instanceType.getSimpleName(),
instanceType.getFullName(),
instanceType.getPackageName(),
instanceType.getKind(),
instanceType.getJavaClass(),
instanceType.isResolved(),
instanceType.getEnclosingType(),
true // subclass marker
);
this.instanceType = instanceType;
}
/**
* Returns true - this TypeInfo represents a class reference, not an instance.
*/
@Override
public boolean isClassReference() {
return true;
}
/**
* Get the TypeInfo for instances of this class.
* This is what you get when you call 'new' on this class.
*/
public TypeInfo getInstanceType() {
return instanceType;
}
@Override
public String toString() {
return "Class<" + getSimpleName() + ">";
}
}
@@ -0,0 +1,282 @@
package noppes.npcs.client.gui.util.script.interpreter.type;
import noppes.npcs.client.gui.util.script.interpreter.type.synthetic.SyntheticMethod;
import noppes.npcs.client.gui.util.script.interpreter.type.synthetic.SyntheticParameter;
import noppes.npcs.client.gui.util.script.interpreter.type.synthetic.SyntheticTypeBuilder;
import noppes.npcs.client.gui.util.script.interpreter.type.synthetic.SyntheticType;
import java.util.*;
/**
* Registry for Nashorn built-in types and global functions.
*
* <p>Nashorn provides several built-in objects and functions:</p>
* <ul>
* <li><b>Java</b> - Object for Java interop: type(), extend(), from(), to()</li>
* <li><b>Packages</b> - Root of package hierarchy for accessing Java packages</li>
* <li><b>JavaImporter</b> - Creates scope with imported Java packages</li>
* <li><b>print()</b> - Global print function</li>
* <li><b>load()</b> - Load and execute a script</li>
* </ul>
*/
public class NashornBuiltins {
private static NashornBuiltins instance;
private final Map<String, SyntheticType> builtinTypes = new LinkedHashMap<>();
private final Map<String, SyntheticMethod> globalFunctions = new LinkedHashMap<>();
private NashornBuiltins() {
initializeBuiltins();
}
public static NashornBuiltins getInstance() {
if (instance == null) {
instance = new NashornBuiltins();
}
return instance;
}
private void initializeBuiltins() {
// ==================== Java object ====================
SyntheticType javaType = new SyntheticTypeBuilder("Java")
.documentation("Nashorn's Java interop object for accessing Java types and utilities.")
.addMethod("type")
.parameter("className", "String", "Fully-qualified Java class name")
.returns("java.lang.Class")
.returnsResolved(args -> {
if (args != null && args.length > 0 && args[0] != null) {
String className = args[0];
// Remove quotes if present
if (className.startsWith("\"") && className.endsWith("\"")) {
className = className.substring(1, className.length() - 1);
} else if (className.startsWith("'") && className.endsWith("'")) {
className = className.substring(1, className.length() - 1);
}
// Try to load the class
TypeInfo resolved = TypeResolver.getInstance().resolve(className);
if (resolved != null && resolved.isResolved()) {
return new ClassTypeInfo(resolved);
}
}
return null;
})
.documentation("Loads a Java class by its fully-qualified name.\n\n" +
"**Usage:**\n" +
"```javascript\n" +
"var File = Java.type(\"java.io.File\");\n" +
"var file = new File(\"path/to/file\");\n" +
"```\n\n" +
"@param className The fully-qualified Java class name\n" +
"@returns The Java class reference (use with 'new' to create instances)")
.done()
.addMethod("extend")
.parameter("type", "java.lang.Class", "Java class or interface to extend/implement")
.returns("java.lang.Class")
.documentation("Creates a subclass or implementation of a Java class or interface.\n\n" +
"**Usage:**\n" +
"```javascript\n" +
"var MyRunnable = Java.extend(Java.type(\"java.lang.Runnable\"), {\n" +
" run: function() {\n" +
" print(\"Running!\");\n" +
" }\n" +
"});\n" +
"```\n\n" +
"@param type The Java class or interface to extend\n" +
"@returns A new class that extends/implements the given type")
.done()
.addMethod("from")
.parameter("javaArray", "Object", "A Java array or Collection")
.returns("Array")
.documentation("Converts a Java array or Collection to a JavaScript array.\n\n" +
"**Usage:**\n" +
"```javascript\n" +
"var jsList = Java.from(javaArrayList);\n" +
"jsList.forEach(function(item) { print(item); });\n" +
"```\n\n" +
"@param javaArray A Java array or java.util.Collection\n" +
"@returns A JavaScript array containing the elements")
.done()
.addMethod("to")
.parameter("jsArray", "Array", "A JavaScript array")
.parameter("javaType", "java.lang.Class", "The target Java array type")
.returns("Object")
.documentation("Converts a JavaScript array to a Java array of the specified type.\n\n" +
"**Usage:**\n" +
"```javascript\n" +
"var jsArray = [1, 2, 3];\n" +
"var intArray = Java.to(jsArray, \"int[]\");\n" +
"```\n\n" +
"@param jsArray A JavaScript array\n" +
"@param javaType The target Java array type (e.g., \"int[]\", \"java.lang.String[]\")\n" +
"@returns A Java array of the specified type")
.done()
.addMethod("super")
.parameter("object", "Object", "A Java object created via Java.extend()")
.returns("Object")
.documentation("Gets a reference to the super class for calling super methods.\n\n" +
"**Usage:**\n" +
"```javascript\n" +
"var MyList = Java.extend(Java.type(\"java.util.ArrayList\"), {\n" +
" add: function(e) {\n" +
" print(\"Adding: \" + e);\n" +
" return Java.super(this).add(e);\n" +
" }\n" +
"});\n" +
"```\n\n" +
"@param object An extended Java object\n" +
"@returns A reference to call super methods")
.done()
.addMethod("synchronized")
.parameter("func", "Function", "The function to synchronize")
.parameter("lock", "Object", "The object to synchronize on")
.returns("Function")
.documentation("Wraps a function to execute synchronized on a given object.\n\n" +
"**Usage:**\n" +
"```javascript\n" +
"var syncFunc = Java.synchronized(function() {\n" +
" // Thread-safe code here\n" +
"}, lockObject);\n" +
"```\n\n" +
"@param func The function to synchronize\n" +
"@param lock The object to use as the monitor\n" +
"@returns A synchronized version of the function")
.done()
.build();
builtinTypes.put("Java", javaType);
// ==================== Global print function ====================
SyntheticType printFunc = new SyntheticTypeBuilder("print")
.addMethod("print")
.parameter("message", "Object", "The message to print")
.returns("void")
.documentation("Prints a message to standard output.\n\n" +
"**Usage:**\n" +
"```javascript\n" +
"print(\"Hello, world!\");\n" +
"print(myObject);\n" +
"```")
.done()
.build();
globalFunctions.put("print", printFunc.getMethod("print"));
// ==================== Global load function ====================
SyntheticType loadFunc = new SyntheticTypeBuilder("load")
.addMethod("load")
.parameter("script", "String", "Path or URL to the script")
.returns("Object")
.documentation("Loads and executes a script file or URL.\n\n" +
"**Usage:**\n" +
"```javascript\n" +
"load(\"./myScript.js\");\n" +
"load(\"http://example.com/script.js\");\n" +
"```\n\n" +
"@param script Path to a local script or URL\n" +
"@returns The result of the script execution")
.done()
.build();
globalFunctions.put("load", loadFunc.getMethod("load"));
}
/**
* Get a built-in type by name.
* @param name The type name (e.g., "Java")
* @return The SyntheticType or null if not found
*/
public SyntheticType getBuiltinType(String name) {
return builtinTypes.get(name);
}
/**
* Check if a name is a built-in type.
*/
public boolean isBuiltinType(String name) {
return builtinTypes.containsKey(name);
}
/**
* Get a global function by name.
*/
public SyntheticMethod getGlobalFunction(String name) {
return globalFunctions.get(name);
}
/**
* Check if a name is a global function.
*/
public boolean isGlobalFunction(String name) {
return globalFunctions.containsKey(name);
}
/**
* Get all built-in type names.
*/
public Set<String> getBuiltinTypeNames() {
return Collections.unmodifiableSet(builtinTypes.keySet());
}
/**
* Get all global function names.
*/
public Set<String> getGlobalFunctionNames() {
return Collections.unmodifiableSet(globalFunctions.keySet());
}
/**
* Get all built-in types.
*/
public Collection<SyntheticType> getAllBuiltinTypes() {
return Collections.unmodifiableCollection(builtinTypes.values());
}
/**
* Get all global functions as SyntheticTypes.
* Each function is wrapped in its own SyntheticType for the registry.
*/
public Map<String, SyntheticType> getAllGlobalFunctions() {
Map<String, SyntheticType> result = new LinkedHashMap<>();
// Wrap each global function in a minimal SyntheticType
for (Map.Entry<String, SyntheticMethod> entry : globalFunctions.entrySet()) {
String name = entry.getKey();
SyntheticMethod method = entry.getValue();
// Create a synthetic type that just contains this one global function
SyntheticTypeBuilder builder = new SyntheticTypeBuilder(name);
builder.documentation("Global " + name + " function");
// Add the method
SyntheticTypeBuilder.MethodBuilder methodBuilder = builder.addMethod(name);
for (SyntheticParameter param : method.parameters) {
methodBuilder.parameter(param.name, param.typeName, param.documentation);
}
methodBuilder.returns(method.returnType);
if (method.documentation != null && !method.documentation.isEmpty()) {
methodBuilder.documentation(method.documentation);
}
methodBuilder.done();
result.put(name, builder.build());
}
return result;
}
/**
* Resolve the return type of Java.type() given the class name argument.
*/
public TypeInfo resolveJavaType(String className) {
SyntheticType javaBuiltin = builtinTypes.get("Java");
if (javaBuiltin != null) {
return javaBuiltin.resolveMethodReturnType("type", new String[]{className});
}
return null;
}
}
@@ -240,6 +240,13 @@ public class TypeInfo {
public boolean isEnum() {return kind == Kind.ENUM;}
public boolean isPrimitive() {return javaClass != null && javaClass.isPrimitive();}
/**
* Returns true if this TypeInfo represents a Class reference (not an instance).
* For example, Java.type("java.io.File") returns a Class reference.
* Override in ClassTypeInfo to return true.
*/
public boolean isClassReference() { return false; }
/**
* Get the appropriate TokenType for highlighting this type.
*/
@@ -1,11 +1,12 @@
package noppes.npcs.client.gui.util.script.interpreter.type;
import noppes.npcs.client.gui.util.script.PackageFinder;
import noppes.npcs.client.gui.util.script.interpreter.js_parser.JSFieldInfo;
import noppes.npcs.client.gui.util.script.interpreter.js_parser.JSMethodInfo;
import noppes.npcs.client.gui.util.script.interpreter.ScriptDocument;
import noppes.npcs.client.gui.util.script.interpreter.field.FieldInfo;
import noppes.npcs.client.gui.util.script.interpreter.js_parser.JSTypeInfo;
import noppes.npcs.client.gui.util.script.interpreter.js_parser.JSTypeRegistry;
import noppes.npcs.client.gui.util.script.interpreter.token.TokenType;
import noppes.npcs.client.gui.util.script.interpreter.type.synthetic.SyntheticType;
import java.util.*;
@@ -29,6 +30,9 @@ public class TypeResolver {
// JavaScript type registry (lazily initialized)
private JSTypeRegistry jsTypeRegistry;
// Synthetic type registry (e.g., Nashorn built-ins)
private final Map<String, SyntheticType> syntheticTypes = new LinkedHashMap<>();
// Auto-imported java.lang classes
public static final Set<String> JAVA_LANG_CLASSES = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
"Object", "String", "Class", "System", "Math", "Integer", "Double", "Float", "Long", "Short", "Byte",
@@ -70,6 +74,60 @@ public class TypeResolver {
validPackages.add("java.lang");
validPackages.add("java.util");
validPackages.add("java.io");
// Initialize built-in synthetic types
initializeSyntheticTypes();
}
/**
* Initialize built-in synthetic types (e.g., Nashorn built-ins).
*/
private void initializeSyntheticTypes() {
// Register all Nashorn built-in types (Java, etc.)
for (SyntheticType type : NashornBuiltins.getInstance().getAllBuiltinTypes()) {
syntheticTypes.put(type.getName(), type);
}
// Register all Nashorn global functions (print, load, etc.)
Map<String, SyntheticType> nashornGlobals = NashornBuiltins.getInstance().getAllGlobalFunctions();
syntheticTypes.putAll(nashornGlobals);
}
// ==================== SYNTHETIC TYPE REGISTRY ====================
/**
* Register a synthetic type for use in scripts.
* @param name The type name (e.g., "Java", "MyCustomType")
* @param type The synthetic type definition
*/
public void registerSyntheticType(String name, SyntheticType type) {
syntheticTypes.put(name, type);
}
/**
* Check if a name is a known synthetic type.
* @param name The type name to check
* @return true if the name is a registered synthetic type
*/
public boolean isSyntheticType(String name) {
return syntheticTypes.containsKey(name);
}
/**
* Get a synthetic type by name.
* @param name The type name
* @return The synthetic type, or null if not found
*/
public SyntheticType getSyntheticType(String name) {
return syntheticTypes.get(name);
}
/**
* Get all registered synthetic types.
* @return Collection of all synthetic types
*/
public Collection<SyntheticType> getAllSyntheticTypes() {
return syntheticTypes.values();
}
// ==================== JS TYPE REGISTRY ACCESS ====================
@@ -124,6 +182,34 @@ public class TypeResolver {
}
// ==================== UNIFIED TYPE RESOLUTION ====================
/**
* General-purpose type resolution.
* Tries JS types first, then falls back to Java types.
*
* @param typeName The type name (can be simple or fully-qualified)
* @return TypeInfo for the resolved type, or null if not found
*/
public TypeInfo resolve(String typeName) {
if (typeName == null || typeName.isEmpty()) {
return null;
}
// Try JS resolution first (handles primitives, .d.ts types)
TypeInfo jsType = resolveJSType(typeName);
if (jsType != null && jsType.isResolved()) {
return jsType;
}
// Try full name resolution
TypeInfo fullType = resolveFullName(typeName);
if (fullType != null && fullType.isResolved()) {
return fullType;
}
// Return unresolved or null
return jsType; // Will be unresolved TypeInfo or null
}
/**
* Resolve a type name for JavaScript context.
@@ -148,6 +234,12 @@ public class TypeResolver {
// Strip array brackets for base type resolution
boolean isArray = jsTypeName.endsWith("[]");
String baseName = isArray ? jsTypeName.substring(0, jsTypeName.length() - 2) : jsTypeName;
// Check synthetic types (Nashorn built-ins, custom types, etc.)
if (isSyntheticType(baseName)) {
SyntheticType syntheticType = getSyntheticType(baseName);
return syntheticType.getTypeInfo();
}
// Handle "Java." prefix - convert to actual Java type
// e.g., "Java.java.io.File" -> "java.io.File"
@@ -0,0 +1,33 @@
package noppes.npcs.client.gui.util.script.interpreter.type.synthetic;
import noppes.npcs.client.gui.util.script.interpreter.field.FieldInfo;
import noppes.npcs.client.gui.util.script.interpreter.type.TypeInfo;
import noppes.npcs.client.gui.util.script.interpreter.type.TypeResolver;
import java.lang.reflect.Modifier;
public class SyntheticField {
public final String name;
public final String typeName;
public final String documentation;
public final boolean isStatic;
SyntheticField(String name, String typeName, String documentation, boolean isStatic) {
this.name = name;
this.typeName = typeName;
this.documentation = documentation;
this.isStatic = isStatic;
}
public FieldInfo toFieldInfo() {
TypeInfo type = TypeResolver.getInstance().resolve(typeName);
if (type == null) {
type = TypeInfo.unresolved(typeName, typeName);
}
int modifiers = Modifier.PUBLIC;
if (isStatic) {
modifiers |= Modifier.STATIC;
}
return FieldInfo.external(name, type, documentation, modifiers);
}
}
@@ -0,0 +1,136 @@
package noppes.npcs.client.gui.util.script.interpreter.type.synthetic;
import noppes.npcs.client.gui.util.script.interpreter.field.FieldInfo;
import noppes.npcs.client.gui.util.script.interpreter.jsdoc.JSDocInfo;
import noppes.npcs.client.gui.util.script.interpreter.jsdoc.JSDocParamTag;
import noppes.npcs.client.gui.util.script.interpreter.jsdoc.JSDocReturnTag;
import noppes.npcs.client.gui.util.script.interpreter.method.MethodInfo;
import noppes.npcs.client.gui.util.script.interpreter.type.TypeInfo;
import noppes.npcs.client.gui.util.script.interpreter.type.TypeResolver;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class SyntheticMethod {
public final String name;
public final String returnType;
public final List<SyntheticParameter> parameters;
public final String documentation;
public final boolean isStatic;
public final SyntheticTypeBuilder.ReturnTypeResolver returnTypeResolver;
SyntheticMethod(String name, String returnType, List<SyntheticParameter> parameters,
String documentation, boolean isStatic,
SyntheticTypeBuilder.ReturnTypeResolver returnTypeResolver) {
this.name = name;
this.returnType = returnType;
this.parameters = Collections.unmodifiableList(new ArrayList<>(parameters));
this.documentation = documentation;
this.isStatic = isStatic;
this.returnTypeResolver = returnTypeResolver;
}
/**
* Create a MethodInfo from this synthetic method.
*/
public MethodInfo toMethodInfo(TypeInfo containingType) {
List<FieldInfo> paramInfos = new ArrayList<>();
for (SyntheticParameter param : parameters) {
TypeInfo paramType = TypeResolver.getInstance().resolve(param.typeName);
if (paramType == null) {
paramType = TypeInfo.unresolved(param.typeName, param.typeName);
}
paramInfos.add(FieldInfo.parameter(param.name, paramType, -1, null));
}
TypeInfo returnTypeInfo = TypeResolver.getInstance().resolve(returnType);
if (returnTypeInfo == null) {
returnTypeInfo = TypeInfo.unresolved(returnType, returnType);
}
int modifiers = Modifier.PUBLIC;
if (isStatic) {
modifiers |= Modifier.STATIC;
}
MethodInfo methodInfo = MethodInfo.external(name, returnTypeInfo, containingType, paramInfos, modifiers, null);
// Create JSDocInfo from documentation
if (documentation != null && !documentation.isEmpty()) {
JSDocInfo jsDocInfo = createJSDocInfo(returnTypeInfo);
methodInfo.setJSDocInfo(jsDocInfo);
}
return methodInfo;
}
/**
* Create JSDocInfo from the documentation string.
*/
private JSDocInfo createJSDocInfo(TypeInfo returnTypeInfo) {
// Parse the documentation to extract description and separate sections
String[] lines = documentation.split("\\n");
StringBuilder descBuilder = new StringBuilder();
// Extract description (everything before @param or @returns)
for (String line : lines) {
line = line.trim();
if (line.startsWith("@param") || line.startsWith("@returns") || line.startsWith("@return")) {
break;
}
if (!line.isEmpty()) {
if (descBuilder.length() > 0)
descBuilder.append("\n");
descBuilder.append(line);
}
}
JSDocInfo jsDocInfo = new JSDocInfo(documentation, -1, -1);
jsDocInfo.setDescription(descBuilder.toString());
// Add @param tags for each parameter
for (int i = 0; i < parameters.size(); i++) {
SyntheticParameter param = parameters.get(i);
TypeInfo paramType = TypeResolver.getInstance().resolve(param.typeName);
if (paramType == null) {
paramType = TypeInfo.unresolved(param.typeName, param.typeName);
}
JSDocParamTag paramTag = JSDocParamTag.create(
-1, -1, -1, // offsets
param.typeName, paramType, -1, -1, // type info
param.name, -1, -1, // param name
param.documentation // description
);
jsDocInfo.addParamTag(paramTag);
}
// Add @returns tag
JSDocReturnTag returnTag = JSDocReturnTag.create(
"returns", -1, -1, -1, // offsets
returnTypeInfo.getSimpleName(), returnTypeInfo, -1, -1, // type info
null // description extracted from main documentation
);
jsDocInfo.setReturnTag(returnTag);
return jsDocInfo;
}
/**
* Get signature string for display.
*/
public String getSignature() {
StringBuilder sb = new StringBuilder();
sb.append(name).append("(");
for (int i = 0; i < parameters.size(); i++) {
if (i > 0)
sb.append(", ");
SyntheticParameter p = parameters.get(i);
sb.append(p.name).append(": ").append(p.typeName);
}
sb.append("): ").append(returnType);
return sb.toString();
}
}
@@ -0,0 +1,13 @@
package noppes.npcs.client.gui.util.script.interpreter.type.synthetic;
public class SyntheticParameter {
public final String name;
public final String typeName;
public final String documentation;
SyntheticParameter(String name, String typeName, String documentation) {
this.name = name;
this.typeName = typeName;
this.documentation = documentation;
}
}
@@ -0,0 +1,104 @@
package noppes.npcs.client.gui.util.script.interpreter.type.synthetic;
import noppes.npcs.client.gui.util.script.interpreter.method.MethodInfo;
import noppes.npcs.client.gui.util.script.interpreter.type.TypeInfo;
import noppes.npcs.client.gui.util.script.interpreter.type.TypeResolver;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Represents a fully-built synthetic type.
*/
public class SyntheticType {
private final String name;
private final String documentation;
private final Map<String, SyntheticMethod> methods;
private final Map<String, SyntheticField> fields;
private TypeInfo typeInfo;
SyntheticType(String name, String documentation, List<SyntheticMethod> methods,
List<SyntheticField> fields) {
this.name = name;
this.documentation = documentation;
this.methods = new LinkedHashMap<>();
for (SyntheticMethod m : methods) {
this.methods.put(m.name, m);
}
this.fields = new LinkedHashMap<>();
for (SyntheticField f : fields) {
this.fields.put(f.name, f);
}
}
public String getName() {
return name;
}
public String getDocumentation() {
return documentation;
}
public SyntheticMethod getMethod(String methodName) {
return methods.get(methodName);
}
public Collection<SyntheticMethod> getMethods() {
return methods.values();
}
public SyntheticField getField(String fieldName) {
return fields.get(fieldName);
}
public Collection<SyntheticField> getFields() {
return fields.values();
}
public boolean hasMethod(String methodName) {
return methods.containsKey(methodName);
}
public boolean hasField(String fieldName) {
return fields.containsKey(fieldName);
}
/**
* Get or create the TypeInfo for this synthetic type.
*/
public TypeInfo getTypeInfo() {
if (typeInfo == null) {
typeInfo = TypeInfo.resolved(name, name, "", TypeInfo.Kind.CLASS, null);
}
return typeInfo;
}
/**
* Create a MethodInfo for a method in this type.
*/
public MethodInfo getMethodInfo(String methodName) {
SyntheticMethod method = methods.get(methodName);
if (method == null)
return null;
return method.toMethodInfo(getTypeInfo());
}
/**
* Resolve the return type of a method given arguments.
* Used for special methods like Java.type().
*/
public TypeInfo resolveMethodReturnType(String methodName, String[] arguments) {
SyntheticMethod method = methods.get(methodName);
if (method == null)
return null;
if (method.returnTypeResolver != null) {
return method.returnTypeResolver.resolve(arguments);
}
// Fall back to static return type
return TypeResolver.getInstance().resolve(method.returnType);
}
}
@@ -0,0 +1,165 @@
package noppes.npcs.client.gui.util.script.interpreter.type.synthetic;
import noppes.npcs.client.gui.util.script.interpreter.type.TypeInfo;
import java.util.*;
/**
* Builder for creating synthetic (non-reflection-based) types.
* Used for built-in Nashorn objects like 'Java' that have no corresponding Java class.
*
* <p>Usage:</p>
* <pre>
* SyntheticType javaType = new SyntheticTypeBuilder("Java")
* .addMethod("type")
* .parameter("className", "string")
* .returns("Class")
* .documentation("Loads a Java class by fully-qualified name.")
* .done()
* .build();
* </pre>
*/
public class SyntheticTypeBuilder {
private final String name;
private String documentation;
private final List<SyntheticMethod> methods = new ArrayList<>();
private final List<SyntheticField> fields = new ArrayList<>();
public SyntheticTypeBuilder(String name) {
this.name = name;
}
public SyntheticTypeBuilder documentation(String doc) {
this.documentation = doc;
return this;
}
/**
* Start building a method for this type.
*/
public MethodBuilder addMethod(String methodName) {
return new MethodBuilder(this, methodName);
}
/**
* Add a field to this type.
*/
public SyntheticTypeBuilder addField(String fieldName, String typeName, String doc) {
fields.add(new SyntheticField(fieldName, typeName, doc, false));
return this;
}
/**
* Add a static field to this type.
*/
public SyntheticTypeBuilder addStaticField(String fieldName, String typeName, String doc) {
fields.add(new SyntheticField(fieldName, typeName, doc, true));
return this;
}
void addBuiltMethod(SyntheticMethod method) {
methods.add(method);
}
/**
* Build the synthetic type.
*/
public SyntheticType build() {
return new SyntheticType(name, documentation, methods, fields);
}
// ==================== Inner classes ====================
/**
* Builder for methods within a synthetic type.
*/
public static class MethodBuilder {
private final SyntheticTypeBuilder parent;
private final String name;
private final List<SyntheticParameter> parameters = new ArrayList<>();
private String returnType = "void";
private String documentation;
private boolean isStatic = false;
private ReturnTypeResolver returnTypeResolver;
MethodBuilder(SyntheticTypeBuilder parent, String name) {
this.parent = parent;
this.name = name;
}
/**
* Add a parameter to this method.
*/
public MethodBuilder parameter(String paramName, String typeName) {
parameters.add(new SyntheticParameter(paramName, typeName, null));
return this;
}
/**
* Add a parameter with documentation.
*/
public MethodBuilder parameter(String paramName, String typeName, String doc) {
parameters.add(new SyntheticParameter(paramName, typeName, doc));
return this;
}
/**
* Set the return type.
*/
public MethodBuilder returns(String typeName) {
this.returnType = typeName;
return this;
}
/**
* Set a dynamic return type resolver.
* Used for methods like Java.type() where return type depends on arguments.
*/
public MethodBuilder returnsResolved(ReturnTypeResolver resolver) {
this.returnTypeResolver = resolver;
return this;
}
/**
* Set method documentation.
*/
public MethodBuilder documentation(String doc) {
this.documentation = doc;
return this;
}
/**
* Mark this method as static.
*/
public MethodBuilder asStatic() {
this.isStatic = true;
return this;
}
/**
* Finish building this method and return to the type builder.
*/
public SyntheticTypeBuilder done() {
parent.addBuiltMethod(new SyntheticMethod(name, returnType, parameters, documentation, isStatic, returnTypeResolver));
return parent;
}
}
/**
* Functional interface for resolving return types dynamically.
*/
@FunctionalInterface
public interface ReturnTypeResolver {
/**
* Resolve the return type given the arguments passed to the method.
* @param arguments The string arguments (for methods like Java.type("className"))
* @return The resolved TypeInfo, or null if cannot be resolved
*/
TypeInfo resolve(String[] arguments);
}
// ==================== Data classes ====================
// ==================== SyntheticType (the result) ====================
}