diff src/nabble/naml/compiler/JavaCommand.java @ 0:7ecd1a4ef557

add content
author Franklin Schmidt <fschmidt@gmail.com>
date Thu, 21 Mar 2019 19:15:52 -0600
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/naml/compiler/JavaCommand.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,155 @@
+package nabble.naml.compiler;
+
+import fschmidt.util.java.Computable;
+import fschmidt.util.java.Interner;
+import fschmidt.util.java.Memoizer;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+
+public final class JavaCommand implements Meaning {
+	final String name;
+	final Method method;
+	final Class scopedType;
+	final CommandSpec cmdSpec;
+	final boolean isStatic;
+	private final String id;
+
+	private JavaCommand(String name,Method method) {
+		this.name = name;
+		this.method = method;
+		this.scopedType = getScopedType();
+		this.cmdSpec = getCommandSpec();
+		this.isStatic = Modifier.isStatic(method.getModifiers());
+		this.id = method.getDeclaringClass().getName() + '.' + name;
+	}
+
+	@Override public String toString() {
+		return "{JavaCommand id=" + id + " method=[" + method + "]}";
+	}
+
+	@Override public String getName() {
+		return name;
+	}
+
+	@Override public String getId() {
+		return id;
+	}
+
+	@Override public Collection<String> getRequiredNamespaces() {
+		return Collections.singleton(JavaNamespace.getNamespace(method.getDeclaringClass()).name);
+	}
+
+	public String addsNamespace() {
+		if( scopedType == null )
+			return null;
+		return JavaNamespace.getNamespace(scopedType).name;
+	}
+
+	public Method getMethod() {
+		return method;
+	}
+
+	private Class getScopedType() {
+		if( !ScopedInterpreter.class.isAssignableFrom(method.getParameterTypes()[1]) )
+			return null;
+		ParameterizedType pt = (ParameterizedType)method.getGenericParameterTypes()[1];
+		return getClass(pt.getActualTypeArguments()[0]);
+	}
+
+	private static Class getClass(Type type) {
+		if( type instanceof ParameterizedType ) {
+			ParameterizedType pt = (ParameterizedType)type;
+			return (Class)pt.getRawType();
+		}
+		return (Class)type;
+	}
+
+	private CommandSpec getCommandSpec() {
+		try {
+			Field f = method.getDeclaringClass().getDeclaredField(method.getName());
+//			f.setAccessible(true);
+			if( !f.getType().equals(CommandSpec.class) )
+				throw new RuntimeException("field "+f+" which matches "+method+" must be a CommandSpec");
+			if( !Modifier.isStatic(f.getModifiers()) )
+				throw new RuntimeException("field "+f+" which matches "+method+" must be static");
+			return (CommandSpec)f.get(null);
+		} catch(IllegalAccessException e) {
+			throw new TemplateRuntimeException(e);
+		} catch(NoSuchFieldException e) {
+			return CommandSpec.EMPTY;
+		}
+	}
+
+	@Override public boolean equals(Object obj) {
+		if( !(obj instanceof JavaCommand) )
+			return false;
+		JavaCommand jc = (JavaCommand)obj;
+		return jc.method.equals(method);
+	}
+
+	@Override public int hashCode() {
+		return method.hashCode();
+	}
+
+	private static final Interner<JavaCommand> interner = new Interner<JavaCommand>();
+
+	private static final Memoizer<Class,Map<String,JavaCommand>> cache = new Memoizer<Class,Map<String,JavaCommand>>(new Computable<Class,Map<String,JavaCommand>>() {
+		public Map<String,JavaCommand> get(Class cls) {
+			Map<String,JavaCommand> map = new HashMap<String,JavaCommand>();
+			for( Method m : cls.getMethods() ) {
+				Command command = m.getAnnotation(Command.class);
+				if( command == null )
+					continue;
+				String name = command.value();
+				if( name.length()==0 )
+					name = m.getName();
+				if( m.getReturnType() != Void.TYPE )
+					throw new RuntimeException("command "+m+" doesn't return void");
+				Class<?>[] params = m.getParameterTypes();
+				if( params.length != 2 )
+					throw new RuntimeException("command "+m+" should have 2 params");
+				if( !params[0].equals(IPrintWriter.class) )
+					throw new RuntimeException("first param of command "+m+" should be of type IPrintWriter");
+				if( !Interpreter.class.isAssignableFrom(params[1]) )
+					throw new RuntimeException("first param of command "+m+" should be of type Interpreter");
+				JavaCommand jc = interner.intern(new JavaCommand(name,m));
+				JavaCommand old = map.put(name,jc);
+				if( old != null )
+					throw new RuntimeException("duplicate commands "+old.method+" and "+m);
+			}
+			return map;
+		}
+	});
+
+	static JavaCommand getJavaCommand(Class cls,String name) {
+		return cache.get(cls).get(name);
+	}
+
+	public String[] getParameterNames() {
+		return getCommandSpec().getParameters().toArray(new String[0]);
+	}
+
+	public String getDotParameterName() {
+		return getCommandSpec().dotParameter;
+	}
+
+	public String[] getRequiredParameterNames() {
+		return getCommandSpec().requiredParameters.toArray(new String[0]);
+	}
+
+	private static final Pattern ID_PTN = Pattern.compile("([^.!]+\\.)+[^.!]+");
+
+	public static boolean isJavaCommandId(String id) {
+		return ID_PTN.matcher(id).matches();
+	}
+}