/** * Copyright (c) 2006 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM - Initial API and implementation */ package org.eclipse.emf.codegen.merge.java.facade.ast; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ClassInstanceCreation; import org.eclipse.jdt.core.dom.EnumConstantDeclaration; import org.eclipse.jdt.core.dom.EnumDeclaration; import org.eclipse.jdt.core.dom.Expression; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jdt.core.dom.VariableDeclarationFragment; import org.eclipse.emf.codegen.merge.java.facade.FacadeFlags; import org.eclipse.emf.codegen.merge.java.facade.JAbstractType; import org.eclipse.emf.codegen.merge.java.facade.JNode; import org.eclipse.emf.codegen.merge.java.facade.NodeConverter; public class ASTNodeConverter implements NodeConverter { protected ASTFacadeHelper facadeHelper; /** * @param facadeHelper must be not <code>null</code> */ public ASTNodeConverter(ASTFacadeHelper facadeHelper) { assert facadeHelper != null; this.facadeHelper = facadeHelper; } public ASTFacadeHelper getFacadeHelper() { return facadeHelper; } public JAbstractType convert(JAbstractType abstractType, Class<? extends JAbstractType> cls) { try { Converter converter = null; // if the given class is subclass of enum and node is of subclass of type if (ASTJEnum.class.isAssignableFrom(cls) && abstractType instanceof ASTJType) { converter = new TypeToEnumConverter((ASTJType)abstractType); } else if (ASTJType.class.isAssignableFrom(cls) && abstractType instanceof ASTJEnum) { converter = new EnumToTypeConverter((ASTJEnum)abstractType); } return converter != null ? (JAbstractType)converter.convert() : null; } catch (Exception e) { if (ASTFacadeHelper.DEBUG) { getFacadeHelper().logError("Error converting " + abstractType.getClass().getSimpleName() + " to " + cls.getSimpleName(), e); } } return null; } /** * Base class for all converters. * * @see #convert() */ protected abstract class Converter { /** * @return converted node */ public abstract JNode convert(); /** * Removes children from the source node and adds them to the target node. * * @param source * @param target */ protected void moveChildren(JNode source, JNode target) { for (JNode child : source.getChildren()) { getFacadeHelper().remove(child); getFacadeHelper().addChild(target, child); } } /** * Replaces existing node by new node. * * @param existingNode * @param newNode */ protected void replaceNode(JNode existingNode, JNode newNode) { getFacadeHelper().insertSibling(existingNode, newNode, false); getFacadeHelper().remove(existingNode); } } /** * Converter of type to enum. * * @see #convert() */ protected class TypeToEnumConverter extends Converter { /** * Type that will be converted */ protected ASTJType type; /** * AST of the source and converted node */ protected AST ast; /** * List of children of the type */ protected List<JNode> typeChildren; /** * Map of field names to fields */ protected Map<String, ASTJField> fieldNamesMap = new HashMap<String, ASTJField>(); /** * @param type to convert to enum */ public TypeToEnumConverter(ASTJType type) { this.type = type; ast = type.getASTNode().getAST(); typeChildren = type.getChildren(); // create map of field names to fields for (JNode child : typeChildren) { if (child instanceof ASTJField) { fieldNamesMap.put(child.getName(), (ASTJField)child); } } } /** * Converts type to the enum. * <p> * Name, flags, comment, super interfaces of the enum are set from the type. * <p> * All children of the type is added to the enum. * <p> * Fields that are <code>public static final </code>and have the same type are converted to * enum constants. Arguments of the enum constants are set from arguments of the initializer. * Arguments that match the name of any other field are replaced by initializer value of that field. * <p> * Conversion must be done only once for the same type. * * @return converted type, or <code>null</code> if conversion not possible */ @Override public ASTJEnum convert() { EnumDeclaration enumDeclaration = ast.newEnumDeclaration(); ASTJEnum astjEnum = (ASTJEnum)getFacadeHelper().convertToNode(enumDeclaration); astjEnum.setRewriter(type.getRewriter()); astjEnum.setName(type.getName()); astjEnum.setFlags(type.getFlags()); astjEnum.setComment(type.getComment()); // move all children to the enum, converting some fields to constants for (JNode child : typeChildren) { ASTJNode<?> nodeToRemove = (ASTJNode<?>)child; ASTJNode<?> nodeToInsert = nodeToRemove; if (nodeToRemove instanceof ASTJField) { ASTJField field = (ASTJField)nodeToRemove; nodeToInsert = convertFieldToEnumConst(field); } type.remove(nodeToRemove); astjEnum.addChild(nodeToInsert); } // remove type, insert enum replaceNode(type, astjEnum); return astjEnum; } /** * Converts given field to enum constant if possible. * <p> * Field must be declared as <code>public static final</code> field, and field must be of the same type * as its parent (e.g. type). * <p> * Enum constant arguments and body is set from the original initializer of the field. * * @param field to convert * @return original field if conversion not possible, or converted field otherwise * * @see #setEnumConstantArgumentsAndBody(ASTJEnumConstant, ASTJField) */ protected ASTJNode<?> convertFieldToEnumConst(ASTJField field) { // convert only public static final fields int flags = field.getFlags(); if ((flags & (FacadeFlags.PUBLIC | FacadeFlags.STATIC | FacadeFlags.FINAL)) == 0) { return field; } // convert only fields of the same type as parent ASTJNode<?> parent = field.getParent(); String parentType = (parent == null ? null : parent.getName()); if (parentType == null || !parentType.equals(field.getType())) { return field; } EnumConstantDeclaration enumConstantDeclaration = ast.newEnumConstantDeclaration(); ASTJEnumConstant enumConstant = (ASTJEnumConstant)getFacadeHelper().convertToNode(enumConstantDeclaration); enumConstant.setRewriter(field.getRewriter()); enumConstant.setName(field.getName()); enumConstant.setComment(field.getComment()); // set arguments and body setEnumConstantArgumentsAndBody(enumConstant, field); // move annotations moveChildren(field, enumConstant); return enumConstant; } /** * Sets arguments and body of the enum constant from the initializer of the given field. * <p> * Note that the original field's initializer node structure is used (e.g. if {@link ASTJField#setInitializer(String)} * has been called, this method will use the original initializer of the field). * * @param enumConstant * @param field */ protected void setEnumConstantArgumentsAndBody(ASTJEnumConstant enumConstant, ASTJField field) { Expression initializer = field.getVariableDeclarationFragment().getInitializer(); if (initializer == null || initializer.getNodeType() != ASTNode.CLASS_INSTANCE_CREATION) { // unable to set arguments or body return; } ClassInstanceCreation classInstanceCreation = (ClassInstanceCreation)initializer; // replace arguments that match the name of the fields with the initializer value of the field // @SuppressWarnings("unchecked") String[] arguments = field.convertASTNodeListToStringArray(classInstanceCreation.arguments()); for (int i = 0; i < arguments.length; i++) { ASTJField existingField = fieldNamesMap.get(arguments[i]); if (existingField != null) { String fieldInitializer = existingField.getInitializer(); if (fieldInitializer != null && fieldInitializer.length() > 0) { arguments[i] = fieldInitializer; } } } enumConstant.setArguments(arguments); enumConstant.setBody(getFacadeHelper().toString(classInstanceCreation.getAnonymousClassDeclaration())); } } /** * Converter of enum to class. * * @see #convert() */ protected class EnumToTypeConverter extends Converter { /** * Enum to convert to type */ protected ASTJEnum astjEnum; /** * AST of the source and converted node */ protected AST ast; /** * List of children of the enum */ protected List<JNode> enumChildren; /** * Map of initializer values of the fields to fields */ protected Map<String, ASTJField> fieldInitializersMap = new HashMap<String, ASTJField>(); /** * Map of fields to their index in the children list */ protected Map<ASTJField, Integer> fieldIndexesMap = new HashMap<ASTJField, Integer>(); /** * Last field used in the initializer string of the fields converted from enum constants. */ protected ASTJNode<?> lastFinalFieldUsed = null; /** * @param astjEnum to convert */ public EnumToTypeConverter(ASTJEnum astjEnum) { this.astjEnum = astjEnum; ast = astjEnum.getASTNode().getAST(); enumChildren = astjEnum.getChildren(); // create map of public static final field initializers to fields int i = 0; for (JNode child : enumChildren) { if (child instanceof ASTJField) { ASTJField field = (ASTJField)child; if ((field.getFlags() & (FacadeFlags.PUBLIC | FacadeFlags.STATIC | FacadeFlags.FINAL)) != 0) { fieldInitializersMap.put(field.getInitializer(), field); fieldIndexesMap.put(field, i++); } } } } /** * Converts enum to the class declaration. * <p> * Name, flags, comment, super interfaces of the class declaration are set from the enum. * <p> * All children of the enum are added to the class declaration. Enum constants are converted * to public static final fields with initializer value created from arguments and body of enum constant. * * @return converted type, or <code>null</code> if conversion not possible * * @see #setFieldInitializer(ASTJField, ASTJEnumConstant) */ @Override public ASTJType convert() { TypeDeclaration typeDeclaration = ast.newTypeDeclaration(); ASTJType type = (ASTJType)getFacadeHelper().convertToNode(typeDeclaration); type.setRewriter(astjEnum.getRewriter()); type.setName(astjEnum.getName()); type.setFlags(astjEnum.getFlags()); type.setComment(astjEnum.getComment()); // move all children to the type, converting enum constants to fields // // fields that are created by conversion from enum constants must be inserted after // the last final field that is used in constructors for initializers List<ASTJNode<?>> convertedEnumConstants = new ArrayList<ASTJNode<?>>(enumChildren.size()); for (JNode child : enumChildren) { ASTJNode<?> originalNode = (ASTJNode<?>)child; // convert enum constants and add to list of converted constants if (originalNode instanceof ASTJEnumConstant) { ASTJEnumConstant constant = (ASTJEnumConstant)originalNode; convertedEnumConstants.add(convertEnumConstToField(constant)); } else { // move the node astjEnum.remove(originalNode); type.addChild(originalNode); } } // add all converted fields after the last final field // that is used in initializers of the converted fields for (ASTJNode<?> nodeToInsert : convertedEnumConstants) { if (lastFinalFieldUsed == null) { type.addChild(nodeToInsert); } else { // insert nodeToInsert after targetNode type.insertSibling(lastFinalFieldUsed, nodeToInsert, false); lastFinalFieldUsed = nodeToInsert; } } // replace type by enum replaceNode(astjEnum, type); return type; } /** * Converts enum constant to field. * <p> * Resulting field is <code>public static final</code>. The type of the field is the name of the * enum. Comment of the field is copied from the enum constant. * Initializer value is a call to the constructor of the enum (or converted type) with arguments * taken from enum constant. * * @see #setFieldInitializer(ASTJField, ASTJEnumConstant) * * @param enumConstant to convert * @return converted field */ protected ASTJNode<?> convertEnumConstToField(ASTJEnumConstant enumConstant) { // create field declaration with 1 variable declaration fragment VariableDeclarationFragment variableDeclarationFragment = ast.newVariableDeclarationFragment(); ast.newFieldDeclaration(variableDeclarationFragment); ASTJField field = (ASTJField)getFacadeHelper().convertToNode(variableDeclarationFragment); field.setRewriter(enumConstant.getRewriter()); field.setName(enumConstant.getName()); field.setComment(enumConstant.getComment()); field.setType(enumConstant.getParent().getName()); field.setFlags(FacadeFlags.PUBLIC | FacadeFlags.STATIC | FacadeFlags.FINAL); // create field initializer from enum constant's arguments and body setFieldInitializer(field, enumConstant); // move annotations moveChildren(enumConstant, field); return field; } /** * Sets field initializer based on arguments and body of enum constant. * <p> * Initializer string is in the form <pre><code> <b>new</b> Type ( ArgumentsList ) AnonymousClassDeclaration</code></pre> where * <ul> * <li><code>Type</code> is the the type of the class * <li><code>ArgumentsList</code> is the arguments of the enum constant with some arguments replaced by the names of static final fields. * Arguments that match the initializer value of any public static final field are replaced by the name of that field. * <li><code>AnonymousClassDeclaration is the body of enum constant * </ul> * <p> * * @param field * @param enumConstant */ protected void setFieldInitializer(ASTJField field, ASTJEnumConstant enumConstant) { String[] arguments = enumConstant.getArguments(); String body = enumConstant.getBody(); ClassInstanceCreation classInstanceCreation = ast.newClassInstanceCreation(); // set the type to create field.setNodeProperty( classInstanceCreation, enumConstant.getParent().getName(), ClassInstanceCreation.TYPE_PROPERTY, ASTNode.SIMPLE_TYPE); // set arguments if (arguments != null) { // replace some arguments by another field names (constants) for (int i = 0; i < arguments.length; i++) { if (arguments[i] != null && arguments[i].length() > 0) { ASTJField constantField = fieldInitializersMap.get(arguments[i]); if (constantField != null) { arguments[i] = constantField.getName(); // update last field that is used if (lastFinalFieldUsed != null) { int constantFieldUsedIndex = fieldIndexesMap.get(constantField); int lastFinalFieldUsedIndex = fieldIndexesMap.get(lastFinalFieldUsed); if (constantFieldUsedIndex > lastFinalFieldUsedIndex) { lastFinalFieldUsed = constantField; } } else { lastFinalFieldUsed = constantField; } } } } // set the arguments to the constructor field.setListNodeProperty(classInstanceCreation, arguments, ClassInstanceCreation.ARGUMENTS_PROPERTY, ASTNode.SIMPLE_NAME); } // set the body if (body != null && body.length() > 0) { field.setTrackedNodeProperty( classInstanceCreation, body, ClassInstanceCreation.ANONYMOUS_CLASS_DECLARATION_PROPERTY, ASTNode.ANONYMOUS_CLASS_DECLARATION); } // set the whole initializer field.setNodeProperty(field.getVariableDeclarationFragment(), classInstanceCreation, VariableDeclarationFragment.INITIALIZER_PROPERTY); } } }