view 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 source

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();
	}
}