0
|
1 package nabble.naml.compiler;
|
|
2
|
|
3 import fschmidt.util.java.Computable;
|
|
4 import fschmidt.util.java.Interner;
|
|
5 import fschmidt.util.java.Memoizer;
|
|
6
|
|
7 import java.lang.reflect.Field;
|
|
8 import java.lang.reflect.Method;
|
|
9 import java.lang.reflect.Modifier;
|
|
10 import java.lang.reflect.ParameterizedType;
|
|
11 import java.lang.reflect.Type;
|
|
12 import java.util.Collection;
|
|
13 import java.util.Collections;
|
|
14 import java.util.HashMap;
|
|
15 import java.util.Map;
|
|
16 import java.util.regex.Pattern;
|
|
17
|
|
18
|
|
19 public final class JavaCommand implements Meaning {
|
|
20 final String name;
|
|
21 final Method method;
|
|
22 final Class scopedType;
|
|
23 final CommandSpec cmdSpec;
|
|
24 final boolean isStatic;
|
|
25 private final String id;
|
|
26
|
|
27 private JavaCommand(String name,Method method) {
|
|
28 this.name = name;
|
|
29 this.method = method;
|
|
30 this.scopedType = getScopedType();
|
|
31 this.cmdSpec = getCommandSpec();
|
|
32 this.isStatic = Modifier.isStatic(method.getModifiers());
|
|
33 this.id = method.getDeclaringClass().getName() + '.' + name;
|
|
34 }
|
|
35
|
|
36 @Override public String toString() {
|
|
37 return "{JavaCommand id=" + id + " method=[" + method + "]}";
|
|
38 }
|
|
39
|
|
40 @Override public String getName() {
|
|
41 return name;
|
|
42 }
|
|
43
|
|
44 @Override public String getId() {
|
|
45 return id;
|
|
46 }
|
|
47
|
|
48 @Override public Collection<String> getRequiredNamespaces() {
|
|
49 return Collections.singleton(JavaNamespace.getNamespace(method.getDeclaringClass()).name);
|
|
50 }
|
|
51
|
|
52 public String addsNamespace() {
|
|
53 if( scopedType == null )
|
|
54 return null;
|
|
55 return JavaNamespace.getNamespace(scopedType).name;
|
|
56 }
|
|
57
|
|
58 public Method getMethod() {
|
|
59 return method;
|
|
60 }
|
|
61
|
|
62 private Class getScopedType() {
|
|
63 if( !ScopedInterpreter.class.isAssignableFrom(method.getParameterTypes()[1]) )
|
|
64 return null;
|
|
65 ParameterizedType pt = (ParameterizedType)method.getGenericParameterTypes()[1];
|
|
66 return getClass(pt.getActualTypeArguments()[0]);
|
|
67 }
|
|
68
|
|
69 private static Class getClass(Type type) {
|
|
70 if( type instanceof ParameterizedType ) {
|
|
71 ParameterizedType pt = (ParameterizedType)type;
|
|
72 return (Class)pt.getRawType();
|
|
73 }
|
|
74 return (Class)type;
|
|
75 }
|
|
76
|
|
77 private CommandSpec getCommandSpec() {
|
|
78 try {
|
|
79 Field f = method.getDeclaringClass().getDeclaredField(method.getName());
|
|
80 // f.setAccessible(true);
|
|
81 if( !f.getType().equals(CommandSpec.class) )
|
|
82 throw new RuntimeException("field "+f+" which matches "+method+" must be a CommandSpec");
|
|
83 if( !Modifier.isStatic(f.getModifiers()) )
|
|
84 throw new RuntimeException("field "+f+" which matches "+method+" must be static");
|
|
85 return (CommandSpec)f.get(null);
|
|
86 } catch(IllegalAccessException e) {
|
|
87 throw new TemplateRuntimeException(e);
|
|
88 } catch(NoSuchFieldException e) {
|
|
89 return CommandSpec.EMPTY;
|
|
90 }
|
|
91 }
|
|
92
|
|
93 @Override public boolean equals(Object obj) {
|
|
94 if( !(obj instanceof JavaCommand) )
|
|
95 return false;
|
|
96 JavaCommand jc = (JavaCommand)obj;
|
|
97 return jc.method.equals(method);
|
|
98 }
|
|
99
|
|
100 @Override public int hashCode() {
|
|
101 return method.hashCode();
|
|
102 }
|
|
103
|
|
104 private static final Interner<JavaCommand> interner = new Interner<JavaCommand>();
|
|
105
|
|
106 private static final Memoizer<Class,Map<String,JavaCommand>> cache = new Memoizer<Class,Map<String,JavaCommand>>(new Computable<Class,Map<String,JavaCommand>>() {
|
|
107 public Map<String,JavaCommand> get(Class cls) {
|
|
108 Map<String,JavaCommand> map = new HashMap<String,JavaCommand>();
|
|
109 for( Method m : cls.getMethods() ) {
|
|
110 Command command = m.getAnnotation(Command.class);
|
|
111 if( command == null )
|
|
112 continue;
|
|
113 String name = command.value();
|
|
114 if( name.length()==0 )
|
|
115 name = m.getName();
|
|
116 if( m.getReturnType() != Void.TYPE )
|
|
117 throw new RuntimeException("command "+m+" doesn't return void");
|
|
118 Class<?>[] params = m.getParameterTypes();
|
|
119 if( params.length != 2 )
|
|
120 throw new RuntimeException("command "+m+" should have 2 params");
|
|
121 if( !params[0].equals(IPrintWriter.class) )
|
|
122 throw new RuntimeException("first param of command "+m+" should be of type IPrintWriter");
|
|
123 if( !Interpreter.class.isAssignableFrom(params[1]) )
|
|
124 throw new RuntimeException("first param of command "+m+" should be of type Interpreter");
|
|
125 JavaCommand jc = interner.intern(new JavaCommand(name,m));
|
|
126 JavaCommand old = map.put(name,jc);
|
|
127 if( old != null )
|
|
128 throw new RuntimeException("duplicate commands "+old.method+" and "+m);
|
|
129 }
|
|
130 return map;
|
|
131 }
|
|
132 });
|
|
133
|
|
134 static JavaCommand getJavaCommand(Class cls,String name) {
|
|
135 return cache.get(cls).get(name);
|
|
136 }
|
|
137
|
|
138 public String[] getParameterNames() {
|
|
139 return getCommandSpec().getParameters().toArray(new String[0]);
|
|
140 }
|
|
141
|
|
142 public String getDotParameterName() {
|
|
143 return getCommandSpec().dotParameter;
|
|
144 }
|
|
145
|
|
146 public String[] getRequiredParameterNames() {
|
|
147 return getCommandSpec().requiredParameters.toArray(new String[0]);
|
|
148 }
|
|
149
|
|
150 private static final Pattern ID_PTN = Pattern.compile("([^.!]+\\.)+[^.!]+");
|
|
151
|
|
152 public static boolean isJavaCommandId(String id) {
|
|
153 return ID_PTN.matcher(id).matches();
|
|
154 }
|
|
155 }
|