2
* Copyright (c) 2009-2010 Ken Wenzel and Mathias Doenitz
4
* Permission is hereby granted, free of charge, to any person obtaining a copy
5
* of this software and associated documentation files (the "Software"), to deal
6
* in the Software without restriction, including without limitation the rights
7
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
* copies of the Software, and to permit persons to whom the Software is
9
* furnished to do so, subject to the following conditions:
11
* The above copyright notice and this permission notice shall be included in
12
* all copies or substantial portions of the Software.
14
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
package org.parboiled.transform;
25
import org.objectweb.asm.Type;
26
import org.objectweb.asm.tree.*;
28
import java.util.ArrayList;
29
import java.util.Arrays;
30
import java.util.List;
32
import static org.objectweb.asm.Opcodes.*;
33
import static org.parboiled.common.Preconditions.checkArgNotNull;
34
import static org.parboiled.common.Preconditions.checkState;
35
import static org.parboiled.common.Utils.toObjectArray;
38
* Wraps the method code with caching and proxying constructs.
40
class CachingGenerator implements RuleMethodProcessor {
42
private ParserClassNode classNode;
43
private RuleMethod method;
44
private InsnList instructions;
45
private AbstractInsnNode current;
46
private String cacheFieldName;
48
public boolean appliesTo(ParserClassNode classNode, RuleMethod method) {
49
checkArgNotNull(classNode, "classNode");
50
checkArgNotNull(method, "method");
51
return method.hasCachedAnnotation();
54
public void process(ParserClassNode classNode, RuleMethod method) throws Exception {
55
checkArgNotNull(classNode, "classNode");
56
checkArgNotNull(method, "method");
57
checkState(!method.isSuperMethod()); // super methods have flag moved to the overriding method
59
this.classNode = classNode;
61
this.instructions = method.instructions;
62
this.current = instructions.getFirst();
64
generateCacheHitReturn();
65
generateStoreNewProxyMatcher();
66
seekToReturnInstruction();
67
generateArmProxyMatcher();
68
generateStoreInCache();
71
// if (<cache> != null) return <cache>;
72
private void generateCacheHitReturn() {
74
generateGetFromCache();
75
// stack: <cachedValue>
76
insert(new InsnNode(DUP));
77
// stack: <cachedValue> :: <cachedValue>
78
LabelNode cacheMissLabel = new LabelNode();
79
insert(new JumpInsnNode(IFNULL, cacheMissLabel));
80
// stack: <cachedValue>
81
insert(new InsnNode(ARETURN));
83
insert(cacheMissLabel);
85
insert(new InsnNode(POP));
89
@SuppressWarnings( {"unchecked"})
90
private void generateGetFromCache() {
91
Type[] paramTypes = Type.getArgumentTypes(method.desc);
92
cacheFieldName = findUnusedCacheFieldName();
94
// if we have no parameters we use a simple Rule field as cache, otherwise a HashMap
95
String cacheFieldDesc = paramTypes.length == 0 ? Types.RULE_DESC : "Ljava/util/HashMap;";
96
classNode.fields.add(new FieldNode(ACC_PRIVATE, cacheFieldName, cacheFieldDesc, null, null));
99
insert(new VarInsnNode(ALOAD, 0));
101
insert(new FieldInsnNode(GETFIELD, classNode.name, cacheFieldName, cacheFieldDesc));
104
if (paramTypes.length == 0) return; // if we have no parameters we are done
106
// generate: if (<cache> == null) <cache> = new HashMap<Object, Rule>();
109
insert(new InsnNode(DUP));
110
// stack: <hashMap> :: <hashMap>
111
LabelNode alreadyInitialized = new LabelNode();
112
insert(new JumpInsnNode(IFNONNULL, alreadyInitialized));
114
insert(new InsnNode(POP));
116
insert(new VarInsnNode(ALOAD, 0));
118
insert(new TypeInsnNode(NEW, "java/util/HashMap"));
119
// stack: <this> :: <hashMap>
120
insert(new InsnNode(DUP_X1));
121
// stack: <hashMap> :: <this> :: <hashMap>
122
insert(new InsnNode(DUP));
123
// stack: <hashMap> :: <this> :: <hashMap> :: <hashMap>
124
insert(new MethodInsnNode(INVOKESPECIAL, "java/util/HashMap", "<init>", "()V"));
125
// stack: <hashMap> :: <this> :: <hashMap>
126
insert(new FieldInsnNode(PUTFIELD, classNode.name, cacheFieldName, cacheFieldDesc));
128
insert(alreadyInitialized);
131
// if we have more than one parameter or the parameter is an array we have to wrap with our Arguments class
132
// since we need to unroll all inner arrays and apply custom hashCode(...) and equals(...) implementations
133
if (paramTypes.length > 1 || paramTypes[0].getSort() == Type.ARRAY) {
134
// generate: push new Arguments(new Object[] {<params>})
136
String arguments = Type.getInternalName(Arguments.class);
138
insert(new TypeInsnNode(NEW, arguments));
139
// stack: <hashMap> :: <arguments>
140
insert(new InsnNode(DUP));
141
// stack: <hashMap> :: <arguments> :: <arguments>
142
generatePushNewParameterObjectArray(paramTypes);
143
// stack: <hashMap> :: <arguments> :: <arguments> :: <array>
144
insert(new MethodInsnNode(INVOKESPECIAL, arguments, "<init>", "([Ljava/lang/Object;)V"));
145
// stack: <hashMap> :: <arguments>
148
generatePushParameterAsObject(paramTypes, 0);
149
// stack: <hashMap> :: <param>
152
// generate: <hashMap>.get(...)
154
// stack: <hashMap> :: <mapKey>
155
insert(new InsnNode(DUP));
156
// stack: <hashMap> :: <mapKey> :: <mapKey>
157
insert(new VarInsnNode(ASTORE, method.maxLocals));
158
// stack: <hashMap> :: <mapKey>
159
insert(new MethodInsnNode(INVOKEVIRTUAL, "java/util/HashMap", "get", "(Ljava/lang/Object;)Ljava/lang/Object;"));
161
insert(new TypeInsnNode(CHECKCAST, Types.RULE.getInternalName()));
165
@SuppressWarnings( {"unchecked"})
166
private String findUnusedCacheFieldName() {
167
String name = "cache$" + method.name;
169
while (hasField(name)) {
170
name = "cache$" + method.name + i++;
175
public boolean hasField(String fieldName) {
176
for (Object field : classNode.fields) {
177
if (fieldName.equals(((FieldNode) field).name)) return true;
182
private void generatePushNewParameterObjectArray(Type[] paramTypes) {
184
insert(new IntInsnNode(BIPUSH, paramTypes.length));
185
// stack: ... :: <length>
186
insert(new TypeInsnNode(ANEWARRAY, "java/lang/Object"));
187
// stack: ... :: <array>
189
for (int i = 0; i < paramTypes.length; i++) {
190
// stack: ... :: <array>
191
insert(new InsnNode(DUP));
192
// stack: ... :: <array> :: <array>
193
insert(new IntInsnNode(BIPUSH, i));
194
// stack: ... :: <array> :: <array> :: <index>
195
generatePushParameterAsObject(paramTypes, i);
196
// stack: ... :: <array> :: <array> :: <index> :: <param>
197
insert(new InsnNode(AASTORE));
198
// stack: ... :: <array>
200
// stack: ... :: <array>
203
private void generatePushParameterAsObject(Type[] paramTypes, int parameterNr) {
204
switch (paramTypes[parameterNr++].getSort()) {
206
insert(new VarInsnNode(ILOAD, parameterNr));
207
insert(new MethodInsnNode(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;"));
210
insert(new VarInsnNode(ILOAD, parameterNr));
211
insert(new MethodInsnNode(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;"));
214
insert(new VarInsnNode(ILOAD, parameterNr));
215
insert(new MethodInsnNode(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;"));
218
insert(new VarInsnNode(ILOAD, parameterNr));
219
insert(new MethodInsnNode(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;"));
222
insert(new VarInsnNode(ILOAD, parameterNr));
223
insert(new MethodInsnNode(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;"));
226
insert(new VarInsnNode(FLOAD, parameterNr));
227
insert(new MethodInsnNode(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;"));
230
insert(new VarInsnNode(LLOAD, parameterNr));
231
insert(new MethodInsnNode(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;"));
234
insert(new VarInsnNode(DLOAD, parameterNr));
235
insert(new MethodInsnNode(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;"));
239
insert(new VarInsnNode(ALOAD, parameterNr));
243
throw new IllegalStateException();
247
// <cache> = new ProxyMatcher();
248
private void generateStoreNewProxyMatcher() {
249
String proxyMatcherType = Types.PROXY_MATCHER.getInternalName();
252
insert(new TypeInsnNode(NEW, proxyMatcherType));
253
// stack: <proxyMatcher>
254
insert(new InsnNode(DUP));
255
// stack: <proxyMatcher> :: <proxyMatcher>
256
insert(new MethodInsnNode(INVOKESPECIAL, proxyMatcherType, "<init>", "()V"));
257
// stack: <proxyMatcher>
258
generateStoreInCache();
259
// stack: <proxyMatcher>
262
private void seekToReturnInstruction() {
263
while (current.getOpcode() != ARETURN) {
264
current = current.getNext();
268
// <proxyMatcher>.arm(<rule>)
269
private void generateArmProxyMatcher() {
270
String proxyMatcherType = Types.PROXY_MATCHER.getInternalName();
272
// stack: <proxyMatcher> :: <rule>
273
insert(new InsnNode(DUP_X1));
274
// stack: <rule> :: <proxyMatcher> :: <rule>
275
insert(new TypeInsnNode(CHECKCAST, Types.MATCHER.getInternalName()));
276
// stack: <rule> :: <proxyMatcher> :: <matcher>
277
insert(new MethodInsnNode(INVOKEVIRTUAL, proxyMatcherType, "arm", '(' + Types.MATCHER_DESC + ")V"));
281
private void generateStoreInCache() {
282
Type[] paramTypes = Type.getArgumentTypes(method.desc);
285
insert(new InsnNode(DUP));
286
// stack: <rule> :: <rule>
288
if (paramTypes.length == 0) {
289
// stack: <rule> :: <rule>
290
insert(new VarInsnNode(ALOAD, 0));
291
// stack: <rule> :: <rule> :: <this>
292
insert(new InsnNode(SWAP));
293
// stack: <rule> :: <this> :: <rule>
294
insert(new FieldInsnNode(PUTFIELD, classNode.name, cacheFieldName, Types.RULE_DESC));
299
// stack: <rule> :: <rule>
300
insert(new VarInsnNode(ALOAD, method.maxLocals));
301
// stack: <rule> :: <rule> :: <mapKey>
302
insert(new InsnNode(SWAP));
303
// stack: <rule> :: <mapKey> :: <rule>
304
insert(new VarInsnNode(ALOAD, 0));
305
// stack: <rule> :: <mapKey> :: <rule> :: <this>
306
insert(new FieldInsnNode(GETFIELD, classNode.name, cacheFieldName, "Ljava/util/HashMap;"));
307
// stack: <rule> :: <mapKey> :: <rule> :: <hashMap>
308
insert(new InsnNode(DUP_X2));
309
// stack: <rule> :: <hashMap> :: <mapKey> :: <rule> :: <hashMap>
310
insert(new InsnNode(POP));
311
// stack: <rule> :: <hashMap> :: <mapKey> :: <rule>
312
insert(new MethodInsnNode(INVOKEVIRTUAL, "java/util/HashMap", "put",
313
"(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"));
314
// stack: <rule> :: <null>
315
insert(new InsnNode(POP));
319
private void insert(AbstractInsnNode instruction) {
320
instructions.insertBefore(current, instruction);
323
public static class Arguments {
324
private final Object[] params;
326
public Arguments(Object[] params) {
327
// we need to "unroll" all inner Object arrays
328
List<Object> list = new ArrayList<Object>();
329
unroll(params, list);
330
this.params = list.toArray();
333
private void unroll(Object[] params, List<Object> list) {
334
for (Object param : params) {
335
if (param != null && param.getClass().isArray()) {
336
switch (Type.getType(param.getClass().getComponentType()).getSort()) {
338
unroll(toObjectArray((boolean[]) param), list);
341
unroll(toObjectArray((byte[]) param), list);
344
unroll(toObjectArray((char[]) param), list);
347
unroll(toObjectArray((double[]) param), list);
350
unroll(toObjectArray((float[]) param), list);
353
unroll(toObjectArray((int[]) param), list);
356
unroll(toObjectArray((long[]) param), list);
359
unroll(toObjectArray((short[]) param), list);
363
unroll((Object[]) param, list);
366
throw new IllegalStateException();
374
public boolean equals(Object o) {
375
if (this == o) return true;
376
if (!(o instanceof Arguments)) return false;
377
Arguments that = (Arguments) o;
378
return Arrays.equals(params, that.params);
382
public int hashCode() {
383
return params != null ? Arrays.hashCode(params) : 0;