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 @Override
60 public String getExtensionName() {
61 return getClass().getSimpleName();
62 }
63
64
65 @Override
66 @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "Extension needs direct access to runtime, VM and settings")
67 public void init(VariableManager vmParam, JRT runtime, final AwkSettings conf) {
68 this.vm = vmParam;
69 this.jrt = runtime;
70 this.settings = conf;
71 }
72
73
74
75
76
77
78
79
80
81 protected final String toAwkString(Object obj) {
82 return jrt.toAwkString(obj);
83 }
84
85
86
87
88
89
90
91
92
93 protected static void checkNumArgs(Object[] arr, int expectedNum) {
94
95
96
97
98
99 if (arr.length != expectedNum) {
100 throw new IllegalAwkArgumentException("Expecting " + expectedNum + " arg(s), got " + arr.length);
101 }
102 }
103
104
105
106
107
108
109
110
111 protected JRT getJrt() {
112 return jrt;
113 }
114
115
116
117
118
119
120
121
122 protected VariableManager getVm() {
123 return vm;
124 }
125
126
127
128
129
130
131
132
133 protected AwkSettings getSettings() {
134 return settings;
135 }
136
137 private Map<String, ExtensionFunction> getAnnotatedFunctions() {
138 if (annotatedFunctions == null) {
139 annotatedFunctions = Collections.unmodifiableMap(scanAnnotatedFunctions());
140 }
141 return annotatedFunctions;
142 }
143
144 private List<Method> getBeforeStartMethods() {
145 if (beforeStartMethods == null) {
146 beforeStartMethods = Collections.unmodifiableList(scanBeforeStartMethods());
147 }
148 return beforeStartMethods;
149 }
150
151 private Map<String, ExtensionFunction> scanAnnotatedFunctions() {
152 Map<String, ExtensionFunction> discovered = new LinkedHashMap<String, ExtensionFunction>();
153 Class<? extends AbstractExtension> type = getClass();
154 for (Method method : type.getMethods()) {
155 JawkFunction function = method.getAnnotation(JawkFunction.class);
156 if (function == null) {
157 continue;
158 }
159 String keyword = function.value();
160 ExtensionFunction existing = discovered.put(keyword, new ExtensionFunction(keyword, method));
161 if (existing != null) {
162 throw new IllegalStateException(
163 "Duplicate @JawkFunction mapping for keyword '" + keyword + "' in " + type.getName());
164 }
165 }
166 return discovered;
167 }
168
169 private List<Method> scanBeforeStartMethods() {
170 List<Method> discovered = new ArrayList<Method>();
171 Class<? extends AbstractExtension> type = getClass();
172 for (Method method : type.getMethods()) {
173 if (!method.isAnnotationPresent(JawkBeforeStart.class)) {
174 continue;
175 }
176 if (java.lang.reflect.Modifier.isStatic(method.getModifiers())) {
177 throw new IllegalStateException(
178 "@" + JawkBeforeStart.class.getSimpleName()
179 + " does not support static methods: " + method.toGenericString());
180 }
181 Class<?>[] parameterTypes = method.getParameterTypes();
182 if (method.getReturnType() != Void.TYPE
183 || parameterTypes.length != 2
184 || parameterTypes[0] != AVM.class
185 || parameterTypes[1] != JRT.class) {
186 throw new IllegalStateException(
187 "@" + JawkBeforeStart.class.getSimpleName()
188 + " method must declare void method(AVM, JRT): " + method.toGenericString());
189 }
190 method.setAccessible(true);
191 discovered.add(method);
192 }
193 return discovered;
194 }
195
196
197 @Override
198 public Map<String, ExtensionFunction> getExtensionFunctions() {
199 return getAnnotatedFunctions();
200 }
201
202
203 @Override
204 public void beforeStart(AVM avm, JRT runtime) {
205 for (Method method : getBeforeStartMethods()) {
206 try {
207 method.invoke(this, avm, runtime);
208 } catch (IllegalAccessException ex) {
209 throw new IllegalStateException(
210 "Unable to access extension before-start method " + method.toGenericString(),
211 ex);
212 } catch (InvocationTargetException ex) {
213 Throwable cause = ex.getCause();
214 if (cause instanceof RuntimeException) {
215 throw (RuntimeException) cause;
216 }
217 if (cause instanceof Error) {
218 throw (Error) cause;
219 }
220 throw new IllegalStateException(
221 "Extension before-start method failed: " + method.toGenericString(),
222 cause);
223 }
224 }
225 }
226 }