~ubuntu-branches/ubuntu/wily/parboiled/wily-proposed

« back to all changes in this revision

Viewing changes to parboiled-java/src/main/java/org/parboiled/transform/CachingGenerator.java

  • Committer: Package Import Robot
  • Author(s): Emmanuel Bourg
  • Date: 2014-11-10 21:10:42 UTC
  • Revision ID: package-import@ubuntu.com-20141110211042-wcmfz25icr5ituj5
Tags: upstream-1.1.6
ImportĀ upstreamĀ versionĀ 1.1.6

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Copyright (c) 2009-2010 Ken Wenzel and Mathias Doenitz
 
3
 *
 
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:
 
10
 *
 
11
 * The above copyright notice and this permission notice shall be included in
 
12
 * all copies or substantial portions of the Software.
 
13
 *
 
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
 
20
 * THE SOFTWARE.
 
21
 */
 
22
 
 
23
package org.parboiled.transform;
 
24
 
 
25
import org.objectweb.asm.Type;
 
26
import org.objectweb.asm.tree.*;
 
27
 
 
28
import java.util.ArrayList;
 
29
import java.util.Arrays;
 
30
import java.util.List;
 
31
 
 
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;
 
36
 
 
37
/**
 
38
 * Wraps the method code with caching and proxying constructs.
 
39
 */
 
40
class CachingGenerator implements RuleMethodProcessor {
 
41
 
 
42
    private ParserClassNode classNode;
 
43
    private RuleMethod method;
 
44
    private InsnList instructions;
 
45
    private AbstractInsnNode current;
 
46
    private String cacheFieldName;
 
47
 
 
48
    public boolean appliesTo(ParserClassNode classNode, RuleMethod method) {
 
49
        checkArgNotNull(classNode, "classNode");
 
50
        checkArgNotNull(method, "method");
 
51
        return method.hasCachedAnnotation();
 
52
    }
 
53
 
 
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
 
58
 
 
59
        this.classNode = classNode;
 
60
        this.method = method;
 
61
        this.instructions = method.instructions;
 
62
        this.current = instructions.getFirst();
 
63
 
 
64
        generateCacheHitReturn();
 
65
        generateStoreNewProxyMatcher();
 
66
        seekToReturnInstruction();
 
67
        generateArmProxyMatcher();
 
68
        generateStoreInCache();
 
69
    }
 
70
 
 
71
    // if (<cache> != null) return <cache>;
 
72
    private void generateCacheHitReturn() {
 
73
        // stack:
 
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));
 
82
        // stack: <null>
 
83
        insert(cacheMissLabel);
 
84
        // stack: <null>
 
85
        insert(new InsnNode(POP));
 
86
        // stack:
 
87
    }
 
88
 
 
89
    @SuppressWarnings( {"unchecked"})
 
90
    private void generateGetFromCache() {
 
91
        Type[] paramTypes = Type.getArgumentTypes(method.desc);
 
92
        cacheFieldName = findUnusedCacheFieldName();
 
93
 
 
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));
 
97
 
 
98
        // stack:
 
99
        insert(new VarInsnNode(ALOAD, 0));
 
100
        // stack: <this>
 
101
        insert(new FieldInsnNode(GETFIELD, classNode.name, cacheFieldName, cacheFieldDesc));
 
102
        // stack: <cache>
 
103
 
 
104
        if (paramTypes.length == 0) return; // if we have no parameters we are done
 
105
 
 
106
        // generate: if (<cache> == null) <cache> = new HashMap<Object, Rule>();
 
107
 
 
108
        // stack: <hashMap>
 
109
        insert(new InsnNode(DUP));
 
110
        // stack: <hashMap> :: <hashMap>
 
111
        LabelNode alreadyInitialized = new LabelNode();
 
112
        insert(new JumpInsnNode(IFNONNULL, alreadyInitialized));
 
113
        // stack: <null>
 
114
        insert(new InsnNode(POP));
 
115
        // stack:
 
116
        insert(new VarInsnNode(ALOAD, 0));
 
117
        // stack: <this>
 
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));
 
127
        // stack: <hashMap>
 
128
        insert(alreadyInitialized);
 
129
        // stack: <hashMap>
 
130
 
 
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>})
 
135
 
 
136
            String arguments = Type.getInternalName(Arguments.class);
 
137
            // stack: <hashMap>
 
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>
 
146
        } else {
 
147
            // stack: <hashMap>
 
148
            generatePushParameterAsObject(paramTypes, 0);
 
149
            // stack: <hashMap> :: <param>
 
150
        }
 
151
 
 
152
        // generate: <hashMap>.get(...)
 
153
 
 
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;"));
 
160
        // stack: <object>
 
161
        insert(new TypeInsnNode(CHECKCAST, Types.RULE.getInternalName()));
 
162
        // stack: <rule>
 
163
    }
 
164
 
 
165
    @SuppressWarnings( {"unchecked"})
 
166
    private String findUnusedCacheFieldName() {
 
167
        String name = "cache$" + method.name;
 
168
        int i = 2;
 
169
        while (hasField(name)) {
 
170
            name = "cache$" + method.name + i++;
 
171
        }
 
172
        return name;
 
173
    }
 
174
 
 
175
    public boolean hasField(String fieldName) {
 
176
        for (Object field : classNode.fields) {
 
177
            if (fieldName.equals(((FieldNode) field).name)) return true;
 
178
        }
 
179
        return false;
 
180
    }
 
181
 
 
182
    private void generatePushNewParameterObjectArray(Type[] paramTypes) {
 
183
        // stack: ...
 
184
        insert(new IntInsnNode(BIPUSH, paramTypes.length));
 
185
        // stack: ... :: <length>
 
186
        insert(new TypeInsnNode(ANEWARRAY, "java/lang/Object"));
 
187
        // stack: ... :: <array>
 
188
 
 
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>
 
199
        }
 
200
        // stack: ... :: <array>
 
201
    }
 
202
 
 
203
    private void generatePushParameterAsObject(Type[] paramTypes, int parameterNr) {
 
204
        switch (paramTypes[parameterNr++].getSort()) {
 
205
            case Type.BOOLEAN:
 
206
                insert(new VarInsnNode(ILOAD, parameterNr));
 
207
                insert(new MethodInsnNode(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;"));
 
208
                return;
 
209
            case Type.CHAR:
 
210
                insert(new VarInsnNode(ILOAD, parameterNr));
 
211
                insert(new MethodInsnNode(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;"));
 
212
                return;
 
213
            case Type.BYTE:
 
214
                insert(new VarInsnNode(ILOAD, parameterNr));
 
215
                insert(new MethodInsnNode(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;"));
 
216
                return;
 
217
            case Type.SHORT:
 
218
                insert(new VarInsnNode(ILOAD, parameterNr));
 
219
                insert(new MethodInsnNode(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;"));
 
220
                return;
 
221
            case Type.INT:
 
222
                insert(new VarInsnNode(ILOAD, parameterNr));
 
223
                insert(new MethodInsnNode(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;"));
 
224
                return;
 
225
            case Type.FLOAT:
 
226
                insert(new VarInsnNode(FLOAD, parameterNr));
 
227
                insert(new MethodInsnNode(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;"));
 
228
                return;
 
229
            case Type.LONG:
 
230
                insert(new VarInsnNode(LLOAD, parameterNr));
 
231
                insert(new MethodInsnNode(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;"));
 
232
                return;
 
233
            case Type.DOUBLE:
 
234
                insert(new VarInsnNode(DLOAD, parameterNr));
 
235
                insert(new MethodInsnNode(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;"));
 
236
                return;
 
237
            case Type.ARRAY:
 
238
            case Type.OBJECT:
 
239
                insert(new VarInsnNode(ALOAD, parameterNr));
 
240
                return;
 
241
            case Type.VOID:
 
242
            default:
 
243
                throw new IllegalStateException();
 
244
        }
 
245
    }
 
246
 
 
247
    // <cache> = new ProxyMatcher();
 
248
    private void generateStoreNewProxyMatcher() {
 
249
        String proxyMatcherType = Types.PROXY_MATCHER.getInternalName();
 
250
 
 
251
        // stack:
 
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>
 
260
    }
 
261
 
 
262
    private void seekToReturnInstruction() {
 
263
        while (current.getOpcode() != ARETURN) {
 
264
            current = current.getNext();
 
265
        }
 
266
    }
 
267
 
 
268
    // <proxyMatcher>.arm(<rule>)
 
269
    private void generateArmProxyMatcher() {
 
270
        String proxyMatcherType = Types.PROXY_MATCHER.getInternalName();
 
271
 
 
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"));
 
278
        // stack: <rule>
 
279
    }
 
280
 
 
281
    private void generateStoreInCache() {
 
282
        Type[] paramTypes = Type.getArgumentTypes(method.desc);
 
283
 
 
284
        // stack: <rule>
 
285
        insert(new InsnNode(DUP));
 
286
        // stack: <rule> :: <rule>
 
287
 
 
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));
 
295
            // stack: <rule>
 
296
            return;
 
297
        }
 
298
 
 
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));
 
316
        // stack: <rule>
 
317
    }
 
318
 
 
319
    private void insert(AbstractInsnNode instruction) {
 
320
        instructions.insertBefore(current, instruction);
 
321
    }
 
322
 
 
323
    public static class Arguments {
 
324
        private final Object[] params;
 
325
 
 
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();
 
331
        }
 
332
 
 
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()) {
 
337
                        case Type.BOOLEAN:
 
338
                            unroll(toObjectArray((boolean[]) param), list);
 
339
                            continue;
 
340
                        case Type.BYTE:
 
341
                            unroll(toObjectArray((byte[]) param), list);
 
342
                            continue;
 
343
                        case Type.CHAR:
 
344
                            unroll(toObjectArray((char[]) param), list);
 
345
                            continue;
 
346
                        case Type.DOUBLE:
 
347
                            unroll(toObjectArray((double[]) param), list);
 
348
                            continue;
 
349
                        case Type.FLOAT:
 
350
                            unroll(toObjectArray((float[]) param), list);
 
351
                            continue;
 
352
                        case Type.INT:
 
353
                            unroll(toObjectArray((int[]) param), list);
 
354
                            continue;
 
355
                        case Type.LONG:
 
356
                            unroll(toObjectArray((long[]) param), list);
 
357
                            continue;
 
358
                        case Type.SHORT:
 
359
                            unroll(toObjectArray((short[]) param), list);
 
360
                            continue;
 
361
                        case Type.OBJECT:
 
362
                        case Type.ARRAY:
 
363
                            unroll((Object[]) param, list);
 
364
                            continue;
 
365
                        default:
 
366
                            throw new IllegalStateException();
 
367
                    }
 
368
                }
 
369
                list.add(param);
 
370
            }
 
371
        }
 
372
 
 
373
        @Override
 
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);
 
379
        }
 
380
 
 
381
        @Override
 
382
        public int hashCode() {
 
383
            return params != null ? Arrays.hashCode(params) : 0;
 
384
        }
 
385
    }
 
386
}