1 package io.jawk.ext;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 import java.lang.reflect.InvocationTargetException;
26 import java.lang.reflect.Method;
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.LinkedHashMap;
30 import java.util.List;
31 import java.util.Map;
32 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
33 import io.jawk.backend.AVM;
34 import io.jawk.ext.annotations.JawkBeforeStart;
35 import io.jawk.ext.annotations.JawkFunction;
36 import io.jawk.jrt.IllegalAwkArgumentException;
37 import io.jawk.jrt.JRT;
38 import io.jawk.jrt.VariableManager;
39 import io.jawk.util.AwkSettings;
40
41
42
43
44
45
46
47
48
49
50 public abstract class AbstractExtension implements JawkExtension {
51
52 private JRT jrt;
53 private VariableManager vm;
54 private AwkSettings settings;
55 private Map<String, ExtensionFunction> annotatedFunctions;
56 private List<Method> beforeStartMethods;
57
58
59
60
61
62
63
64
65
66
67
68 protected AbstractExtension() {
69
70 }
71
72
73 @Override
74 public String getExtensionName() {
75 return getClass().getSimpleName();
76 }
77
78
79 @Override
80 @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "Extension needs direct access to runtime, VM and settings")
81 public void init(VariableManager vmParam, JRT runtime, final AwkSettings conf) {
82 this.vm = vmParam;
83 this.jrt = runtime;
84 this.settings = conf;
85 }
86
87
88
89
90
91
92
93
94
95 protected final String toAwkString(Object obj) {
96 return jrt.toAwkString(obj);
97 }
98
99
100
101
102
103
104
105
106
107 protected static void checkNumArgs(Object[] arr, int expectedNum) {
108
109
110
111
112
113 if (arr.length != expectedNum) {
114 throw new IllegalAwkArgumentException("Expecting " + expectedNum + " arg(s), got " + arr.length);
115 }
116 }
117
118
119
120
121
122
123
124
125 protected JRT getJrt() {
126 return jrt;
127 }
128
129
130
131
132
133
134
135
136 protected VariableManager getVm() {
137 return vm;
138 }
139
140
141
142
143
144
145
146
147 protected AwkSettings getSettings() {
148 return settings;
149 }
150
151 private Map<String, ExtensionFunction> getAnnotatedFunctions() {
152 if (annotatedFunctions == null) {
153 annotatedFunctions = Collections.unmodifiableMap(scanAnnotatedFunctions());
154 }
155 return annotatedFunctions;
156 }
157
158 private List<Method> getBeforeStartMethods() {
159 if (beforeStartMethods == null) {
160 beforeStartMethods = Collections.unmodifiableList(scanBeforeStartMethods());
161 }
162 return beforeStartMethods;
163 }
164
165 private Map<String, ExtensionFunction> scanAnnotatedFunctions() {
166 Map<String, ExtensionFunction> discovered = new LinkedHashMap<String, ExtensionFunction>();
167 Class<? extends AbstractExtension> type = getClass();
168 for (Method method : type.getMethods()) {
169 JawkFunction function = method.getAnnotation(JawkFunction.class);
170 if (function == null) {
171 continue;
172 }
173 String keyword = function.value();
174 ExtensionFunction existing = discovered.put(keyword, new ExtensionFunction(keyword, method));
175 if (existing != null) {
176 throw new IllegalStateException(
177 "Duplicate @JawkFunction mapping for keyword '" + keyword + "' in " + type.getName());
178 }
179 }
180 return discovered;
181 }
182
183 private List<Method> scanBeforeStartMethods() {
184 List<Method> discovered = new ArrayList<Method>();
185 Class<? extends AbstractExtension> type = getClass();
186 for (Method method : type.getMethods()) {
187 if (!method.isAnnotationPresent(JawkBeforeStart.class)) {
188 continue;
189 }
190 if (java.lang.reflect.Modifier.isStatic(method.getModifiers())) {
191 throw new IllegalStateException(
192 "@" + JawkBeforeStart.class.getSimpleName()
193 + " does not support static methods: " + method.toGenericString());
194 }
195 Class<?>[] parameterTypes = method.getParameterTypes();
196 if (method.getReturnType() != Void.TYPE
197 || parameterTypes.length != 2
198 || parameterTypes[0] != AVM.class
199 || parameterTypes[1] != JRT.class) {
200 throw new IllegalStateException(
201 "@" + JawkBeforeStart.class.getSimpleName()
202 + " method must declare void method(AVM, JRT): " + method.toGenericString());
203 }
204 method.setAccessible(true);
205 discovered.add(method);
206 }
207 return discovered;
208 }
209
210
211 @Override
212 public Map<String, ExtensionFunction> getExtensionFunctions() {
213 return getAnnotatedFunctions();
214 }
215
216
217 @Override
218 public void beforeStart(AVM avm, JRT runtime) {
219 for (Method method : getBeforeStartMethods()) {
220 try {
221 method.invoke(this, avm, runtime);
222 } catch (IllegalAccessException ex) {
223 throw new IllegalStateException(
224 "Unable to access extension before-start method " + method.toGenericString(),
225 ex);
226 } catch (InvocationTargetException ex) {
227 Throwable cause = ex.getCause();
228 if (cause instanceof RuntimeException) {
229 throw (RuntimeException) cause;
230 }
231 if (cause instanceof Error) {
232 throw (Error) cause;
233 }
234 throw new IllegalStateException(
235 "Extension before-start method failed: " + method.toGenericString(),
236 cause);
237 }
238 }
239 }
240 }