/** * 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.common.tools.jdt; import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.List; import java.util.Map; import net.atos.optimus.common.tools.Activator; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Path; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTParser; import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.EnumConstantDeclaration; import org.eclipse.jdt.core.dom.FieldDeclaration; import org.eclipse.jdt.core.dom.MemberValuePair; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.NormalAnnotation; import org.eclipse.jdt.core.dom.StringLiteral; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.Document; import org.eclipse.jface.text.IDocument; import org.eclipse.text.edits.MalformedTreeException; /** * Helper class to manage the hashcode computation, in Generated Annotations * (value used by the Java Code Merger. * * @author Maxence Vanbésien (mvaawl@gmail.com) * @since 1.0 * */ @SuppressWarnings("unchecked") public class GeneratedAnnotationHelper { /** * Default hashCode value to overwrite */ public static final String VALUE_TO_OVERWRITE = "ToBeCalculated"; /** * Comment parameter name of the Generated annotation */ public static final String COMMENTS_PARAM = "comments"; private static final String COMMENT_SEPARATOR = ","; private static final int HASHCODE_RANK_IN_COMMENT = 0; private static final int UNIQUEID_RANK_IN_COMMENT = 1; /** * Compiler options used inside this Java code Merger */ public static Map<Object, Object> compilerOptions = null; static { // Set compiler options to 1.5 level GeneratedAnnotationHelper.compilerOptions = JavaCore.getOptions(); GeneratedAnnotationHelper.compilerOptions.put(JavaCore.COMPILER_SOURCE, JavaCore.VERSION_1_5); } /** * Visitor which role is to detect methods from Compilation unit, where * Generated annotation is missing * @author Maxence Vanbésien (mvaawl@gmail.com) * @since 1.0 */ private static class GeneratedAnnotationVisitor extends ASTVisitor { /** * Methods to process list */ private List<NormalAnnotation> methodsToProcess = new ArrayList<NormalAnnotation>(); /** * Execute this visitor on Compilation Unit passed as parameter * * @param cu * AST Compilation Unit */ public void process(CompilationUnit cu) { this.methodsToProcess.clear(); cu.accept(this); } /* * (non-Javadoc) * * @see * org.eclipse.jdt.core.dom.ASTVisitor#visit(org.eclipse.jdt.core.dom * .NormalAnnotation) */ @Override public boolean visit(NormalAnnotation node) { if (this.needToEnrichAnnotation(node)) this.methodsToProcess.add(node); return super.visit(node); } /** * Looks in declaration to find if the Generated annotation is present * * @param methodDeclaration * MethodDeclaration * @return true is annotation is present, false otherwise */ private boolean needToEnrichAnnotation(NormalAnnotation annotation) { String hashcode = getGeneratedAnnotationHashcode(annotation); return VALUE_TO_OVERWRITE.equals(hashcode); } /** * @return List of method that should be processed by post process (i.e. * without Generated annotation) */ public List<NormalAnnotation> getAnnotationsToEnrich() { return this.methodsToProcess; } } /** *Stores source code passed as parameter, in file, which path is passed as * parameter. * * @param source * : Source code * @param path * : destination file path */ public static void writeSourceToFile(String source, String path) { IPath cuLocation = new Path(path); ICompilationUnit javaCU = null; IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects(); IProject myProject = null; for (int i = 0; i < projects.length && myProject == null; i++) { if (projects[i].getLocation().isPrefixOf(cuLocation)) { myProject = projects[i]; } } if (myProject != null) { IPath relativePath = cuLocation.removeFirstSegments(myProject.getLocation() .segmentCount()); IFile file = myProject.getFile(relativePath); if (!file.exists()) try { file.getParent().refreshLocal(IResource.DEPTH_ONE, new NullProgressMonitor()); } catch (CoreException e) { Activator.getDefault().logError( "Exception thrown while refreshing package file parent of " + cuLocation.toString(), e); } javaCU = (ICompilationUnit) JavaCore.create(file); } if (javaCU == null || !javaCU.exists()) { Activator.getDefault().logError( "Java Compilation Unit couldn't be found from File at path: " + cuLocation.toString(), new FileNotFoundException()); return; } try { // Writes file on disk javaCU.getBuffer().setContents(source); javaCU.getBuffer().save(null, true); } catch (Exception e) { Activator.getDefault().logError("Error when writing file on disk " + javaCU, e); } } /** * Updates the source code passed as parameter (updates the annotation * value), and stores it in file, according to path passed as parameter. * * @param source * : Source code * @param path * : destination file path */ public static void updateAndWriteHashcodeInSource(String source, String path) { String updatedSource = updateHashcodeInSource(source); if (!source.equals(updatedSource)) { writeSourceToFile(updatedSource, path); } } /** * Updates the source passed as parameter, by populating the hashCode * values. * * @param source * : Source * @return updated Source */ public static String updateHashcodeInSource(String source) { ASTParser parser = ASTParserFactory.INSTANCE.newParser(); parser.setSource(source.toCharArray()); parser.setCompilerOptions(GeneratedAnnotationHelper.compilerOptions); CompilationUnit cu = (CompilationUnit) parser.createAST(null); // Create and call the new Visitor, which role will be to // gather the methods on which the Generated annotation is yet not // defined. GeneratedAnnotationVisitor ev = new GeneratedAnnotationVisitor(); ev.process(cu); // Get the annotations to modify, fetched by the Visitor List<NormalAnnotation> annotationsToEnrich = ev.getAnnotationsToEnrich(); if (annotationsToEnrich.size() > 0) { cu.recordModifications(); boolean modified = false; for (NormalAnnotation annotationToEnrich : annotationsToEnrich) { // For each annotation to modify, replace the null value of // the comments field by the method's hashcode ASTNode parent = annotationToEnrich.getParent(); if (parent instanceof MethodDeclaration) { MethodDeclaration methodDeclaration = (MethodDeclaration) parent; if (methodDeclaration.getBody() != null) { int hashCode = ASTHelper.methodBodyHashCode(methodDeclaration.getBody().toString()); modified = setGeneratedAnnotationHashcode(cu, annotationToEnrich, String.valueOf(hashCode)); } else { modified = setGeneratedAnnotationHashcode(cu, annotationToEnrich, "0"); } } else if (parent instanceof EnumConstantDeclaration) { EnumConstantDeclaration enumConstantDeclaration = (EnumConstantDeclaration) parent; int hashCode = ASTHelper.enumConstantHashCode(enumConstantDeclaration); modified = setGeneratedAnnotationHashcode(cu, annotationToEnrich, String.valueOf(hashCode)); } else if (parent instanceof FieldDeclaration) { modified = setGeneratedAnnotationHashcode(cu, annotationToEnrich, "0"); } } if (modified) { // Writes file on disk IDocument document = new Document(source); try { cu.rewrite(document, null).apply(document); return document.get(); } catch (MalformedTreeException e) { Activator.getDefault().logError( "The compilation unit could not be" + " written on FileSystem because of thrown Exception.", e); } catch (BadLocationException e) { Activator.getDefault().logError( "The compilation unit could not be" + " written on FileSystem because of thrown Exception", e); } } } return source; } //-------------------------------------------------------------------------------------- // // MANAGES MULTIPLE INFO IN COMMENT (separated by ',') // //-------------------------------------------------------------------------------------- public static boolean setGeneratedAnnotationHashcode(CompilationUnit cu, NormalAnnotation annotation, String hashcode) { return setGeneratedAnnotationPart(cu, annotation, hashcode, HASHCODE_RANK_IN_COMMENT); } public static boolean setGeneratedAnnotationUniqueId(CompilationUnit cu, NormalAnnotation annotation, String uniqueId) { return setGeneratedAnnotationPart(cu, annotation, uniqueId, UNIQUEID_RANK_IN_COMMENT); } public static String getGeneratedAnnotationHashcode(NormalAnnotation annotation) { StringLiteral comment = getGeneratedAnnotationComment(annotation); if(comment != null) { return getGeneratedAnnotationCommentPart(comment.getLiteralValue(), HASHCODE_RANK_IN_COMMENT); } return null; } public static String getGeneratedAnnotationUniqueId(NormalAnnotation annotation) { StringLiteral comment = getGeneratedAnnotationComment(annotation); if(comment != null) { return getGeneratedAnnotationCommentPart(comment.getLiteralValue(), UNIQUEID_RANK_IN_COMMENT); } return null; } public static String getHashCodeFromComment(String comment) { return getGeneratedAnnotationCommentPart(comment, HASHCODE_RANK_IN_COMMENT); } public static String getUniqueIdFromComment(String comment) { return getGeneratedAnnotationCommentPart(comment, UNIQUEID_RANK_IN_COMMENT); } private static boolean setGeneratedAnnotationPart(CompilationUnit cu, NormalAnnotation annotation, String newPartValue, int rankInComment) { StringLiteral oldCommentLiteral = getGeneratedAnnotationComment(annotation); if (oldCommentLiteral != null) { String[] oldCommentParts = oldCommentLiteral.getLiteralValue().split(COMMENT_SEPARATOR); StringBuilder newCommentSB = new StringBuilder(25).append(""); for(int i = 0; i<oldCommentParts.length; i++) { if(i == rankInComment) { newCommentSB.append(newPartValue); } else { newCommentSB.append(oldCommentParts[i]); } if(i<(oldCommentParts.length-1)) { newCommentSB.append(COMMENT_SEPARATOR); } } oldCommentLiteral.setLiteralValue(newCommentSB.toString()); return true; } return false; } private static String getGeneratedAnnotationCommentPart(String comment, int rankInComment) { if(comment != null) { String[] commentParts = comment.split(COMMENT_SEPARATOR); if(commentParts != null && commentParts.length>rankInComment) return commentParts[rankInComment]; } return null; } private static StringLiteral getGeneratedAnnotationComment(NormalAnnotation annotation) { MemberValuePair mvp = null; for (Object o : annotation.values()) { if (o instanceof MemberValuePair) { MemberValuePair tempMVP = (MemberValuePair) o; if (COMMENTS_PARAM.equals(tempMVP.getName().toString())) { mvp = tempMVP; break; } } } if (mvp != null && mvp.getValue() != null && mvp.getValue() instanceof StringLiteral) { StringLiteral literal = (StringLiteral) mvp.getValue(); return literal; } else{ return null; } } }