| /* |
| * Copyright (c) 1998, 2021 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 v. 2.0 which is available at |
| * http://www.eclipse.org/legal/epl-2.0, |
| * or the Eclipse Distribution License v. 1.0 which is available at |
| * http://www.eclipse.org/org/documents/edl-v10.php. |
| * |
| * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause |
| */ |
| |
| // 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 static org.eclipse.persistence.internal.dynamic.DynamicPropertiesManager.PROPERTIES_MANAGER_FIELD; |
| import static org.eclipse.persistence.internal.libraries.asm.Opcodes.AASTORE; |
| import static org.eclipse.persistence.internal.libraries.asm.Opcodes.ACC_ENUM; |
| import static org.eclipse.persistence.internal.libraries.asm.Opcodes.ACC_FINAL; |
| import static org.eclipse.persistence.internal.libraries.asm.Opcodes.ACC_PRIVATE; |
| import static org.eclipse.persistence.internal.libraries.asm.Opcodes.ACC_PUBLIC; |
| import static org.eclipse.persistence.internal.libraries.asm.Opcodes.ACC_STATIC; |
| import static org.eclipse.persistence.internal.libraries.asm.Opcodes.ACC_SUPER; |
| import static org.eclipse.persistence.internal.libraries.asm.Opcodes.ACC_SYNTHETIC; |
| import static org.eclipse.persistence.internal.libraries.asm.Opcodes.ALOAD; |
| import static org.eclipse.persistence.internal.libraries.asm.Opcodes.ANEWARRAY; |
| import static org.eclipse.persistence.internal.libraries.asm.Opcodes.ARETURN; |
| import static org.eclipse.persistence.internal.libraries.asm.Opcodes.BIPUSH; |
| import static org.eclipse.persistence.internal.libraries.asm.Opcodes.CHECKCAST; |
| import static org.eclipse.persistence.internal.libraries.asm.Opcodes.DUP; |
| import static org.eclipse.persistence.internal.libraries.asm.Opcodes.GETSTATIC; |
| import static org.eclipse.persistence.internal.libraries.asm.Opcodes.ICONST_0; |
| import static org.eclipse.persistence.internal.libraries.asm.Opcodes.ICONST_1; |
| import static org.eclipse.persistence.internal.libraries.asm.Opcodes.ICONST_2; |
| import static org.eclipse.persistence.internal.libraries.asm.Opcodes.ICONST_3; |
| import static org.eclipse.persistence.internal.libraries.asm.Opcodes.ICONST_4; |
| import static org.eclipse.persistence.internal.libraries.asm.Opcodes.ICONST_5; |
| import static org.eclipse.persistence.internal.libraries.asm.Opcodes.ILOAD; |
| import static org.eclipse.persistence.internal.libraries.asm.Opcodes.INVOKESPECIAL; |
| import static org.eclipse.persistence.internal.libraries.asm.Opcodes.INVOKESTATIC; |
| import static org.eclipse.persistence.internal.libraries.asm.Opcodes.INVOKEVIRTUAL; |
| import static org.eclipse.persistence.internal.libraries.asm.Opcodes.NEW; |
| import static org.eclipse.persistence.internal.libraries.asm.Opcodes.PUTSTATIC; |
| import static org.eclipse.persistence.internal.libraries.asm.Opcodes.RETURN; |
| import static org.eclipse.persistence.internal.libraries.asm.Opcodes.SIPUSH; |
| |
| import java.lang.reflect.Modifier; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| //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.eclipse.persistence.internal.libraries.asm.ClassWriter; |
| import org.eclipse.persistence.internal.libraries.asm.EclipseLinkASMClassWriter; |
| import org.eclipse.persistence.internal.libraries.asm.MethodVisitor; |
| import org.eclipse.persistence.internal.libraries.asm.Type; |
| |
| /** |
| * 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; |
| |
| private List<String> interfaces; |
| |
| 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; |
| } |
| |
| @Override |
| public Class<?> getParentClass() { |
| return this.parentClass; |
| } |
| |
| @Override |
| 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; |
| } |
| |
| @Override |
| 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.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('.', '/'); |
| |
| EclipseLinkASMClassWriter cw = new EclipseLinkASMClassWriter(); |
| |
| // public class Foo extends DynamicEntityImpl { |
| cw.visit(ACC_PUBLIC + ACC_SUPER, classNameAsSlashes, null, parentClassNameAsSlashes, interfaces != null ? interfaces.toArray(new String[interfaces.size()]) : 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", false); |
| 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", false); |
| 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 interfaces to the dynamic entity. |
| * |
| * @param intf additional interface |
| */ |
| protected void addInterface(String intf) { |
| if (interfaces == null) { |
| interfaces = new ArrayList<>(); |
| } |
| interfaces.add(intf); |
| } |
| |
| /** |
| * Allow subclasses to add additional state to the dynamic entity. |
| * |
| * @param cw |
| * @param parentClassType |
| */ |
| protected void addFields(ClassWriter cw, String parentClassType) { |
| } |
| |
| /** |
| * Allow subclasses to add additional methods to the dynamic entity. |
| * |
| * @param cw |
| * @param parentClassType |
| */ |
| 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('.', '/'); |
| |
| EclipseLinkASMClassWriter cw = new EclipseLinkASMClassWriter(); |
| cw.visit(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;", false); |
| 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;", false); |
| 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", false); |
| 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", false); |
| 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, EclipseLinkClassWriter)} |
| * to verify if a duplicate request of the same className can proceed and |
| * return the same class that may already exist. |
| */ |
| @Override |
| 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 + ")"; |
| } |
| } |