/** * Copyright (c) 2006-2008 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; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.emf.codegen.merge.java.JControlModel; import org.eclipse.emf.codegen.util.CodeGenUtil; import org.eclipse.jdt.core.JavaCore; public abstract class FacadeHelper { protected static final String CLASS_PREFIX = "org.eclipse.emf.codegen.merge.java.facade.J"; protected JControlModel controlModel; protected Map<Object, JNode> objectToNodeMap; /** * @since 2.8 */ protected String compilerCompliance; public void reset() { if (objectToNodeMap != null) { for (JNode node : objectToNodeMap.values()) { disposeNode(node); } objectToNodeMap.clear(); } } /** * @return whether this facade helper can be used on a merge operation. Typically * subclasses would return <code>false</code> depending on some environment requirement. * @since 2.4 */ public boolean canMerge() { return true; } protected Map<Object, JNode> getObjectToNodeMap() { if (objectToNodeMap == null) { objectToNodeMap = new HashMap<Object, JNode>(); } return objectToNodeMap; } public String getClassPrefix() { return CLASS_PREFIX; } /** * <p>Disposes the node and its children. If the node is null, * this method doesn't do anything.</p> * <p>After being disposed, the node and its children should not be * reused.</p> * @param node */ public void dispose(JNode node) { for (JNode child : node.getChildren()) { dispose(child); } disposeNode(node); } /** * Disposes a single node. * @param node */ protected void disposeNode(JNode node) { if (node instanceof AbstractJNode) { ((AbstractJNode)node).dispose(); } } /** * Returns <code>true</code> if a node is * disposed. * @param node * @return boolean * @see #dispose(JNode) */ public boolean isDisposed(JNode node) { return node instanceof AbstractJNode && ((AbstractJNode)node).isDisposed(); } /** * Sets this facade helper's control model. * @param controlModel * @throws IllegalArgumentException if the control model's facade helper is * different than this facade helper. */ public void setControlModel(JControlModel controlModel) throws IllegalArgumentException { if (controlModel != null && controlModel.getFacadeHelper() != this) { throw new IllegalArgumentException("Invalid control model"); } if (this.controlModel != null) { reset(); } this.controlModel = controlModel; } public JControlModel getControlModel() { return controlModel; } public JNode convertToNode(Object object) { JNode node = getObjectToNodeMap().get(object); if (node == null) { node = doConvertToNode(object); if (node != null) { getObjectToNodeMap().put(object, node); } } return node; } /** * Clones the specified node, returning an object that is related to * the given context. On some implementations the context may be * <code>null</code>. * @param context * @param node * @return a cloned version of the specified node */ public abstract JNode cloneNode(Object context, JNode node); /** * Returns the context of a node. The context is usually * an object that would be used to create a child or sibling of the node. * @return the context of a node. */ public abstract Object getContext(JNode node); public abstract JCompilationUnit createCompilationUnit(String name, String contents); protected abstract JNode doConvertToNode(Object object); /** * Returns the original, unmodified, content of the compilation unit. * Not all facade implementation may be able to provide this information. * @param compilationUnit * @return the original contents or null */ public String getOriginalContents(JCompilationUnit compilationUnit) { return null; } /** * @return converter that can be used to convert nodes, or <code>null</code> if none */ public NodeConverter getNodeConverter() { return null; } /** * Returns the compilation unit of the specified node. * @param node * @return the {@link JCompilationUnit} of a {@link JNode} or * <code>null</code>. */ public JCompilationUnit getCompilationUnit(JNode node) { while(node != null) { if (node instanceof JCompilationUnit) { return (JCompilationUnit)node; } else { node = node.getParent(); } } return null; } /** * Returns the package of the specified node. * @param node * @return the {@link JPackage} of a {@link JNode} or * <code>null</code> */ public JPackage getPackage(JNode node) { JCompilationUnit compilationUnit = getCompilationUnit(node); if (compilationUnit != null) { for (JNode child : compilationUnit.getChildren()) { if (child instanceof JPackage) { return (JPackage)child; } } } return null; } /** * Returns the first public type in of a compilation unit. * @param compilationUnit * @return the first public type of a compilation unit */ public JType getMainType(JCompilationUnit compilationUnit) { for (JType type : getChildren(compilationUnit, JType.class)) { if (FacadeFlags.isPublic(type.getFlags())) { return type; } } return null; } /** * Returns a list with the children of the specified node that are instances of * the given class. The list should be an unmodifiable list if it doesn't support * changes. * @param node * @param cls class of the child * @return the list of children of a {@link JNode} */ public <T extends JNode> List<T> getChildren(JNode node, Class<T> cls) { if (node != null && cls != null) { List<JNode> allChildren = node.getChildren(); List<T> children = new ArrayList<T>(allChildren.size()); for (JNode child : allChildren) { if (cls.isInstance(child)) { children.add(cls.cast(child)); } } if (!children.isEmpty()) { return Collections.unmodifiableList(children); } } return Collections.<T>emptyList(); } /** * Returns the first child of the specified node. * Children appear in the order in which they exist in the source code. * @param node * @return the first child, or <code>null</code> if this node has no children * @see #getChildren(JNode, Class) */ public JNode getFirstChild(JNode node) { if (node != null) { List<JNode> children = node.getChildren(); if (!children.isEmpty()) { return children.get(0); } } return null; } /** * Returns the sibling node immediately preceding the specified node. * @param node * @return the previous node, or <code>null</code> if there is no preceding node */ public JNode getPrevious(JNode node) { return getSibiling(node, -1); } /** * Returns the sibling node immediately following the specified node. * @param node * @return the next node, or <code>null</code> if there is no following node */ public JNode getNext(JNode node) { return getSibiling(node, 1); } /** * Returns the sibling of the specified node that is located * in a specific position relative to the node. * @param node * @param pos position of the sibling, relative to the node (can be a negative number) * @return the sibling, or <code>null</code> if this node has no children * @see #getChildren(JNode, Class) */ protected JNode getSibiling(JNode node, int pos) { if (node != null && node.getParent() != null) { List<JNode> children = node.getParent().getChildren(); int index = children.indexOf(node) + pos; if (index >= 0 && index < children.size()) { return children.get(index); } } return null; } /** * Adds the given orphan node (document fragment) as the last child of the * specified node. * @param node the parent of the child to be added * @param child the new child node * @return whether the operation was successful. * @see #insertSibling(JNode, JNode, boolean) * @see #remove(JNode) */ public boolean addChild(JNode node, JNode child) { if (node != null && child != null) { try { return node.getChildren().add(child); } catch(UnsupportedOperationException e) { // Ignore } } return false; } /** * Inserts the given orphan node as a sibling of the specified node, immediately * before or after it. * @param node the node that will be after the new sibling * @param newSibling the new sibling node * @param before whether the sibling should be added before the node * @see #addChild(JNode, JNode) * @see #remove(JNode) */ public boolean insertSibling(JNode node, JNode newSibling, boolean before) { if (node != null && newSibling != null) { JNode parent = node.getParent(); if (parent != null) { List<JNode> children = parent.getChildren(); int index = children.indexOf(node); if (!before) { index++; } try { if (index == children.size()) { return children.add(newSibling); } else { children.add(index, newSibling); return children.get(index) == newSibling; } } catch(UnsupportedOperationException e) { // Ignore } } } return false; } /** * Separates the specified node from its parent and siblings, maintaining any ties * that this node has to the underlying document fragment. A document fragment that * is removed from its host document may still be dependent on that host document * until it is inserted into a different document. Removing a root node has no effect. * @param node the node to be removed * @return whether the operation was successful. * @see #addChild(JNode, JNode) * @see #insertSibling(JNode, JNode, boolean) */ public boolean remove(JNode node) { if (node != null) { JNode parent = node.getParent(); if (parent != null) { try { return parent.getChildren().remove(node); } catch(UnsupportedOperationException e) { // Ignore } } } return false; } /** * Comment out the node and its children. The node may still be returned by * {@link JNode#getChildren()} * @param node */ public void commentOut(JNode node) { throw new UnsupportedOperationException("This facade implementation cannot comment out nodes."); } /** * Formats the specified string using the * {@link CodeGenUtil#convertFormat(String, boolean, String)} method. * @param value * @return the formatted String. */ public String applyFormatRules(String value) { // do not crash when control model is not set return getControlModel() == null ? value : CodeGenUtil.convertFormat(getControlModel().getLeadingTabReplacement(), getControlModel().convertToStandardBraceStyle(), value); } /** * @param object */ public String toString(Object object) { return object == null ? null : object.toString(); } public boolean fixInterfaceBrace() { return false; } /** * Returns whether this facade implementation may return the * wrong Javadoc for a node when there are 2 or more javadoc blocks preceding * the node. * @return boolean */ public boolean canYieldWrongJavadoc() { return false; } /** * Returns whether using the {@link #getPrevious(JNode)} and * {@link #getNext(JNode)} methods is slower than using {@link JNode#getChildren()} * directly. * @return boolean */ public boolean isSibilingTraversalExpensive() { return true; } /** * Returns the required Java {@link JavaCore#COMPILER_COMPLIANCE compiler compliance} level. * @return the required Java compiler compliance level. * @since 2.8 */ public String getCompilerCompliance() { return compilerCompliance; } /** * Specify the required Java {@link JavaCore#COMPILER_COMPLIANCE compiler compliance} level. * @param complierCompliance the required Java compiler compliance level. * @since 2.8 */ public void setCompilerCompliance(String compilerCompliance) { this.compilerCompliance = compilerCompliance; } }