private CtMethod generateFieldReader( CtClass managedCtClass, CtField persistentField, AttributeTypeDescriptor typeDescriptor) { final String fieldName = persistentField.getName(); final String readerName = EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + fieldName; // read attempts only have to deal lazy-loading support, not dirty checking; // so if the field is not enabled as lazy-loadable return a plain simple getter as the reader if (!enhancementContext.isLazyLoadable(persistentField)) { return MethodWriter.addGetter(managedCtClass, fieldName, readerName); } try { return MethodWriter.write( managedCtClass, "public %s %s() {%n %s%n return this.%s;%n}", persistentField.getType().getName(), readerName, typeDescriptor.buildReadInterceptionBodyFragment(fieldName), fieldName); } catch (CannotCompileException cce) { final String msg = String.format( "Could not enhance entity class [%s] to add field reader method [%s]", managedCtClass.getName(), readerName); throw new EnhancementException(msg, cce); } catch (NotFoundException nfe) { final String msg = String.format( "Could not enhance entity class [%s] to add field reader method [%s]", managedCtClass.getName(), readerName); throw new EnhancementException(msg, nfe); } }
protected void enhanceAttributesAccess( CtClass managedCtClass, IdentityHashMap<String, PersistentAttributeAccessMethods> attributeDescriptorMap) { final ConstPool constPool = managedCtClass.getClassFile().getConstPool(); for (Object oMethod : managedCtClass.getClassFile().getMethods()) { final MethodInfo methodInfo = (MethodInfo) oMethod; final String methodName = methodInfo.getName(); // skip methods added by enhancement and abstract methods (methods without any code) if (methodName.startsWith("$$_hibernate_") || methodInfo.getCodeAttribute() == null) { continue; } try { final CodeIterator itr = methodInfo.getCodeAttribute().iterator(); while (itr.hasNext()) { final int index = itr.next(); final int op = itr.byteAt(index); if (op != Opcode.PUTFIELD && op != Opcode.GETFIELD) { continue; } final String fieldName = constPool.getFieldrefName(itr.u16bitAt(index + 1)); final PersistentAttributeAccessMethods attributeMethods = attributeDescriptorMap.get(fieldName); // its not a field we have enhanced for interception, so skip it if (attributeMethods == null) { continue; } // System.out.printf( "Transforming access to field [%s] from method [%s]%n", fieldName, // methodName ); log.debugf("Transforming access to field [%s] from method [%s]", fieldName, methodName); if (op == Opcode.GETFIELD) { final int methodIndex = MethodWriter.addMethod(constPool, attributeMethods.getReader()); itr.writeByte(Opcode.INVOKESPECIAL, index); itr.write16bit(methodIndex, index + 1); } else { final int methodIndex = MethodWriter.addMethod(constPool, attributeMethods.getWriter()); itr.writeByte(Opcode.INVOKESPECIAL, index); itr.write16bit(methodIndex, index + 1); } } methodInfo.getCodeAttribute().setAttribute(MapMaker.make(classPool, methodInfo)); } catch (BadBytecode bb) { final String msg = String.format( "Unable to perform field access transformation in method [%s]", methodName); throw new EnhancementException(msg, bb); } } }
private CtMethod generateFieldWriter( CtClass managedCtClass, CtField persistentField, AttributeTypeDescriptor typeDescriptor) { final String fieldName = persistentField.getName(); final String writerName = EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + fieldName; try { final CtMethod writer; if (!enhancementContext.isLazyLoadable(persistentField)) { writer = MethodWriter.addSetter(managedCtClass, fieldName, writerName); } else { writer = MethodWriter.write( managedCtClass, "public void %s(%s %s) {%n %s%n}", writerName, persistentField.getType().getName(), fieldName, typeDescriptor.buildWriteInterceptionBodyFragment(fieldName)); } if (enhancementContext.isCompositeClass(managedCtClass)) { writer.insertBefore( String.format( "if (%s != null) { %<s.callOwner(\".%s\"); }%n", EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME, fieldName)); } else if (enhancementContext.doDirtyCheckingInline(managedCtClass)) { writer.insertBefore( typeDescriptor.buildInLineDirtyCheckingBodyFragment( enhancementContext, persistentField)); } handleCompositeField(managedCtClass, persistentField, writer); if (enhancementContext.doBiDirectionalAssociationManagement(persistentField)) { handleBiDirectionalAssociation(managedCtClass, persistentField, writer); } return writer; } catch (CannotCompileException cce) { final String msg = String.format( "Could not enhance entity class [%s] to add field writer method [%s]", managedCtClass.getName(), writerName); throw new EnhancementException(msg, cce); } catch (NotFoundException nfe) { final String msg = String.format( "Could not enhance entity class [%s] to add field writer method [%s]", managedCtClass.getName(), writerName); throw new EnhancementException(msg, nfe); } }
/** * Build the method documentation. * * @param node the XML element that specifies which components to document * @param memberDetailsTree the content tree to which the documentation will be added */ public void buildMethodDoc(XMLNode node, Content memberDetailsTree) { if (writer == null) { return; } int size = methods.size(); if (size > 0) { Content methodDetailsTree = writer.getMethodDetailsTreeHeader(classDoc, memberDetailsTree); for (currentMethodIndex = 0; currentMethodIndex < size; currentMethodIndex++) { Content methodDocTree = writer.getMethodDocTreeHeader( (MethodDoc) methods.get(currentMethodIndex), methodDetailsTree); buildChildren(node, methodDocTree); methodDetailsTree.addContent( writer.getMethodDoc(methodDocTree, (currentMethodIndex == size - 1))); } memberDetailsTree.addContent(writer.getMethodDetails(methodDetailsTree)); } }
/** * Pops a type from the output frame stack. * * @param desc the descriptor of the type to be popped. Can also be a method descriptor (in this * case this method pops the types corresponding to the method arguments). */ private void pop(final String desc) { char c = desc.charAt(0); if (c == '(') { pop((MethodWriter.getArgumentsAndReturnSizes(desc) >> 2) - 1); } else if (c == 'J' || c == 'D') { pop(2); } else { pop(1); } }
/** * Build the comments for the method. Do nothing if {@link Configuration#nocomment} is set to * true. * * @param node the XML element that specifies which components to document * @param methodDocTree the content tree to which the documentation will be added */ public void buildMethodComments(XMLNode node, Content methodDocTree) { if (!configuration.nocomment) { MethodDoc method = (MethodDoc) methods.get(currentMethodIndex); if (method.inlineTags().length == 0) { DocFinder.Output docs = DocFinder.search(configuration, new DocFinder.Input(method)); method = docs.inlineTags != null && docs.inlineTags.length > 0 ? (MethodDoc) docs.holder : method; } // NOTE: When we fix the bug where ClassDoc.interfaceTypes() does // not pass all implemented interfaces, holder will be the // interface type. For now, it is really the erasure. writer.addComments(method.containingClass(), method, methodDocTree); } }
private void handleCompositeField( CtClass managedCtClass, CtField persistentField, CtMethod fieldWriter) throws NotFoundException, CannotCompileException { if (!persistentField.hasAnnotation(Embedded.class)) { return; } // make sure to add the CompositeOwner interface managedCtClass.addInterface(classPool.get(CompositeOwner.class.getName())); if (enhancementContext.isCompositeClass(managedCtClass)) { // if a composite have a embedded field we need to implement the TRACKER_CHANGER_NAME method // as well MethodWriter.write( managedCtClass, "" + "public void %1$s(String name) {%n" + " if (%2$s != null) { %2$s.callOwner(\".\" + name) ; }%n}", EnhancerConstants.TRACKER_CHANGER_NAME, EnhancerConstants.TRACKER_COMPOSITE_FIELD_NAME); } // cleanup previous owner fieldWriter.insertBefore( String.format( "" + "if (%1$s != null) { ((%2$s) %1$s).%3$s(\"%1$s\"); }%n", persistentField.getName(), CompositeTracker.class.getName(), EnhancerConstants.TRACKER_COMPOSITE_CLEAR_OWNER)); // trigger track changes fieldWriter.insertAfter( String.format( "" + "((%2$s) %1$s).%4$s(\"%1$s\", (%3$s) this);%n" + "%5$s(\"%1$s\");", persistentField.getName(), CompositeTracker.class.getName(), CompositeOwner.class.getName(), EnhancerConstants.TRACKER_COMPOSITE_SET_OWNER, EnhancerConstants.TRACKER_CHANGER_NAME)); }
/** * Build the tag information. * * @param node the XML element that specifies which components to document * @param methodDocTree the content tree to which the documentation will be added */ public void buildTagInfo(XMLNode node, Content methodDocTree) { writer.addTags((MethodDoc) methods.get(currentMethodIndex), methodDocTree); }
/** * Build the deprecation information. * * @param node the XML element that specifies which components to document * @param methodDocTree the content tree to which the documentation will be added */ public void buildDeprecationInfo(XMLNode node, Content methodDocTree) { writer.addDeprecated((MethodDoc) methods.get(currentMethodIndex), methodDocTree); }
/** * Build the signature. * * @param node the XML element that specifies which components to document * @param methodDocTree the content tree to which the documentation will be added */ public void buildSignature(XMLNode node, Content methodDocTree) { methodDocTree.addContent(writer.getSignature((MethodDoc) methods.get(currentMethodIndex))); }
/** * Returns the bytecode of the class that was build with this class writer. * * @return the bytecode of the class that was build with this class writer. */ public byte[] toByteArray() { // computes the real size of the bytecode of this class int size = 24 + 2 * interfaceCount; int nbFields = 0; FieldWriter fb = firstField; while (fb != null) { ++nbFields; size += fb.getSize(); fb = fb.next; } int nbMethods = 0; MethodWriter mb = firstMethod; while (mb != null) { ++nbMethods; size += mb.getSize(); mb = mb.next; } int attributeCount = 0; if (signature != 0) { ++attributeCount; size += 8; newUTF8("Signature"); } if (sourceFile != 0) { ++attributeCount; size += 8; newUTF8("SourceFile"); } if (sourceDebug != null) { ++attributeCount; size += sourceDebug.length + 4; newUTF8("SourceDebugExtension"); } if (enclosingMethodOwner != 0) { ++attributeCount; size += 10; newUTF8("EnclosingMethod"); } if ((access & Opcodes.ACC_DEPRECATED) != 0) { ++attributeCount; size += 6; newUTF8("Deprecated"); } if ((access & Opcodes.ACC_SYNTHETIC) != 0 && (version & 0xffff) < Opcodes.V1_5) { ++attributeCount; size += 6; newUTF8("Synthetic"); } if (innerClasses != null) { ++attributeCount; size += 8 + innerClasses.length; newUTF8("InnerClasses"); } if (anns != null) { ++attributeCount; size += 8 + anns.getSize(); newUTF8("RuntimeVisibleAnnotations"); } if (ianns != null) { ++attributeCount; size += 8 + ianns.getSize(); newUTF8("RuntimeInvisibleAnnotations"); } if (attrs != null) { attributeCount += attrs.getCount(); size += attrs.getSize(this, null, 0, -1, -1); } size += pool.length; // allocates a byte vector of this size, in order to avoid unnecessary // arraycopy operations in the ByteVector.enlarge() method ByteVector out = new ByteVector(size); out.putInt(0xCAFEBABE).putInt(version); out.putShort(index).putByteArray(pool.data, 0, pool.length); out.putShort(access).putShort(name).putShort(superName); out.putShort(interfaceCount); for (int i = 0; i < interfaceCount; ++i) { out.putShort(interfaces[i]); } out.putShort(nbFields); fb = firstField; while (fb != null) { fb.put(out); fb = fb.next; } out.putShort(nbMethods); mb = firstMethod; while (mb != null) { mb.put(out); mb = mb.next; } out.putShort(attributeCount); if (signature != 0) { out.putShort(newUTF8("Signature")).putInt(2).putShort(signature); } if (sourceFile != 0) { out.putShort(newUTF8("SourceFile")).putInt(2).putShort(sourceFile); } if (sourceDebug != null) { int len = sourceDebug.length - 2; out.putShort(newUTF8("SourceDebugExtension")).putInt(len); out.putByteArray(sourceDebug.data, 2, len); } if (enclosingMethodOwner != 0) { out.putShort(newUTF8("EnclosingMethod")).putInt(4); out.putShort(enclosingMethodOwner).putShort(enclosingMethod); } if ((access & Opcodes.ACC_DEPRECATED) != 0) { out.putShort(newUTF8("Deprecated")).putInt(0); } if ((access & Opcodes.ACC_SYNTHETIC) != 0 && (version & 0xffff) < Opcodes.V1_5) { out.putShort(newUTF8("Synthetic")).putInt(0); } if (innerClasses != null) { out.putShort(newUTF8("InnerClasses")); out.putInt(innerClasses.length + 2).putShort(innerClassesCount); out.putByteArray(innerClasses.data, 0, innerClasses.length); } if (anns != null) { out.putShort(newUTF8("RuntimeVisibleAnnotations")); anns.put(out); } if (ianns != null) { out.putShort(newUTF8("RuntimeInvisibleAnnotations")); ianns.put(out); } if (attrs != null) { attrs.put(this, null, 0, -1, -1, out); } if (invalidFrames) { ClassWriter cw = new ClassWriter(COMPUTE_FRAMES); new ClassReader(out.data).accept(cw, ClassReader.SKIP_FRAMES); return cw.toByteArray(); } return out.data; }
private void handleBiDirectionalAssociation( CtClass managedCtClass, CtField persistentField, CtMethod fieldWriter) throws NotFoundException, CannotCompileException { if (!isPossibleBiDirectionalAssociation(persistentField)) { return; } final CtClass targetEntity = getTargetEntityClass(persistentField); if (targetEntity == null) { log.debugf( "Could not find type of bi-directional association for field [%s#%s]", managedCtClass.getName(), persistentField.getName()); return; } final String mappedBy = getMappedBy(persistentField, targetEntity); if (mappedBy.isEmpty()) { log.warnf( "Could not find bi-directional association for field [%s#%s]", managedCtClass.getName(), persistentField.getName()); return; } // create a temporary getter and setter on the target entity to be able to compile our code final String mappedByGetterName = EnhancerConstants.PERSISTENT_FIELD_READER_PREFIX + mappedBy; final String mappedBySetterName = EnhancerConstants.PERSISTENT_FIELD_WRITER_PREFIX + mappedBy; MethodWriter.addGetter(targetEntity, mappedBy, mappedByGetterName); MethodWriter.addSetter(targetEntity, mappedBy, mappedBySetterName); if (persistentField.hasAnnotation(OneToOne.class)) { // only unset when $1 != null to avoid recursion fieldWriter.insertBefore( String.format( "if ($0.%s != null && $1 != null) $0.%<s.%s(null);%n", persistentField.getName(), mappedBySetterName)); fieldWriter.insertAfter( String.format( "if ($1 != null && $1.%s() != $0) $1.%s($0);%n", mappedByGetterName, mappedBySetterName)); } if (persistentField.hasAnnotation(OneToMany.class)) { // only remove elements not in the new collection or else we would loose those elements // don't use iterator to avoid ConcurrentModException fieldWriter.insertBefore( String.format( "if ($0.%s != null) { Object[] array = $0.%<s.toArray(); for (int i = 0; i < array.length; i++) { %s target = (%<s) array[i]; if ($1 == null || !$1.contains(target)) target.%s(null); } }%n", persistentField.getName(), targetEntity.getName(), mappedBySetterName)); fieldWriter.insertAfter( String.format( "if ($1 != null) { Object[] array = $1.toArray(); for (int i = 0; i < array.length; i++) { %s target = (%<s) array[i]; if (target.%s() != $0) target.%s((%s)$0); } }%n", targetEntity.getName(), mappedByGetterName, mappedBySetterName, managedCtClass.getName())); } if (persistentField.hasAnnotation(ManyToOne.class)) { fieldWriter.insertBefore( String.format( "if ($0.%1$s != null && $0.%1$s.%2$s() != null) $0.%1$s.%2$s().remove($0);%n", persistentField.getName(), mappedByGetterName)); // check .contains($0) to avoid double inserts (but preventing duplicates) fieldWriter.insertAfter( String.format( "if ($1 != null) { java.util.Collection c = $1.%s(); if (c != null && !c.contains($0)) c.add($0); }%n", mappedByGetterName)); } if (persistentField.hasAnnotation(ManyToMany.class)) { fieldWriter.insertBefore( String.format( "if ($0.%s != null) { Object[] array = $0.%<s.toArray(); for (int i = 0; i < array.length; i++) { %s target = (%<s) array[i]; if ($1 == null || !$1.contains(target)) target.%s().remove($0); } }%n", persistentField.getName(), targetEntity.getName(), mappedByGetterName)); fieldWriter.insertAfter( String.format( "if ($1 != null) { Object[] array = $1.toArray(); for (int i = 0; i < array.length; i++) { %s target = (%<s) array[i]; java.util.Collection c = target.%s(); if ( c != $0 && c != null) c.add($0); } }%n", targetEntity.getName(), mappedByGetterName)); } // implementation note: association management @OneToMany and @ManyToMay works for add() // operations but for remove() a snapshot of the collection is needed so we know what // associations to break. // another approach that could force that behavior would be to return // Collections.unmodifiableCollection() ... }