File: DynamicClassWriter.java

package info (click to toggle)
eclipselink 2.5.1-2
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd
  • size: 44,568 kB
  • ctags: 76,888
  • sloc: java: 451,835; xml: 93; sh: 33; makefile: 24
file content (396 lines) | stat: -rw-r--r-- 16,934 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
/*******************************************************************************
 * Copyright (c) 1998, 2013 Oracle and/or its affiliates. All rights reserved.
 * This program and the accompanying materials are made available under the 
 * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 
 * which accompanies this distribution. 
 * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at 
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * Contributors:
 *     dclarke, mnorman - Dynamic Persistence
 *       http://wiki.eclipse.org/EclipseLink/Development/Dynamic 
 *       (https://bugs.eclipse.org/bugs/show_bug.cgi?id=200045)
 *     dclarke - Bug 387240: added field and method calls to allow extensibility
 *
 ******************************************************************************/
package org.eclipse.persistence.dynamic;

//javase imports
import java.lang.reflect.Modifier;

//EclipseLink imports
import org.eclipse.persistence.dynamic.DynamicClassLoader.EnumInfo;
import org.eclipse.persistence.exceptions.DynamicException;
import org.eclipse.persistence.internal.dynamic.DynamicEntityImpl;
import org.eclipse.persistence.internal.dynamic.DynamicPropertiesManager;
import org.eclipse.persistence.internal.helper.Helper;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;

import static org.eclipse.persistence.internal.dynamic.DynamicPropertiesManager.PROPERTIES_MANAGER_FIELD;
import static org.objectweb.asm.Opcodes.AASTORE;
import static org.objectweb.asm.Opcodes.ACC_ENUM;
import static org.objectweb.asm.Opcodes.ACC_FINAL;
import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import static org.objectweb.asm.Opcodes.ACC_STATIC;
import static org.objectweb.asm.Opcodes.ACC_SUPER;
import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC;
import static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.ANEWARRAY;
import static org.objectweb.asm.Opcodes.ARETURN;
import static org.objectweb.asm.Opcodes.BIPUSH;
import static org.objectweb.asm.Opcodes.SIPUSH;
import static org.objectweb.asm.Opcodes.CHECKCAST;
import static org.objectweb.asm.Opcodes.DUP;
import static org.objectweb.asm.Opcodes.GETSTATIC;
import static org.objectweb.asm.Opcodes.ICONST_0;
import static org.objectweb.asm.Opcodes.ICONST_1;
import static org.objectweb.asm.Opcodes.ICONST_2;
import static org.objectweb.asm.Opcodes.ICONST_3;
import static org.objectweb.asm.Opcodes.ICONST_4;
import static org.objectweb.asm.Opcodes.ICONST_5;
import static org.objectweb.asm.Opcodes.ILOAD;
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
import static org.objectweb.asm.Opcodes.NEW;
import static org.objectweb.asm.Opcodes.PUTSTATIC;
import static org.objectweb.asm.Opcodes.RETURN;
import static org.objectweb.asm.Opcodes.V1_5;

/**
 * Write the byte codes of a dynamic entity class. The class writer will create
 * the byte codes for a dynamic class that subclasses any provided class
 * replicating its constructors and writeReplace method (if one exists).
 * <p>
 * The intent is to provide a common writer for dynamic JPA entities but also
 * allow for subclasses of this to be used in more complex writing situations
 * such as SDO and DBWS.
 * <p>
 * Instances of this class and any subclasses are maintained within the
 * {@link DynamicClassLoader#getClassWriters()} and
 * {@link DynamicClassLoader#defaultWriter} for the life of the class loader so
 * it is important that no unnecessary state be maintained that may effect
 * memory usage.
 * 
 * @author dclarke, mnorman
 * @since EclipseLink 1.2
 */
public class DynamicClassWriter implements EclipseLinkClassWriter {

    /*
     * Pattern is as follows: <pre> public class Foo extends DynamicEntityImpl {
     * 
     * public static DynamicPropertiesManager DPM = new
     * DynamicPropertiesManager();
     * 
     * public Foo() { super(); } public DynamicPropertiesManager
     * fetchPropertiesManager() { return DPM; } }
     * 
     * later on, the DPM field is populated: Field dpmField =
     * myDynamicClass.getField
     * (DynamicPropertiesManager.PROPERTIES_MANAGER_FIELD);
     * DynamicPropertiesManager dpm =
     * (DynamicPropertiesManager)dpmField.get(null); dpm.setType(...) </pre>
     */

    protected static final String DYNAMIC_PROPERTIES_MANAGER_CLASSNAME_SLASHES = DynamicPropertiesManager.class.getName().replace('.', '/');
    protected static final String INIT = "<init>";
    protected static final String CLINIT = "<clinit>";

    protected Class<?> parentClass;

    /**
     * Name of parent class. This is used only when the parent class is not
     * known at the time the dynamic class writer is registered. This is
     * generally only required when loading from an XML mapping file where the
     * order of class access is not known.
     */
    protected String parentClassName;

    public DynamicClassWriter() {
        this(DynamicEntityImpl.class);
    }

    public DynamicClassWriter(Class<?> parentClass) {
        this.parentClass = parentClass;
    }

    /**
     * Create using a loader and class name so that the parent class can be
     * lazily loaded when the writer is used to generate a dynamic class.
     * <p>
     * The loader must not be null and the parentClassName must not be null and
     * not an empty String. The parentClassName will be converted to a class
     * using the provided loader lazily.
     * 
     * @see #getParentClass()
     * @see DynamicException#illegalDynamicClassWriter(DynamicClassLoader,
     *      String)
     */
    public DynamicClassWriter(String parentClassName) {
        if (parentClassName == null || parentClassName.length() == 0) {
            throw DynamicException.illegalParentClassName(parentClassName);
        }
        this.parentClassName = parentClassName;
    }

    public Class<?> getParentClass() {
        return this.parentClass;
    }

    public String getParentClassName() {
        return this.parentClassName;
    }

    /**
     * Return the {@link #parentClass} converting the {@link #parentClassName}
     * using the provided loader if required.
     * 
     * @throws ClassNotFoundException
     *             if the parentClass is not available.
     */
    private Class<?> getParentClass(ClassLoader loader) throws ClassNotFoundException {
        if (parentClass == null && parentClassName != null) {
            parentClass = loader.loadClass(parentClassName);
        }
        return parentClass;
    }

    public byte[] writeClass(DynamicClassLoader loader, String className) throws ClassNotFoundException {

        EnumInfo enumInfo = loader.enumInfoRegistry.get(className);
        if (enumInfo != null) {
            return createEnum(enumInfo);
        }

        Class<?> parent = getParentClass(loader);
        parentClassName = parent.getName();
        if (parent == null || parent.isPrimitive() || parent.isArray() || parent.isEnum() || parent.isInterface() || Modifier.isFinal(parent.getModifiers())) {
            throw new IllegalArgumentException("Invalid parent class: " + parent);
        }
        String classNameAsSlashes = className.replace('.', '/');
        String parentClassNameAsSlashes = parentClassName.replace('.', '/');

        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);

        // public class Foo extends DynamicEntityImpl {
        cw.visit(V1_5, ACC_PUBLIC + ACC_SUPER, classNameAsSlashes, null, parentClassNameAsSlashes, null);

        // public static DynamicPropertiesManager DPM = new
        // DynamicPropertiesManager();
        cw.visitField(ACC_PUBLIC + ACC_STATIC, PROPERTIES_MANAGER_FIELD, "L" + DYNAMIC_PROPERTIES_MANAGER_CLASSNAME_SLASHES + ";", null, null);
        MethodVisitor mv = cw.visitMethod(ACC_STATIC, CLINIT, "()V", null, null);
        mv.visitTypeInsn(NEW, DYNAMIC_PROPERTIES_MANAGER_CLASSNAME_SLASHES);
        mv.visitInsn(DUP);
        mv.visitMethodInsn(INVOKESPECIAL, DYNAMIC_PROPERTIES_MANAGER_CLASSNAME_SLASHES, INIT, "()V");
        mv.visitFieldInsn(PUTSTATIC, classNameAsSlashes, PROPERTIES_MANAGER_FIELD, "L" + DYNAMIC_PROPERTIES_MANAGER_CLASSNAME_SLASHES + ";");
        mv.visitInsn(RETURN);
        mv.visitMaxs(0, 0);

        // public Foo() {
        // super();
        // }
        mv = cw.visitMethod(ACC_PUBLIC, INIT, "()V", null, null);
        mv.visitVarInsn(ALOAD, 0);
        mv.visitMethodInsn(INVOKESPECIAL, parentClassNameAsSlashes, INIT, "()V");
        mv.visitInsn(RETURN);
        mv.visitMaxs(0, 0);

        mv = cw.visitMethod(ACC_PUBLIC, "fetchPropertiesManager", "()L" + DYNAMIC_PROPERTIES_MANAGER_CLASSNAME_SLASHES + ";", null, null);
        mv.visitFieldInsn(GETSTATIC, classNameAsSlashes, PROPERTIES_MANAGER_FIELD, "L" + DYNAMIC_PROPERTIES_MANAGER_CLASSNAME_SLASHES + ";");
        mv.visitInsn(ARETURN);
        mv.visitMaxs(0, 0);

        addFields(cw, parentClassNameAsSlashes);
        addMethods(cw, parentClassNameAsSlashes);

        cw.visitEnd();
        return cw.toByteArray();

    }

    /**
     * Allow subclasses to add additional state to the dynamic entity.
     * 
     * @param cw
     * @param parentClassNameAsSlashes
     */
    protected void addFields(ClassWriter cw, String parentClassType) {
    }

    /**
     * Allow subclasses to add additional methods to the dynamic entity.
     * 
     * @param cw
     * @param parentClassNameAsSlashes
     */
    protected void addMethods(ClassWriter cw, String parentClassType) {
    }

    public static int[] ICONST = new int[] { ICONST_0, ICONST_1, ICONST_2, ICONST_3, ICONST_4, ICONST_5 };

    protected byte[] createEnum(EnumInfo enumInfo) {

        String[] enumValues = enumInfo.getLiteralLabels();
        String className = enumInfo.getClassName();

        String internalClassName = className.replace('.', '/');

        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        cw.visit(V1_5, ACC_PUBLIC + ACC_FINAL + ACC_SUPER + ACC_ENUM, internalClassName, null, "java/lang/Enum", null);

        // Add the individual enum values
        for (String enumValue : enumValues) {
            cw.visitField(ACC_PUBLIC + ACC_FINAL + ACC_STATIC + ACC_ENUM, enumValue, "L" + internalClassName + ";", null, null);
        }

        // add the synthetic "$VALUES" field
        cw.visitField(ACC_PRIVATE + ACC_FINAL + ACC_STATIC + ACC_SYNTHETIC, "$VALUES", "[L" + internalClassName + ";", null, null);

        // Add the "values()" method
        MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "values", "()[L" + internalClassName + ";", null, null);
        mv.visitFieldInsn(GETSTATIC, internalClassName, "$VALUES", "[L" + internalClassName + ";");
        mv.visitMethodInsn(INVOKEVIRTUAL, "[L" + internalClassName + ";", "clone", "()Ljava/lang/Object;");
        mv.visitTypeInsn(CHECKCAST, "[L" + internalClassName + ";");
        mv.visitInsn(ARETURN);
        mv.visitMaxs(1, 0);

        // Add the "valueOf()" method
        mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "valueOf", "(Ljava/lang/String;)L" + internalClassName + ";", null, null);
        mv.visitLdcInsn(Type.getType("L" + internalClassName + ";"));
        mv.visitVarInsn(ALOAD, 0);
        mv.visitMethodInsn(INVOKESTATIC, "java/lang/Enum", "valueOf", "(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;");
        mv.visitTypeInsn(CHECKCAST, internalClassName);
        mv.visitInsn(ARETURN);
        mv.visitMaxs(2, 1);

        // Add constructors
        // SignatureAttribute methodAttrs1 = new SignatureAttribute("()V");
        mv = cw.visitMethod(ACC_PRIVATE, "<init>", "(Ljava/lang/String;I)V", null, null);
        mv.visitVarInsn(ALOAD, 0);
        mv.visitVarInsn(ALOAD, 1);
        mv.visitVarInsn(ILOAD, 2);
        mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Enum", "<init>", "(Ljava/lang/String;I)V");
        mv.visitInsn(RETURN);
        mv.visitMaxs(3, 3);

        // Add enum constants
        mv = cw.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);

        int lastCount = 0;
        for (int i = 0; i < enumValues.length; i++) {
            String enumValue = enumValues[i];
            mv.visitTypeInsn(NEW, internalClassName);
            mv.visitInsn(DUP);
            mv.visitLdcInsn(enumValue);
            if (i <= 5) {
                mv.visitInsn(ICONST[i]);
            } else if (i <= 127) {
                mv.visitIntInsn(BIPUSH, i);
            } else {
                mv.visitIntInsn(SIPUSH, i);
            }
            mv.visitMethodInsn(INVOKESPECIAL, internalClassName, "<init>", "(Ljava/lang/String;I)V");
            mv.visitFieldInsn(PUTSTATIC, internalClassName, enumValue, "L" + internalClassName + ";");
            lastCount = i;
        }

        if (lastCount < 5) {
            mv.visitInsn(ICONST[lastCount + 1]);
        } else if (lastCount < 127) {
            mv.visitIntInsn(BIPUSH, lastCount + 1);
        } else {
            mv.visitIntInsn(SIPUSH, lastCount + 1);
        }
        mv.visitTypeInsn(ANEWARRAY, internalClassName);

        for (int i = 0; i < enumValues.length; i++) {
            String enumValue = enumValues[i];
            mv.visitInsn(DUP);
            if (i <= 5) {
                mv.visitInsn(ICONST[i]);
            } else if (i <= 127) {
                mv.visitIntInsn(BIPUSH, i);
            } else {
                mv.visitIntInsn(SIPUSH, i);
            }
            mv.visitFieldInsn(GETSTATIC, internalClassName, enumValue, "L" + internalClassName + ";");
            mv.visitInsn(AASTORE);
        }
        mv.visitFieldInsn(PUTSTATIC, internalClassName, "$VALUES", "[L" + internalClassName + ";");
        mv.visitInsn(RETURN);
        mv.visitMaxs(4, 0);

        cw.visitEnd();
        return cw.toByteArray();
    }

    /**
     * Verify that the provided class meets the requirements of the writer. In
     * the case of {@link DynamicClassWriter} this will ensure that the class is
     * a subclass of the {@link #parentClass}
     * 
     * @param dynamicClass
     * @throws ClassNotFoundException
     */
    protected boolean verify(Class<?> dynamicClass, ClassLoader loader) throws ClassNotFoundException {
        Class<?> parent = getParentClass(loader);
        return dynamicClass != null && parent.isAssignableFrom(dynamicClass);
    }

    /**
     * Interfaces the dynamic entity class implements. By default this is none
     * but in the case of SDO a concrete interface must be implemented.
     * Subclasses should override this as required.
     * 
     * @return Interfaces implemented by Dynamic class. May be null
     */
    protected String[] getInterfaces() {
        return null;
    }

    /**
     * Create a copy of this {@link DynamicClassWriter} but with a different
     * parent class.
     * 
     * @see DynamicClassLoader#addClass(String, Class)
     */
    protected DynamicClassWriter createCopy(Class<?> parentClass) {
        return new DynamicClassWriter(parentClass);
    }

    /**
     * Verify that the provided writer is compatible with the current writer.
     * Returning true means that the bytes that would be created using this
     * writer are identical with what would come from the provided writer.
     * <p>
     * Used in {@link DynamicClassLoader#addClass(String, DynamicClassWriter)}
     * to verify if a duplicate request of the same className can proceed and
     * return the same class that may already exist.
     */
    public boolean isCompatible(EclipseLinkClassWriter writer) {
        if (writer == null) {
            return false;
        }
        // Ensure writers are the exact same class. If subclasses do not alter
        // the bytes created then they must override this method and not return
        // false on this check.
        if (getClass() != writer.getClass()) {
            return false;
        }
        if (getParentClass() == null) {
            return getParentClassName() != null && getParentClassName().equals(writer.getParentClassName());
        }
        return getParentClass() == writer.getParentClass();
    }

    @Override
    public String toString() {
        String parentName = getParentClass() == null ? getParentClassName() : getParentClass().getName();
        return Helper.getShortClassName(getClass()) + "(" + parentName + ")";
    }
}