/** * Optimus, framework for Model Transformation * * Copyright (C) 2013 Worldline or third-party contributors as * indicated by the @author tags or express copyright attribution * statements applied by the authors. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ package net.atos.optimus.m2t.merger.java.core; import static org.eclipse.jdt.core.dom.MethodDeclaration.BODY_PROPERTY; import static org.eclipse.jdt.core.dom.MethodDeclaration.JAVADOC_PROPERTY; import static org.eclipse.jdt.core.dom.MethodDeclaration.MODIFIERS2_PROPERTY; import static org.eclipse.jdt.core.dom.MethodDeclaration.PARAMETERS_PROPERTY; import java.util.ArrayList; import java.util.List; import java.util.Set; import net.atos.optimus.common.tools.jdt.ASTHelper; import net.atos.optimus.common.tools.jdt.JavaCodeHelper; import net.atos.optimus.common.tools.jdt.jstcomp.ASTThrownExceptionsHelper; import net.atos.optimus.m2t.merger.java.core.internal.Activator; import net.atos.optimus.m2t.merger.java.core.internal.MergerLogger; import net.atos.optimus.m2t.merger.java.core.internal.MergerLoggerMessages; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.AbstractTypeDeclaration; import org.eclipse.jdt.core.dom.Annotation; import org.eclipse.jdt.core.dom.Block; import org.eclipse.jdt.core.dom.BodyDeclaration; import org.eclipse.jdt.core.dom.ChildListPropertyDescriptor; import org.eclipse.jdt.core.dom.ChildPropertyDescriptor; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.Name; import org.eclipse.jdt.core.dom.SingleVariableDeclaration; import org.eclipse.jdt.core.dom.rewrite.ASTRewrite; import org.eclipse.jdt.core.dom.rewrite.ListRewrite; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.Document; /** * Merge two MethodDeclaration instance. A MethodDeclaration is composed by a * javadoc, modifiers, parameters list and exceptions. * * @author Maxence Vanbésien (mvaawl@gmail.com) * @since 1.0 */ class MethodDeclarationMerger extends BodyDeclarationMerger { private final Document generated; /** * Constructor * * @param log * A Merger Logger instance * @param astr * An ASTRewrite instance * @param generated * A document containing generated code (used to merge method * bodies) */ public MethodDeclarationMerger(ASTRewrite astr, Document generated) { super(astr); this.generated = generated; } /** * Returns a StringPlaceholder block containing the source code of a given * block This is usefull to preserve comments of the original code. (these * commments are not representated in the AST) * * @param block * the node to get the source of * @return a StringPlaceholder block containing the block's source code */ private Block createSourceCodePlaceholder(Block block) { // For Abstract method and interfaces methods if (block == null) { return null; } try { return (Block) astr.createStringPlaceholder(generated.get(block.getStartPosition(), block.getLength()), ASTNode.BLOCK); } catch (BadLocationException e) { if (MergerLogger.enabled()) MergerLogger.log(MergerLoggerMessages.METHODDECL_ERRORSETBODY.value(e.toString())); Activator.getDefault().logError("An exception has been thrown", e); // We were not able to extract the original source code, return the // node back return block; } } @Override protected ChildPropertyDescriptor getJavadocPropertyDescriptor() { return JAVADOC_PROPERTY; } @Override protected ChildListPropertyDescriptor getModifiersPropertyDescriptor() { return MODIFIERS2_PROPERTY; } @Override public void insert(ASTNode parent, BodyDeclaration originalNode) { super.insert(parent, originalNode); MethodDeclaration lastMethod = null; AbstractTypeDeclaration parentType = (AbstractTypeDeclaration) parent; /* * Read all methods of the type declaration (used to insert this method * at the right place) */ List<MethodDeclaration> existingMethods = JavaCodeHelper.getTypedChildren(parentType, MethodDeclaration.class); // Save the last method if a method already exist if (existingMethods.size() > 0) { lastMethod = existingMethods.get(existingMethods.size() - 1); } // We need to clone this node, to change its body MethodDeclaration methodToInsert = (MethodDeclaration) ASTNode.copySubtree(astr.getAST(), originalNode); // Set the method Body as a copy of the original (not parsed) body // This preserves comments in the generated method body methodToInsert.setBody(createSourceCodePlaceholder(methodToInsert.getBody())); // perform the rewrite ListRewrite lw = astr.getListRewrite(parentType, JavaCodeHelper.getBodyDeclarationProperty(parentType.getClass())); if (lastMethod == null) { // ... at the end of the type if this is the first method lw.insertLast(methodToInsert, null); } else { // ... after the previous method if a method already exist lw.insertAfter(methodToInsert, lastMethod, null); } } @SuppressWarnings("unchecked") @Override public void merge(BodyDeclaration existing, BodyDeclaration generated, Set<String> generatedAnnotations) { super.merge(existing, generated, generatedAnnotations); MethodDeclaration existingMethod = (MethodDeclaration) existing; MethodDeclaration generatedMethod = (MethodDeclaration) generated; int existingMethodBodyAnnotationHashCode = 0; int generatedMethodBodyAnnotationHashCode = 0; int existingMethodBodyCalculatedHashCode = 0; try { /* * Try to read the hashcode of the existing method in the associated * * @Generated annotation */ existingMethodBodyAnnotationHashCode = ASTHelper.getHashCodeFromGeneratedAnnotation(existingMethod); /* * Test if the method body is not null before doing the hash code * processing */ if (existingMethod.getBody() != null) { existingMethodBodyCalculatedHashCode = ASTHelper .methodBodyHashCode(existingMethod.getBody().toString()); } if (existingMethodBodyAnnotationHashCode == existingMethodBodyCalculatedHashCode) { /* * The calculated hashCode body of the existing method is equal * to the hashCode read from @Generated of the method. The body * has not been customized by the user */ /* * Try to read the hashcode of the generated method in the * associated @Generated annotation */ generatedMethodBodyAnnotationHashCode = ASTHelper.getHashCodeFromGeneratedAnnotation(generatedMethod); if (existingMethodBodyAnnotationHashCode != generatedMethodBodyAnnotationHashCode) { /* * The generated method body and the existing method body * are not the same. The existing method body must (and can) * be updated */ replaceMethodBody(existingMethod, generatedMethod); replaceGeneratedAnnotation(existingMethod, generatedMethod); } } } catch (IllegalArgumentException iae) { /* * If this excpetion is thrown, the associated annotation doesn't * contain this information. In this case, copy the new method body */ replaceMethodBody(existingMethod, generatedMethod); replaceGeneratedAnnotation(existingMethod, generatedMethod); } catch (UnsupportedOperationException uoe) { // Nothing to do } // Updates the name in case of refactoring: if (MergerLogger.enabled()) MergerLogger.log(MergerLoggerMessages.METHODDECL_RENAME.value(existingMethod.getName().getIdentifier(), generatedMethod .getName().getIdentifier())); astr.replace(existingMethod.getName(), generatedMethod.getName(), null); // Return type is replaced (test before if return types are not null) if (existingMethod.getReturnType2() != null && generatedMethod.getReturnType2() != null) { mergeTypes(existingMethod.getReturnType2(), generatedMethod.getReturnType2()); } // Exceptions are merged mergeExceptions(existingMethod, generatedMethod); // Parameters from generated methods should become parameters for // existing method. if (existingMethod.parameters() != null && generatedMethod.parameters() != null) { if (MergerLogger.enabled()) MergerLogger.log(MergerLoggerMessages.METHODDECL_REPLACEPARAMS.value(JavaCodeHelper.getName(existing), JavaCodeHelper.getDescription(existing))); if (MergerLogger.enabled()) MergerLogger.log(getParametersLog(existingMethod, generatedMethod)); ListRewrite lw = astr.getListRewrite(existingMethod, PARAMETERS_PROPERTY); for (SingleVariableDeclaration svd : (List<SingleVariableDeclaration>) existingMethod.parameters()) { lw.remove(svd, null); } for (SingleVariableDeclaration svd : (List<SingleVariableDeclaration>) generatedMethod.parameters()) { lw.insertLast(svd, null); } } } private String getParametersLog(MethodDeclaration existingMethod, MethodDeclaration generatedMethod) { StringBuffer sb = new StringBuffer("\t\tWas :"); if (existingMethod.parameters() == null || existingMethod.parameters().size() == 0) sb.append("[none]"); else for (Object param : existingMethod.parameters()) { sb.append("[").append(param).append("]"); } sb.append(", now : "); if (generatedMethod.parameters() == null || generatedMethod.parameters().size() == 0) sb.append("[none]"); else for (Object param : generatedMethod.parameters()) { sb.append("[").append(param).append("]"); } return sb.toString(); } /** * Replace method body */ private void replaceMethodBody(MethodDeclaration existingMethod, MethodDeclaration generatedMethod) { Block generatedBody = generatedMethod.getBody(); Block existingBody = existingMethod.getBody(); if (generatedBody == null) { /* Nothing to do */ return; } if (existingBody != null) { if (MergerLogger.enabled()) MergerLogger.log(MergerLoggerMessages.METHODDECL_REPLACEBODY.value(JavaCodeHelper.getName(existingMethod), JavaCodeHelper.getDescription(existingMethod))); } else { if (MergerLogger.enabled()) MergerLogger.log(MergerLoggerMessages.METHODDECL_ADDBODY.value(JavaCodeHelper.getName(existingMethod), JavaCodeHelper.getDescription(existingMethod))); } astr.set(existingMethod, BODY_PROPERTY, createSourceCodePlaceholder(generatedBody), null); } /** * Replace @Generated annotation */ private void replaceGeneratedAnnotation(MethodDeclaration existingMethod, MethodDeclaration generatedMethod) { Annotation existingAnnotation = ASTHelper.getAnnotation(JavaCodeHelper.GENERATED_SIMPLECLASSNAME, existingMethod); Annotation generatedAnnotation = ASTHelper.getAnnotation(JavaCodeHelper.GENERATED_SIMPLECLASSNAME, generatedMethod); if (existingAnnotation != null && generatedAnnotation != null && generatedAnnotation.isNormalAnnotation()) { astr.replace(existingAnnotation, generatedAnnotation, null); } } /** * Merge exceptions from existingMethod and generatedMethod to an AST * Rewrite object instance. * * @param astr * The ASTRewrite object instance containing the merge result */ protected void mergeExceptions(MethodDeclaration existingMethod, MethodDeclaration generatedMethod) { List<String> existingExceptions = new ArrayList<String>(3); // Create an annotations list in String format. for (Name n : ASTThrownExceptionsHelper.getThrownExceptionNames(existingMethod)) { existingExceptions.add(JavaCodeHelper.getSimpleName(n)); } /* * Get a ListRewrite object used to modify annotations in the existing * code. */ ListRewrite lw = astr.getListRewrite(existingMethod, ASTThrownExceptionsHelper.getThrownExceptionsPropertyDescriptor(existingMethod)); // For each annotation of the generated source for (Name n : ASTThrownExceptionsHelper.getThrownExceptionNames(generatedMethod)) { if (!existingExceptions.contains(JavaCodeHelper.getSimpleName(n))) { /* * This exception is containing in the generated code but not in * the existing code => Add this exception in the merge result. */ if (MergerLogger.enabled()) MergerLogger.log(MergerLoggerMessages.METHODDECL_ADDEXCEPTION.value(n.getFullyQualifiedName(), JavaCodeHelper.getName(existingMethod), JavaCodeHelper.getDescription(existingMethod))); lw.insertLast(n, null); } } } }