mirror of
https://github.com/KAMKEEL/CustomNPC-Plus.git
synced 2026-11-12 21:17:32 +00:00
- 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:
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) {
|
||||
|
||||
+26
-3
@@ -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.
|
||||
*/
|
||||
|
||||
+23
@@ -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() + ">";
|
||||
}
|
||||
}
|
||||
+282
@@ -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.
|
||||
*/
|
||||
|
||||
+94
-2
@@ -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"
|
||||
|
||||
+33
@@ -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);
|
||||
}
|
||||
}
|
||||
+136
@@ -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();
|
||||
}
|
||||
}
|
||||
+13
@@ -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;
|
||||
}
|
||||
}
|
||||
+104
@@ -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);
|
||||
}
|
||||
}
|
||||
+165
@@ -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) ====================
|
||||
}
|
||||
Reference in new issue
Block a user