@Override public void processField(CtField field) throws Exception { if (!field.getName().equals("Id")) { CtMethod ctMethod = checkSetMethodNull(field); ctMethod.insertAfter("this.modify();"); } }
private void changeMethod(CtClass ctClass, CtMethod ctMethod) throws CannotCompileException { // basically your before-advice... ctMethod.insertBefore("System.out.println(\"started method at \" + new java.util.Date());"); // basically your after-advice... ctMethod.insertAfter("System.out.println(\"ended method at \" + new java.util.Date());"); // basically your around-advice... String methodName = ctMethod.getName(); String proxyName = methodName + "_$proxy"; CtMethod proxy = CtNewMethod.copy(ctMethod, proxyName, ctClass, null); ctMethod.setName(ctMethod.getName() + "_orig"); proxy.setBody( "{ System.out.println(\"hoot!\"); return $proceed($$);}", "this", ctMethod.getName()); proxy.setName(methodName); ctClass.addMethod(proxy); }
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)); }
private static void insertAfter(CtMethod m, String newBody) throws Exception { removeNativeModifier(m); m.insertAfter(newBody); }
@Override public byte[] transform( ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { // Don't transform system classes. This reason is: // - To improve performance // - To avoid unexpected behaviors. // For example, if transforms java.lang.invoke.** classes in Java8 // even without any ctClass modification, the classes become broken // and Java stream API call fails unexpectedly. // Maybe this is because CtClass instance generated by ClassPool.makeClass method // is cached on global default ClassPool instance. if (isJavaSystemClassName(className)) { return null; } // TODO don't need to do anything for java package classes ClassPool classPool = ClassPool.getDefault(); String hookClassName = HookMethodDef.class.getCanonicalName(); String initializeSrc = hookInitializeSrc(); boolean transformed = false; InputStream stream = null; try { stream = new ByteArrayInputStream(classfileBuffer); CtClass ctClass = null; try { ctClass = classPool.makeClass(stream, true); } catch (RuntimeException e) { // makeClass raises RuntimeException when the existing class is frozen. // Since frozen classes are maybe system class, just ignore this exception return null; } for (Pair<CtMethod, TestMethod> pair : allSubMethods(srcTree, ctClass)) { CtMethod ctSubMethod = pair.getLeft(); TestMethod subMethod = pair.getRight(); if (ctSubMethod.isEmpty()) { logger.info("skip empty method: " + ctSubMethod.getLongName()); continue; // cannot hook empty method } String subClassQualifiedName = subMethod.getTestClass().getQualifiedName(); String subMethodSimpleName = subMethod.getSimpleName(); String subMethodArgClassesStr = TestMethod.argClassQualifiedNamesToArgClassesStr( getArgClassQualifiedNames(ctSubMethod)); for (int i = 0; i < subMethod.getCodeBody().size(); i++) { CodeLine codeLine = subMethod.getCodeBody().get(i); if (i + 1 < subMethod.getCodeBody().size()) { CodeLine nextCodeLine = subMethod.getCodeBody().get(i + 1); assert codeLine.getEndLine() <= nextCodeLine.getStartLine(); if (codeLine.getEndLine() == nextCodeLine.getStartLine()) { // - if multiple statements exist on a line, insert hook only after the last statement // - avoid insertion at the middle of the statement. // The problem happens when multi-line statements are like: // method(1);method( // 2); continue; } } // Hook should be inserted just after the code has finished // since the code inserted by the insertAt method is inserted just before the specified // line. int insertedLine = subMethod.getCodeBody().get(i).getEndLine() + 1; int actualInsertedLine = ctSubMethod.insertAt(insertedLine, false, null); ctSubMethod.insertAt( insertedLine, String.format( "%s%s.beforeCodeLineHook(\"%s\",\"%s\",\"%s\",\"%s\",%d, %d);", initializeSrc, hookClassName, subClassQualifiedName, subMethodSimpleName, subMethodSimpleName, subMethodArgClassesStr, codeLine.getStartLine(), actualInsertedLine)); transformed = true; } } CtClass exceptionType = classPool.get(Throwable.class.getCanonicalName()); for (Pair<CtMethod, TestMethod> pair : allRootMethods(srcTree, ctClass)) { CtMethod ctRootMethod = pair.getLeft(); TestMethod rootMethod = pair.getRight(); if (ctRootMethod.isEmpty()) { continue; // cannot hook empty method } String rootClassQualifiedName = rootMethod.getTestClass().getQualifiedName(); String rootMethodSimpleName = rootMethod.getSimpleName(); String rootMethodArgClassesStr = TestMethod.argClassQualifiedNamesToArgClassesStr( getArgClassQualifiedNames(ctRootMethod)); for (int i = 0; i < rootMethod.getCodeBody().size(); i++) { CodeLine codeLine = rootMethod.getCodeBody().get(i); if (i + 1 < rootMethod.getCodeBody().size()) { CodeLine nextCodeLine = rootMethod.getCodeBody().get(i + 1); assert codeLine.getEndLine() <= nextCodeLine.getStartLine(); if (codeLine.getEndLine() == nextCodeLine.getStartLine()) { // - if multiple statements exist on a line, insert hook only after the last statement // - avoid insertion at the middle of the statement. // The problem happens when multi-line statements are like: // method(1);method( // 2); continue; // TODO screen capture is not taken correctly for multiple statements in a line } } // Hook should be inserted just after the code has finished // since the code inserted by the insertAt method is inserted just before the specified // line. int insertedLine = rootMethod.getCodeBody().get(i).getEndLine() + 1; int actualInsertedLine = ctRootMethod.insertAt(insertedLine, false, null); ctRootMethod.insertAt( insertedLine, String.format( "%s%s.beforeCodeLineHook(\"%s\",\"%s\",\"%s\",\"%s\",%d,%d);", initializeSrc, hookClassName, rootClassQualifiedName, rootMethodSimpleName, rootMethodSimpleName, rootMethodArgClassesStr, codeLine.getStartLine(), actualInsertedLine)); } ctRootMethod.insertBefore( String.format( "%s%s.beforeMethodHook(\"%s\",\"%s\",\"%s\");", initializeSrc, hookClassName, rootClassQualifiedName, rootMethodSimpleName, rootMethodSimpleName)); ctRootMethod.addCatch( String.format( "{ %s%s.methodErrorHook(\"%s\",\"%s\",$e); throw $e; }", initializeSrc, hookClassName, rootClassQualifiedName, rootMethodSimpleName), exceptionType); ctRootMethod.insertAfter( String.format( "%s%s.afterMethodHook(\"%s\",\"%s\");", initializeSrc, hookClassName, rootClassQualifiedName, rootMethodSimpleName), true); transformed = true; } // don't transform not changed ctClass // (to improve performance and avoid unexpected error) if (transformed) { logger.info("transform " + className); return ctClass.toBytecode(); } else { return null; } } catch (CannotCompileException e) { // print error since exception in transform method is just ignored System.err.println("exception on " + className); e.printStackTrace(); throw new IllegalClassFormatException(e.getLocalizedMessage()); } catch (Exception e) { // print error since exception in transform method is just ignored System.err.println("exception on " + className); e.printStackTrace(); throw new RuntimeException(e); } finally { IOUtils.closeQuietly(stream); } }
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() ... }