comparison 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
comparison
equal deleted inserted replaced
-1:000000000000 0:7ecd1a4ef557
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 }