Mercurial Hosting > nabble
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 } |