Java tutorial
/* license-start * * Copyright (C) 2008 - 2013 Crispico, <http://www.crispico.com/>. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation version 3. * * This program 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 General Public License for more details, at <http://www.gnu.org/licenses/>. * * Contributors: * Crispico - Initial API and implementation * * license-end */ package com.crispico.flower.mp.metamodel.codesyncjava.algorithm; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.InputStreamReader; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.core.internal.resources.ResourceException; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IResource; import org.eclipse.emf.common.util.BasicDiagnostic; import org.eclipse.emf.common.util.Diagnostic; import org.eclipse.emf.common.util.DiagnosticChain; import org.eclipse.emf.common.util.EMap; import org.eclipse.emf.ecore.EAnnotation; import org.eclipse.emf.ecore.EObject; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTParser; 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.CompilationUnit; import org.eclipse.jdt.core.dom.Expression; import org.eclipse.jdt.core.dom.FieldDeclaration; import org.eclipse.jdt.core.dom.IExtendedModifier; import org.eclipse.jdt.core.dom.Javadoc; import org.eclipse.jdt.core.dom.MarkerAnnotation; import org.eclipse.jdt.core.dom.MemberValuePair; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.Modifier; import org.eclipse.jdt.core.dom.Modifier.ModifierKeyword; import org.eclipse.jdt.core.dom.NormalAnnotation; import org.eclipse.jdt.core.dom.PrimitiveType; import org.eclipse.jdt.core.dom.ReturnStatement; import org.eclipse.jdt.core.dom.SingleMemberAnnotation; import org.eclipse.jdt.core.dom.SingleVariableDeclaration; import org.eclipse.jdt.core.dom.Statement; import org.eclipse.jdt.core.dom.Type; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jdt.core.dom.VariableDeclarationFragment; import org.eclipse.jdt.core.dom.VariableDeclarationStatement; import org.eclipse.jface.text.Document; import org.eclipse.text.edits.TextEdit; import org.eclipse.uml2.uml.Comment; import org.eclipse.uml2.uml.Element; import org.eclipse.uml2.uml.NamedElement; import org.eclipse.uml2.uml.Operation; import org.eclipse.uml2.uml.Parameter; import org.eclipse.uml2.uml.ParameterDirectionKind; import org.eclipse.uml2.uml.TypedElement; import org.eclipse.uml2.uml.VisibilityKind; import com.crispico.flower.mp.FlowerMPUtils; import com.crispico.flower.mp.GeneralCreateNewElement; import com.crispico.flower.mp.metamodel.classmetamodel.ClassMetamodel; import com.crispico.flower.mp.metamodel.classmetamodel.ClassMetamodelUtils; import com.crispico.flower.mp.metamodel.codesync.CodeSyncMetamodel; import com.crispico.flower.mp.metamodel.codesync.CodeSyncUtils; import com.crispico.flower.mp.metamodel.codesync.algorithm.CodeSyncException; import com.crispico.flower.mp.metamodel.codesync.algorithm.SyncUtils; import com.crispico.flower.mp.metamodel.codesync.algorithm.reverse.base.ReversePackage_Type; import com.crispico.flower.mp.metamodel.codesync.jet.JetTemplateFactory; import com.crispico.flower.mp.metamodel.codesync.model.SrcDirPackage; import com.crispico.flower.mp.metamodel.codesync.model.SyncElement; import com.crispico.flower.mp.metamodel.codesyncjava.algorithm.reverse.ReverseJavaDeclaration; import com.crispico.flower.mp.metamodel.codesyncjava.algorithm.reverse.ReverseJavaType; import com.crispico.flower.mp.metamodel.codesyncjava.model.CodeSyncJavaFactory; import com.crispico.flower.mp.metamodel.codesyncjava.model.CodeSyncJavaPackage; import com.crispico.flower.mp.metamodel.codesyncjava.model.JavaAnnotation; import com.crispico.flower.mp.metamodel.codesyncjava.model.JavaOperation; import com.crispico.flower.mp.validation.FlowerDiagnostic; /** * @author sorin * @flowerModelElementId _zVs8IJiOEd6aNMdNFvR5WQ */ public class JavaSyncUtils { /** * @author Luiza * @flowerModelElementId _zVs8JJiOEd6aNMdNFvR5WQ */ public static final String MODIFIER_ABSTRACT = "abstract"; /** * @flowerModelElementId _zVs8KZiOEd6aNMdNFvR5WQ */ public static final String MODIFIER_STATIC = "static"; public static final String MODIFIER_FINAL = "final"; /** * Pattern used to identify line end characters in a java comment * @flowerModelElementId _dOK_YL8REd6XgrpwHbbsYQ */ protected static final Pattern COMMENT_LINE_PATTERN = Pattern.compile("(\r{0,1}\n[\t]*( [*] {0,1}))"); /** * Pattern used to identify java doc markers * @flowerModelElementId _dOK_ZL8REd6XgrpwHbbsYQ */ protected static final Pattern COMMENT_MARKERS_PATTERN = Pattern.compile("^(/[*][*] {0,1})|( {0,1}[*]{0,1}/)$"); /** * Loads the java file and sets the retrieved comments for every method/attribute found inside it * @throws JavaModelException * @flowerModelElementId _zVs8LZiOEd6aNMdNFvR5WQ */ @SuppressWarnings("unchecked") public static CompilationUnit loadJavaFile(IFile file, ASTParser parser) throws Exception { CompilationUnit result = null; char[] fileContent; try { fileContent = getFileContent(file).toCharArray(); } catch (ResourceException e) { file.refreshLocal(IResource.DEPTH_INFINITE, null); fileContent = getFileContent(file).toCharArray(); } HashMap defaultOptions = new HashMap(JavaCore.getOptions()); String compilerSourceLevel = (String) defaultOptions.get(JavaCore.COMPILER_SOURCE); // Source level 1.5 is necessary to enable annotations if (Double.valueOf(compilerSourceLevel) < 1.5) { defaultOptions.put(JavaCore.COMPILER_SOURCE, "1.5"); parser.setCompilerOptions(defaultOptions); } parser.setSource(fileContent); result = (CompilationUnit) parser.createAST(null); for (Object o : result.getCommentList()) { if (o instanceof Javadoc) { Javadoc doc = (Javadoc) o; String docComment = new String(fileContent, doc.getStartPosition(), doc.getLength()); //use Pattern and Matcher instead of string.replaceAll(...) to compile the pattern only once Matcher matcher = COMMENT_LINE_PATTERN.matcher(docComment); docComment = matcher.replaceAll("\r\n"); matcher = COMMENT_MARKERS_PATTERN.matcher(docComment); docComment = matcher.replaceAll(""); doc.setProperty("comment", docComment); } } return result; } /** * * @param file the <code>{@link IFile}</code> * @return the content of this file * * @author Luiza. * The reading technique was modified because we were loosing the characters marking end of row * @flowerModelElementId _zVs8OJiOEd6aNMdNFvR5WQ */ private static String getFileContent(IFile file) throws Exception { BufferedReader br = null; try { br = new BufferedReader(new InputStreamReader(file.getContents())); StringBuffer buffer = new StringBuffer(); char[] charBuffer = new char[100]; int charsRed = 0; while (true) { charsRed = br.read(charBuffer); if (charsRed == -1) { break; } if (charsRed < charBuffer.length) { for (int i = 0; i < charsRed; i++) { buffer.append(charBuffer[i]); } break; } buffer.append(charBuffer); } return buffer.toString(); } finally { if (br != null) br.close(); } } /** * @flowerModelElementId _zVs8PpiOEd6aNMdNFvR5WQ */ public static void writeJavafile(CompilationUnit cUnit, IFile file) { // TODO ::plus numele packetului? String sourceCode = null; try { Document doc = null; if (file.exists()) doc = new Document(getFileContent(file)); else doc = new Document(); TextEdit edits = cUnit.rewrite(doc, null); edits.apply(doc); sourceCode = doc.get(); if (sourceCode.getBytes().length == 0) throw new IllegalArgumentException("java sourcecode.getBytes length = 0 "); ByteArrayInputStream bais = new ByteArrayInputStream(sourceCode.getBytes()); if (!file.exists()) file.create(bais, true, null); else // keep the 3rd flag true! keeps history file.setContents(bais, true, true, null); bais.close(); } catch (Exception e) { throw new RuntimeException("Could not write Java file: " + file.getFullPath(), e); } } /** * For a <code>CompilationUnit</code> retrieves the name of the class with modifier <code>public</code> * @flowerModelElementId _zVs8RJiOEd6aNMdNFvR5WQ */ public static String getNameOfMasterClass(CompilationUnit node) { TypeDeclaration td = getMasterClass(node); if (td != null) return td.getName().toString(); else return null; } /** * If no public type is found in a compilation unit it is returned the first one * * @param node {@link CompilationUnit} * @return <code>TypeDeclaration</code> object which defines the main class in a compilation unit * (this is the public class or interface which gives the name of the file). * * @flowerModelElementId _zVs8SpiOEd6aNMdNFvR5WQ */ @SuppressWarnings("unchecked") public static TypeDeclaration getMasterClass(CompilationUnit node) { Iterator<TypeDeclaration> it = node.types().iterator(); for (; it.hasNext();) { TypeDeclaration td = it.next(); for (Iterator<Modifier> it2 = td.modifiers().iterator(); it2.hasNext();) { IExtendedModifier iExtendedModifier = it2.next(); if (iExtendedModifier.isAnnotation()) continue; if (((Modifier) iExtendedModifier).isPublic()) return td; } } if (node.types().size() > 0) return (TypeDeclaration) node.types().get(0); throw new IllegalStateException( "A master class for " + node.getPackage() + ".[" + node.types() + "] could not been found"); } /** * Returns the list of {@link IExtendedModifier modifiers} of the given * <code>astElement</code>. This shall contain annotations, visibility * markers (public, protected, private), static, final. * * @param astElement * the types accepted for this parameter are : * {@link CompilationUnit}, {@link TypeDeclaration}, or * {@link BodyDeclaration} * * @author Luiza */ @SuppressWarnings("unchecked") private static List<IExtendedModifier> getModifiers(Object astElement) { if (astElement instanceof CompilationUnit) { return getMasterClass((CompilationUnit) astElement).modifiers(); } else if (astElement instanceof TypeDeclaration) { return ((TypeDeclaration) astElement).modifiers(); } else if (astElement instanceof BodyDeclaration) { return ((BodyDeclaration) astElement).modifiers(); } else throw new IllegalArgumentException("Can't get modifiers for " + astElement); } /** * @flowerModelElementId _zVs8UZiOEd6aNMdNFvR5WQ */ public static VisibilityKind convertJavaToModelVisibility(Object node) { List<IExtendedModifier> modifiers = getModifiers(node); for (IExtendedModifier iExtendedModifier : modifiers) { if (iExtendedModifier.isAnnotation()) continue; String modif = ((Modifier) iExtendedModifier).getKeyword().toString(); if ("public".equals(modif)) { return VisibilityKind.PUBLIC_LITERAL; } else if ("private".equals(modif)) { return VisibilityKind.PRIVATE_LITERAL; } else if ("protected".equals(modif)) { return VisibilityKind.PROTECTED_LITERAL; } } return VisibilityKind.PACKAGE_LITERAL; } /** * Gets the feature value from Java Element for Model Element. * * @param node astElement for class or method declaration * @param modifier flag indicating the modifier for which to get the feature value * This should be one of the predefined flags: * <ul> * <li>{@link #MODIFIER_ABSTRACT}</li> * <li>{@link #MODIFIER_STATIC}</li> * </ul> * @return value for Model feature * * @author Luiza * @flowerModelElementId _zVs8VpiOEd6aNMdNFvR5WQ */ public static boolean getFeatureValueFromJavaModifier(Object node, String modifier) { List<IExtendedModifier> modifiers = getModifiers(node); for (IExtendedModifier iExtendedModifier : modifiers) { if (iExtendedModifier.isAnnotation()) continue; String modif = ((Modifier) iExtendedModifier).getKeyword().toString(); if (modifier.equals(modif)) return true; } return false; } /** * Parse the given {@link String value} into a java expression. * * @author Luiza */ private static Expression makeExpression(AST ast, String value) { ASTParser expressionParser = ASTParser.newParser(AST.JLS3); expressionParser.setKind(ASTParser.K_EXPRESSION); expressionParser.setSource(value.toCharArray()); Expression expression = (Expression) expressionParser.createAST(null); return (Expression) ASTNode.copySubtree(ast, expression); } /** * Retrieves the annotations from the <code>astElement</code>'s modifiers * list. * * @param astElement * @return a list of {@link JavaAnnotationHolder} wrapping the * {@link Annotation annotations} * * @author Luiza */ public static List<JavaAnnotationHolder> getAnnotations(Object astElement) { List<JavaAnnotationHolder> annotations = new ArrayList<JavaAnnotationHolder>(); List<IExtendedModifier> modifiers = getModifiers(astElement); for (Iterator<IExtendedModifier> it = modifiers.iterator(); it.hasNext();) { IExtendedModifier iExtendedModifier = it.next(); if (iExtendedModifier.isAnnotation()) { annotations.add(new JavaAnnotationHolder((Annotation) iExtendedModifier)); } } return annotations; } /** * Adds <code>newAnnotation</code> to the list of modifiers of * <code>toAstElement</code>. * The new Annotation will be placed on the first position in the modifiers list. * * @param toAstElement * the {@link ASTNode parent ast element} to receive the * {@link Annotation} * @param newAnnotation * the new created {@link Annotation}. Note that it must be * unparented. Otherwise an exception will be generated. * * @author Luiza */ public static void addAnnotation(Object toAstElement, Annotation newAnnotation) { List<IExtendedModifier> modifiers = getModifiers(toAstElement); int lastAnnotationIndex = -1, index = 0; // find the last annotation position for (IExtendedModifier modifier : modifiers) { if (modifier.isAnnotation()) lastAnnotationIndex = index; index++; } index = lastAnnotationIndex + 1; if (index >= modifiers.size()) modifiers.add(newAnnotation); else modifiers.add(index, newAnnotation); } /** * Creates a new appropriate {@link Annotation java Annotation} from the * given {@link EAnnotation modelEAnnotation}. The returned * {@link Annotation} can be a {@link MarkerAnnotation}, a * {@link SingleMemberAnnotation} or a {@link NormalAnnotation} according to * the <code>modelEAnnotation</code> features. * * @param ast * the parent {@link AST} used to create the new * {@link Annotation} * @param modelEAnnotation * the model element {@link EAnnotation} * @return a new unparented {@link Annotation} * * @author Luiza */ @SuppressWarnings("unchecked") public static Annotation createNewJavaAnnotationFromModelEAnnotation(AST ast, EAnnotation modelEAnnotation) { EMap<String, String> detailsMap = modelEAnnotation.getDetails(); Annotation newAnnotation = null; if (detailsMap.size() == 0) { newAnnotation = ast.newMarkerAnnotation(); } else if (detailsMap.size() == 1 && detailsMap.get("") != null) { String value = detailsMap.get(""); newAnnotation = ast.newSingleMemberAnnotation(); ((SingleMemberAnnotation) newAnnotation).setValue(makeExpression(ast, value)); } else { newAnnotation = ast.newNormalAnnotation(); for (Entry<String, String> mapEntry : detailsMap) { MemberValuePair mvp = ast.newMemberValuePair(); mvp.setName(ast.newSimpleName(mapEntry.getKey())); mvp.setValue(makeExpression(ast, mapEntry.getValue())); ((NormalAnnotation) newAnnotation).values().add(mvp); } } newAnnotation.setTypeName(ast.newName(modelEAnnotation.getSource())); return newAnnotation; } /** * Removes the given {@link Annotation annotation} from * <code>astElement</code> * * @author Luiza */ public static void removeAnnotation(Object astElement, Annotation annotation) { List<IExtendedModifier> modifiers = getModifiers(astElement); modifiers.remove(annotation); } /** * Moves <code>toMove</code> after <code>afterAnnotation</code> in the * modifiers list of the given <code>astElement</code>. * <p> * Supposes that both {@link Annotation annotations} are contained in the * <code>astElement</code>'s list of modifiers. * * @author Luiza */ public static void moveAfterAnnotation(Object astElement, Annotation toMove, Annotation afterAnnotation) { List<IExtendedModifier> modifiers = getModifiers(astElement); modifiers.remove(toMove); int toIndex = 0; if (afterAnnotation == null) { // if toMove should be the first for (IExtendedModifier modifier : modifiers) if (modifier.isAnnotation()) break; // find the first element of its kind else toIndex++; } else { toIndex = modifiers.indexOf(afterAnnotation) + 1; } modifiers.add(toIndex, (IExtendedModifier) toMove); } /** * Replace <code>oldAnnotation</code> with <code>newAnnotation</code> in * the <code>parentAstElement</code>'s modifiers list. * <p> * Supposes the modifiers list contains <code>oldAnnotation</code>. * * @param parentAstElement * the {@link CompilationUnit}, {@link TypeDeclaration}, * {@link BodyDeclaration} containing the annotations * @param oldAnnotation * the {@link Annotation} to replace. * @param newAnnotation * the new {@link Annotation} to add on the * <code>oldAnnotation</code>'s position. * * @author Luiza */ public static void replaceAnnotation(Object parentAstElement, Annotation oldAnnotation, Annotation newAnnotation) { List<IExtendedModifier> modifiers = getModifiers(parentAstElement); int index = modifiers.indexOf(oldAnnotation); // index still in range // oldAnnotation was not the last modifier if (index >= 0 && index < modifiers.size()) { modifiers.add(index, newAnnotation); } else modifiers.add(newAnnotation); modifiers.remove(index + 1); } /** * @flowerModelElementId _zVs8XZiOEd6aNMdNFvR5WQ */ @SuppressWarnings("unchecked") public static void updateVisibilityFromModelToJavaClass(BodyDeclaration bd, VisibilityKind visibilityKind) {// NamedElement // modelElement) { ModifierKeyword javaVisibFlag = getJavaVisibilityFlagFromJavaClass(bd); ModifierKeyword modelVisibFlag = convertModelToJavaVisibilityFlag(visibilityKind); if (modelVisibFlag != javaVisibFlag) { if (javaVisibFlag != null) { // /meaning the package visibility // Which to remove? Modifier toRemove = null; for (Iterator<Modifier> it = bd.modifiers().iterator(); it.hasNext();) { Modifier modif = it.next(); if (modif.isPrivate() || modif.isProtected() || modif.isPublic()) { toRemove = modif; break; } } bd.modifiers().remove(toRemove); } // Adding if (modelVisibFlag != null) { Modifier newModif = bd.getAST().newModifier(modelVisibFlag); bd.modifiers().add(newModif); } } } /** * @flowerModelElementId _zVs8ZJiOEd6aNMdNFvR5WQ */ public static ModifierKeyword getJavaVisibilityFlagFromJavaClass(BodyDeclaration bd) { if (Modifier.isPrivate(bd.getModifiers())) return ModifierKeyword.PRIVATE_KEYWORD; else if (Modifier.isProtected(bd.getModifiers())) return ModifierKeyword.PROTECTED_KEYWORD; else if (Modifier.isPublic(bd.getModifiers())) return ModifierKeyword.PUBLIC_KEYWORD; return null; } /** * @flowerModelElementId _zVs8a5iOEd6aNMdNFvR5WQ */ public static ModifierKeyword convertModelToJavaVisibilityFlag(VisibilityKind src) { if (VisibilityKind.PRIVATE_LITERAL.equals(src)) return ModifierKeyword.PRIVATE_KEYWORD; else if (VisibilityKind.PROTECTED_LITERAL.equals(src)) return ModifierKeyword.PROTECTED_KEYWORD; else if (VisibilityKind.PUBLIC_LITERAL.equals(src)) return ModifierKeyword.PUBLIC_KEYWORD; else return null; } /** * Updates java code according to the model value for abstract or static modifiers * * @author Luiza * @param bd <code>{@link BodyDeclaration}</code> for a class, method or field. * @param isTrue value for the modifier * @param modifier flag indicating the modifier to update. * This should be one of the predefined flags: * <ul> * <li>{@link #MODIFIER_ABSTRACT}</li> * <li>{@link #MODIFIER_STATIC}</li> * </ul> * @flowerModelElementId _zVs8cJiOEd6aNMdNFvR5WQ */ @SuppressWarnings("unchecked") public static void updateModifierFromModelToJavaClass(BodyDeclaration bd, boolean isTrue, String modifier) { ModifierKeyword javaModifierFlag = null; ModifierKeyword modelModifierFlag = null; boolean isForAbstractModif = modifier.equals(MODIFIER_ABSTRACT); boolean isForStaticModif = modifier.equals(MODIFIER_STATIC); boolean isForFinalModif = modifier.equals(MODIFIER_FINAL); if (isForAbstractModif) { javaModifierFlag = Modifier.isAbstract(bd.getModifiers()) ? ModifierKeyword.ABSTRACT_KEYWORD : null; modelModifierFlag = isTrue ? ModifierKeyword.ABSTRACT_KEYWORD : null; } else if (isForStaticModif) { javaModifierFlag = Modifier.isStatic(bd.getModifiers()) ? ModifierKeyword.STATIC_KEYWORD : null; modelModifierFlag = isTrue ? ModifierKeyword.STATIC_KEYWORD : null; } else if (isForFinalModif) { javaModifierFlag = Modifier.isFinal(bd.getModifiers()) ? ModifierKeyword.FINAL_KEYWORD : null; modelModifierFlag = isTrue ? ModifierKeyword.FINAL_KEYWORD : null; } else throw new IllegalArgumentException( "updateModifierFromModelToJavaClass - can't update java code for modifier " + modifier); //if there are differences between the model and the java file if (modelModifierFlag != javaModifierFlag) { //if this modifier has been set before find it and remove it if (javaModifierFlag != null) { //Modifier toRemove = null; /* * we use bd.modifiers() instead of bd.getModifiers(), which computes a bit-wise integer consisting in all the modifier values * because it also iterates over the modifiers and method bd.setModifiers(modifiers) is deprecated */ for (Iterator<Modifier> it = bd.modifiers().iterator(); it.hasNext();) { Modifier modif = it.next(); if (isForAbstractModif && modif.isAbstract()) { it.remove(); break; } else if (isForStaticModif && modif.isStatic()) { it.remove(); break; } else if (isForFinalModif && modif.isFinal()) { it.remove(); break; } } //bd.modifiers().remove(toRemove); } // add the new modifier value if changes have been made in the model if (modelModifierFlag != null) { Modifier newModif = bd.getAST().newModifier(modelModifierFlag); bd.modifiers().add(newModif); } } } /** * @flowerModelElementId _zVs8eJiOEd6aNMdNFvR5WQ */ public static Object getJavaLookupKeyForField(FieldDeclaration fd) { return getSimpleNameString(fd); } /** * @author Mariana - added support for array dimensions (e.g. int[][]) and variable arguments (e.g. int...) * * @throws CodeSyncException * @flowerModelElementId _zVs8fpiOEd6aNMdNFvR5WQ */ @SuppressWarnings("unchecked") public static Object getJavaLookupKeyForMethod(MethodDeclaration md, ReversePackage_Type reversePackage_Type) throws CodeSyncException { String result = getSimpleNameString(md) + "("; List<SingleVariableDeclaration> params = md.parameters(); for (Iterator<SingleVariableDeclaration> it = params.iterator(); it.hasNext();) { SingleVariableDeclaration p = it.next(); result += SyncUtils.getFullyQualifiedNameForAstType(p.getType().toString(), reversePackage_Type); // support for extra dimensions // e.g. int x[][] will return int[][] for (int i = 0; i < p.getExtraDimensions(); i++) { result += "[]"; } // support for variable arguments if (p.isVarargs()) { result += "..."; } result += ","; } result += ")"; return result; } public static Object getModelLookupKeyForOperation(Operation o) throws CodeSyncException { String result = o.getName() + "("; for (Parameter p : o.getOwnedParameters()) if (p.getDirection().equals(ParameterDirectionKind.IN_LITERAL)) { org.eclipse.uml2.uml.Type t = SyncUtils.getActualTypeValue(p, p.getType()); o.getNamespace().allNamespaces(); String paramType = t == null ? null : SyncUtils.getFullyQualifiedTypeNameFromType(t); result += paramType + ","; } result += ")"; return result; } public static Object getLookupKeyForJavaAnnotation(Annotation annotation) { StringBuilder key = new StringBuilder(annotation.getTypeName().toString() + '('); if (annotation.isSingleMemberAnnotation()) { key.append(((SingleMemberAnnotation) annotation).getValue().toString() + ","); } else if (annotation.isNormalAnnotation()) { for (Object o : ((NormalAnnotation) annotation).values()) { MemberValuePair mvp = (MemberValuePair) o; key.append(mvp.getName().toString() + "=" + mvp.getValue().toString() + ","); } } key.append(')'); return key.toString(); } /** * Creates a new appropriate {@link Type} from the given <code>value</code>. * <p> * Note that if <code>value</code> is <code>null</code> returns * {@link PrimitiveType#VOID}. * * @param ast * {@link AST} of the {@link ASTNode ASTElement} needing a new * Type * @param value * the name of the {@link Type} to be created. * @param canBePrimitiveType * if <code>true</code> try to create a primitive from the * given <code>value</code> if possible, otherwise create a new * Type without checking primitives * @author Luiza * * @flowerModelElementId _zVs8hZiOEd6aNMdNFvR5WQ */ public static Type getJavaTypeFromString(AST ast, String value, boolean canBePrimitiveType) { if (canBePrimitiveType) { PrimitiveType.Code primitiveTypeCode = null; if (value == null) primitiveTypeCode = PrimitiveType.VOID; else { primitiveTypeCode = PrimitiveType.toCode(value); } if (primitiveTypeCode != null) return ast.newPrimitiveType(primitiveTypeCode); } // not a primitive ASTParser statementParser = ASTParser.newParser(AST.JLS3); statementParser.setKind(ASTParser.K_STATEMENTS); statementParser.setSource((value + " a;").toCharArray()); // try to parse a variable declaration Block block = (Block) statementParser.createAST(null); VariableDeclarationStatement declaration = (VariableDeclarationStatement) block.statements().get(0); return (Type) ASTNode.copySubtree(ast, declaration.getType()); // detach the type from the parent node } /** * @flowerModelElementId _zVs8jZiOEd6aNMdNFvR5WQ */ public static String getReturnTypeAsString(BodyDeclaration bd) { if (bd instanceof FieldDeclaration) return ((FieldDeclaration) bd).getType().toString(); else if (bd instanceof MethodDeclaration) { //constructors in java have null returning type Type returnType = ((MethodDeclaration) bd).getReturnType2(); return returnType == null ? null : returnType.toString(); } else { throw new IllegalArgumentException("Expecting method or field declaration: " + bd); } } /** * @flowerModelElementId _zVs8kpiOEd6aNMdNFvR5WQ */ public static String getSimpleNameString(BodyDeclaration bd) { if (bd instanceof FieldDeclaration) { FieldDeclaration fd = (FieldDeclaration) bd; VariableDeclarationFragment vdf = (VariableDeclarationFragment) fd.fragments().get(0); return vdf.getName().toString(); } else if (bd instanceof MethodDeclaration) { MethodDeclaration md = (MethodDeclaration) bd; return md.getName().toString(); } else { throw new IllegalArgumentException("Expecting method or field declaration: " + bd); } } /** * This is a common method for {@link ReverseJavaType} and * {@link ReverseJavaDeclaration} used for retrieving the comment feature. * * @param astElement * must be of type {@link CompilationUnit} or * {@link BodyDeclaration} * @return the comment list for this <code>astElement</code> * * @author Luiza */ public static List<Comment> getComment(ASTNode astElement) { List<Comment> listComments = new ArrayList<Comment>(); String documentation = ""; Javadoc javaDoc = null; if (astElement instanceof CompilationUnit) { // class or interface javaDoc = JavaSyncUtils.getMasterClass((CompilationUnit) astElement).getJavadoc(); } else if (astElement instanceof BodyDeclaration) { // attribute or method javaDoc = ((BodyDeclaration) astElement).getJavadoc(); } else { return null; } if (javaDoc != null) { documentation = (String) javaDoc.getProperty("comment"); // the comment as it is in the file } if (documentation != null && documentation.length() > 0) { listComments.add(SyncUtils.createComment(documentation)); } return listComments; } /** * The method verifies if a Java type name is identifier. * The following conditions must be satisfied: * <ul> * <li> if the name contains '<' and '>', the number of * open brackets should be equal with the number of * closed ones; otherwise the <code>isIdentifier()</code> * method is called;</li> * <li> if the name contains '<' the first token (substring from * index 0 to the index of '<') should be an identifier</li> * </ul> * @param object = object whose name has changed * @param diagnosticChain = <code>DiagnosticChain</code> that will be updated * @param string = the new name * @param errorName = error's title * @param notIdentifierError = error's message * * @author Georgi * @flowerModelElementId _mX0RIARBEd-87NNJOl334A */ public static FlowerDiagnostic isJavaIdentifier(EObject object, DiagnosticChain diagnosticChain, String string) { String errorMessage = null; /* will be set by caller method*/ FlowerDiagnostic diag; if (string == null || string.trim().length() == 0) { String qualifiedName = FlowerMPUtils.getElementQualifiedName((Element) object); diag = ClassMetamodelUtils.createDignostic(qualifiedName, errorMessage, ClassMetamodel.VAL_CODE_NAME_NULL, Diagnostic.ERROR, object); ((BasicDiagnostic) (diagnosticChain)).add(diag); return diag; } else { if (FlowerDiagnostic.collectOKDiagnostic(object)) { String qualifiedName = FlowerMPUtils.getElementQualifiedName((Element) object); diag = ClassMetamodelUtils.createDignostic(qualifiedName, errorMessage, ClassMetamodel.VAL_CODE_NAME_NULL, Diagnostic.OK, object); ((BasicDiagnostic) (diagnosticChain)).add(diag); // this diagnostic will not be returned because it will not allow the rest of the validation to work, it will remain undecorated with the external message } } if (!string.contains("<") && !string.contains(">")) { diag = CodeSyncUtils.isIdentifier(object, diagnosticChain, string); return diag; // error message will be set by caller method } int imbricationLevel = 0; int pos = string.indexOf('<'); if (pos > 0) { boolean isIdentifier = object instanceof SrcDirPackage ? CodeSyncUtils.isSrcDirIdentifier(string.substring(0, pos)) : CodeSyncUtils.isIdentifier(string.substring(0, pos)); if (!isIdentifier) { String qualifiedName = FlowerMPUtils.getElementQualifiedName((Element) object); diag = ClassMetamodelUtils.createDignostic(qualifiedName, errorMessage, CodeSyncMetamodel.VAL_CODE_NAME_NOT_IDENTIFIER, Diagnostic.ERROR, object); ((BasicDiagnostic) (diagnosticChain)).add(diag); return diag; } } for (int i = 0; i < string.length(); i++) if (string.charAt(i) == '<') imbricationLevel++; else if (string.charAt(i) == '>') imbricationLevel--; else if (string.charAt(i) == ',') { if (string.charAt(i - 1) == '>' || string.charAt(i - 1) == '<' || string.charAt(i + 1) == '>' || string.charAt(i + 1) == '<') { String qualifiedName = FlowerMPUtils.getElementQualifiedName((Element) object); diag = ClassMetamodelUtils.createDignostic(qualifiedName, errorMessage, CodeSyncMetamodel.VAL_CODE_NAME_NOT_IDENTIFIER, Diagnostic.ERROR, object); ((BasicDiagnostic) (diagnosticChain)).add(diag); return diag; } } if (imbricationLevel != 0) { String qualifiedName = FlowerMPUtils.getElementQualifiedName((Element) object); diag = ClassMetamodelUtils.createDignostic(qualifiedName, errorMessage, CodeSyncMetamodel.VAL_CODE_NAME_NOT_IDENTIFIER, Diagnostic.ERROR, object); ((BasicDiagnostic) (diagnosticChain)).add(diag); return diag; } if (FlowerDiagnostic.collectOKDiagnostic(object)) { String qualifiedName = FlowerMPUtils.getElementQualifiedName((Element) object); diag = ClassMetamodelUtils.createDignostic(qualifiedName, errorMessage, CodeSyncMetamodel.VAL_CODE_NAME_NOT_IDENTIFIER, Diagnostic.OK, object); ((BasicDiagnostic) (diagnosticChain)).add(diag); return diag; } return null; } private static final HashSet<String> keywords = new HashSet<String>(Arrays.asList("abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue", "default", "do", "double", "else", "enum", "extends", "false", "final", "finally", "float", "for", "goto", "if", "implements", "import", "instanceof", "int", "interface", "long", "native", "new", "null", "package", "private", "protected", "public", "return", "short", "static", "strictfp", "super", "switch", "synchronized", "this", "throw", "throws", "transient", "true", "try", "void", "volatile", "while")); /** * The method checks if a Java type name is a Java keyword. * * @param object the object whose name was changed * @param diagnosticChain <code>DiagnosticChain</code> to be updated * @param string the new name * @param errorName the error's title * @param keywordError the error's message * * @author Mariana * @author Sorin */ public static FlowerDiagnostic isJavaKeyword(EObject object, DiagnosticChain diagnosticChain, String string) { String errorMessage = null; /* will be set by caller method*/ FlowerDiagnostic diag; // The old implementation with Scanner demanded to use lot of memory for initialization. if (keywords.contains(string)) { String qualifiedName = (object instanceof NamedElement) ? FlowerMPUtils.getElementQualifiedName((Element) object) : string; diag = ClassMetamodelUtils.createDignostic(qualifiedName, errorMessage, ClassMetamodel.VAL_CODE_KEYWORD, Diagnostic.ERROR, object); ((BasicDiagnostic) (diagnosticChain)).add(diag); return diag; } else { if (FlowerDiagnostic.collectOKDiagnostic(object)) { String qualifiedName = (object instanceof NamedElement) ? FlowerMPUtils.getElementQualifiedName((Element) object) : string; diag = ClassMetamodelUtils.createDignostic(qualifiedName, errorMessage, ClassMetamodel.VAL_CODE_KEYWORD, Diagnostic.OK, object); ((BasicDiagnostic) (diagnosticChain)).add(diag); return diag; } } return null; } /** * The method is called to check if the given element has the * type not set. The verification is made for <code>JavaProperty</code>, * <code>JavaParameter</code> and <code>JavaOperation</code>. * * If the <code>object</code> is a Java operation and it has the same name as the class, * it is a constructor and the check is not done. * * @param object * @param diagnosticChain * @param errorMessage * @author Georgi * @flowerModelElementId _mX0RKARBEd-87NNJOl334A */ public static FlowerDiagnostic isTypeNull(EObject object, DiagnosticChain diagnosticChain) { String errorMessage = null; /* will be set by caller method*/ org.eclipse.uml2.uml.Type type = null; if (object instanceof TypedElement) type = ((TypedElement) (object)).getType(); else if (object instanceof Operation) type = ((Operation) object).getType(); else return null; FlowerDiagnostic diag = null; if (CodeSyncUtils.validationAllowedOnElement((SyncElement) object) && type == null && !(object instanceof Operation && ((Operation) object).getName() != null && ((Operation) object) .getName().equals(((org.eclipse.uml2.uml.Type) object.eContainer()).getName()))) { String qualifiedName = FlowerMPUtils.getElementQualifiedName((Element) object); diag = ClassMetamodelUtils.createDignostic(qualifiedName, errorMessage, CodeSyncMetamodel.VAL_CODE_TYPE_NULL, Diagnostic.ERROR, object); } else { if (FlowerDiagnostic.collectOKDiagnostic(object)) { // remove error if the element is deleted, locked, or under locked parent String qualifiedName = FlowerMPUtils.getElementQualifiedName((Element) object); diag = ClassMetamodelUtils.createDignostic(qualifiedName, errorMessage, CodeSyncMetamodel.VAL_CODE_TYPE_NULL, Diagnostic.OK, object); } } if (diag != null) ((BasicDiagnostic) (diagnosticChain)).add(diag); return diag; } /** * Creates a new block and adds it as body to the given {@link MethodDeclaration}. * * @param md - the {@link MethodDeclaration} that requires a new body block. * @param blockString - the {@link String} that will form the block content. * * @author Luiza */ public static void generateBodyForMethod(MethodDeclaration md, String blockString) { AST ast = md.getAST(); ASTParser statementParser = ASTParser.newParser(AST.JLS3); statementParser.setKind(ASTParser.K_STATEMENTS); // parse the block statementParser.setSource(blockString.toCharArray()); Block block = (Block) statementParser.createAST(null); block = (Block) ASTNode.copySubtree(ast, block); md.setBody(block); } /** * Generates a body for method and inserts a comment at first line in the body content. * <p> * Comments aren't created as child nodes if we parse a block string that contains comment lines. <br> * Hack made : <br> * <ul> * <li>a new {@link Statement} is created and added to block node. Because we can't create comments as {@link ASTNode} * the statement is created as a {@link ReturnStatement}. * <li>the statement will contain the comment code * <li>because the return statement node created must return our code (and not return;), * it must be stored in NodeInfoStore class found as private field in InternalASTRewrite found as private field in the root AST class. * * <li>when {@link JavaSyncUtils#writeJavafile(CompilationUnit, IFile)} is called at the end of forward mechanism * it invokes {@link CompilationUnit#rewrite(org.eclipse.jface.text.IDocument, java.util.Map)} that will replace the * return statement code with our code based on what it finds in NodeInfoStore list. * </ul> * @param md - the {@link MethodDeclaration} that requires a new body block. * @param blockString - the {@link String} that will form the block content. * @param commentString - the {@link String} that will form the line comment. * * @author Cristina */ @SuppressWarnings("unchecked") public static void generateBodyWithCommentForMethod(MethodDeclaration md, String blockString, String commentString) { // generate body content without comments generateBodyForMethod(md, blockString); // insert comment on first line try { // get rewriter field from AST Object rewriter = FlowerMPUtils.getPrivateField(AST.class, "rewriter", md.getRoot().getAST()); Class<?> thisClass = Class.forName("org.eclipse.jdt.core.dom.InternalASTRewrite"); // get nodeStore field from InternalASTRewrite Object nodestore = FlowerMPUtils.getPrivateField(thisClass, "nodeStore", rewriter); Class<?> nodeInfoStore = Class.forName("org.eclipse.jdt.internal.core.dom.rewrite.NodeInfoStore"); // nodeStore.newPlaceholderNode(ASTNode.RETURN_STATEMENT) Method newPlaceholderNode = nodeInfoStore.getDeclaredMethod("newPlaceholderNode", int.class); ASTNode placeholder = (ASTNode) newPlaceholderNode.invoke(nodestore, ASTNode.RETURN_STATEMENT); // nodestore.markAsStringPlaceholder(placeholder, commentString) Method markAsStringPlaceholder = nodeInfoStore.getDeclaredMethod("markAsStringPlaceholder", ASTNode.class, String.class); markAsStringPlaceholder.invoke(nodestore, placeholder, commentString); // add comment to body content md.getBody().statements().add(0, placeholder); } catch (Exception e) { // swallowed } } /** * Creates a template annotation based on jet templates and returns it. * <br> * This annotation will be add to a method content in the following cases : * <ul> * <li> when creating new java method * <li> when creating an overridable method * <li> when creating a getter/setter method * </ul> * It has the following structure : <br> * TemplateMethod(id=_id_, comment=_comment_, version=_version_) * <br> * At forward mechanism, based on this annotations details, the body content for given method will be created. * @param bodyTemplateClass - jet template for operation's body * @param operation - the operation where the annotation will be added * @param commentTemplateClass - jet template for line comment (optional) * @return - the created annotation * * @author Cristina */ public static JavaAnnotation createTemplateMethodAnnotation(java.lang.Class<?> bodyTemplateClass, JavaOperation operation, java.lang.Class<?> commentTemplateClass) { JavaAnnotation annotation = (JavaAnnotation) GeneralCreateNewElement.createElementWithName( CodeSyncJavaFactory.eINSTANCE, CodeSyncJavaPackage.eINSTANCE.getJavaAnnotation(), operation); annotation.setSource("TemplateMethod"); annotation.getDetails().put("id", JetTemplateFactory.INSTANCE.getJetTemplateId(bodyTemplateClass)); if (commentTemplateClass != null) { annotation.getDetails().put("comment", JetTemplateFactory.INSTANCE.getJetTemplateId(commentTemplateClass)); } annotation.getDetails().put("version", "1.0"); return annotation; } }