/**
* 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;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import org.eclipse.emf.codegen.CodeGenPlugin;
import org.eclipse.emf.codegen.merge.java.facade.FacadeHelper;
import org.eclipse.emf.codegen.merge.java.facade.NodeConverter;
import org.eclipse.emf.common.EMFPlugin;
class PrefixHandler
{
protected String classPrefix;
protected PrefixHandler()
{
super();
}
protected PrefixHandler(String classPrefix)
{
this.classPrefix = classPrefix;
}
protected void setClassPrefix(String classPrefix)
{
this.classPrefix = classPrefix;
}
public String getClassPrefix()
{
return classPrefix;
}
}
/**
* A control model that provides dictionaries and rules to drive a merge process.
*/
public class JControlModel extends PrefixHandler
{
protected final static Class<?>[] NO_PARAMETER_TYPES = new Class<?>[0];
public static class Feature extends PrefixHandler
{
protected Class<?> featureClass;
protected Method featureMethod;
public Feature(String classPrefix)
{
super(classPrefix);
}
public Feature(String classPrefix, String path, Class<?>[] parameterTypes)
{
super(classPrefix);
initialize(path, parameterTypes);
}
public Class<?> getFeatureClass()
{
return featureClass;
}
public Method getFeatureMethod()
{
return featureMethod;
}
public void initialize(String path, Class<?>[] parameterTypes)
{
int index = path.indexOf('/');
String className = path.substring(0, index);
String methodName = path.substring(index + 1);
try
{
featureClass = classForClassName(classPrefix, className);
if (featureClass != null)
{
featureMethod = featureClass.getMethod(methodName, parameterTypes);
}
}
catch (NoSuchMethodException exception)
{
// Ignore the exception.
}
}
}
public static class DictionaryPattern extends PrefixHandler
{
protected static Class<?>[] stringParameterType = new Class<?>[] { String.class };
protected String name;
protected Feature selectorFeature;
protected Pattern pattern;
public DictionaryPattern(String classPrefix)
{
super(classPrefix);
}
public DictionaryPattern(String classPrefix, Element element)
{
this(classPrefix);
initialize(element);
}
public void initialize(Element element)
{
name = element.getAttribute("name");
selectorFeature = createFeature(getClassPrefix(), element.getAttribute("select"), NO_PARAMETER_TYPES);
pattern = Pattern.compile(element.getAttribute("match"), Pattern.MULTILINE | Pattern.DOTALL);
}
protected Feature createFeature(String classPrefix, String path, Class<?>[] parameterTypes)
{
return new Feature(getClassPrefix(), path, parameterTypes);
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public Feature getSelectorFeature()
{
return selectorFeature;
}
public void setSelectorFeature(Feature selectorFeature)
{
this.selectorFeature = selectorFeature;
}
public Pattern getPattern()
{
return pattern;
}
public void setPattern(Pattern pattern)
{
this.pattern = pattern;
}
}
public static class PullRule extends PrefixHandler
{
protected static Class<?>[] noParameterTypes = new Class<?>[0];
protected String name;
protected Pattern sourceMarkup;
protected Pattern sourceParentMarkup;
protected Feature sourceGetFeature;
protected Pattern sourceTransfer;
protected Pattern targetMarkup;
protected Pattern targetParentMarkup;
protected Feature targetPutFeature;
protected Feature equalityFeature;
public PullRule(String classPrefix)
{
super(classPrefix);
}
public PullRule(String classPrefix, Element element)
{
this(classPrefix);
initialize(element);
}
public void initialize(Element element)
{
String classPrefix = getClassPrefix();
sourceGetFeature = createFeature(classPrefix, element.getAttribute("sourceGet"), noParameterTypes);
if (sourceGetFeature != null)
{
Method featureMethod = sourceGetFeature.getFeatureMethod();
if (featureMethod != null)
{
Class<?> sourceReturnType = featureMethod.getReturnType();
targetPutFeature = createFeature(classPrefix, element.getAttribute("targetPut"), new Class<?>[] { sourceReturnType });
if (targetPutFeature.getFeatureMethod() == null && sourceReturnType.isArray())
{
targetPutFeature = createFeature(classPrefix, element.getAttribute("targetPut"), new Class<?>[] { sourceReturnType.getComponentType() });
}
}
}
if (element.hasAttribute("sourceMarkup"))
{
sourceMarkup= Pattern.compile(element.getAttribute("sourceMarkup"), Pattern.MULTILINE | Pattern.DOTALL);
}
if (element.hasAttribute("sourceParentMarkup"))
{
sourceParentMarkup= Pattern.compile(element.getAttribute("sourceParentMarkup"), Pattern.MULTILINE | Pattern.DOTALL);
}
if (element.hasAttribute("targetMarkup"))
{
targetMarkup= Pattern.compile(element.getAttribute("targetMarkup"), Pattern.MULTILINE | Pattern.DOTALL);
}
if (element.hasAttribute("targetParentMarkup"))
{
targetParentMarkup= Pattern.compile(element.getAttribute("targetParentMarkup"), Pattern.MULTILINE | Pattern.DOTALL);
}
if (element.hasAttribute("sourceTransfer"))
{
sourceTransfer= Pattern.compile(element.getAttribute("sourceTransfer"), Pattern.MULTILINE | Pattern.DOTALL);
}
if (element.hasAttribute("equals"))
{
equalityFeature = createFeature(classPrefix, element.getAttribute("equals"), noParameterTypes);
}
}
protected Feature createFeature(String classPrefix, String path, Class<?>[] parameterTypes)
{
return new Feature(getClassPrefix(), path, parameterTypes);
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public Feature getSourceGetFeature()
{
return sourceGetFeature;
}
public void setSourceGetFeature(Feature sourceGetFeature)
{
this.sourceGetFeature = sourceGetFeature;
}
public Feature getEqualityFeature()
{
return equalityFeature;
}
public void setEqualityFeature(Feature equalityFeature)
{
this.equalityFeature = equalityFeature;
}
public Feature getTargetPutFeature()
{
return targetPutFeature;
}
public void setTargetPutFeature(Feature targetPutFeature)
{
this.targetPutFeature = targetPutFeature;
}
public Pattern getSourceTransfer()
{
return sourceTransfer;
}
public void setSourceTransfer(Pattern sourceTransfer)
{
this.sourceTransfer = sourceTransfer;
}
public Pattern getSourceMarkup()
{
return sourceMarkup;
}
public void setSourceMarkup(Pattern sourceMarkup)
{
this.sourceMarkup = sourceMarkup;
}
public Pattern getTargetMarkup()
{
return targetMarkup;
}
public void setTargetMarkup(Pattern targetMarkup)
{
this.targetMarkup = targetMarkup;
}
public Pattern getSourceParentMarkup()
{
return sourceParentMarkup;
}
public void setSourceParentMarkup(Pattern sourceParentMarkup)
{
this.sourceParentMarkup = sourceParentMarkup;
}
public Pattern getTargetParentMarkup()
{
return targetParentMarkup;
}
public void setTargetParentMarkup(Pattern targetParentMarkup)
{
this.targetParentMarkup = targetParentMarkup;
}
}
/**
* <p>A push rule restricts what elements are pushed from the source to the target.
* By default, if there are no push rules for the specific element type (element is
* not an instance of selector class of any rules), then the element is pushed.</p>
*
* <p>If element is an instance of selector class of at least one push rule, then the
* element is pushed only if the element is marked up by at least one push rule with
* matching selector class.</p>
*
* <p>If none of mark-up and targetParentMarkup is set in the push rule with matching
* selector class, then node is marked up. If both mark-up and targetParentMarkup is set,
* then the node is marked up only if node and its parent in the target are marked up
* respectively. If mark-up or targetParentMarkup is set, the node is marked up if node or
* its parent in the target are marked up respectively.</p>
*/
public static class PushRule extends PrefixHandler
{
protected String name;
protected Class<?> selector;
protected Pattern markup;
protected Pattern targetParentMarkup;
public PushRule(String classPrefix)
{
super(classPrefix);
}
public PushRule(String classPrefix, Element element)
{
this(classPrefix);
initialize(element);
}
public void initialize(Element element)
{
if (element.hasAttribute("select"))
{
selector = classForClassName(getClassPrefix(), element.getAttribute("select"));
}
if (element.hasAttribute("markup"))
{
markup= Pattern.compile(element.getAttribute("markup"), Pattern.MULTILINE | Pattern.DOTALL);
}
if (element.hasAttribute("targetParentMarkup"))
{
targetParentMarkup= Pattern.compile(element.getAttribute("targetParentMarkup"), Pattern.MULTILINE | Pattern.DOTALL);
}
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public Class<?> getSelector()
{
return selector;
}
public void setSelector(Class<?> selector)
{
this.selector = selector;
}
public Pattern getMarkup()
{
return markup;
}
public void setMarkup(Pattern markup)
{
this.markup = markup;
}
public Pattern getTargetParentMarkup()
{
return targetParentMarkup;
}
public void setTargetParentMarkup(Pattern targetParentMarkup)
{
this.targetParentMarkup = targetParentMarkup;
}
}
/**
* <p>A sweep rule removes elements from the target if they are <b>NOT</b> available
* in the source. It can work on available Dictionary Patterns or be used to
* filter out import statements as follows:</p>
* <pre>
* <merge:sweep markup="^gen$" select="Member"/>
* <merge:sweep markup="^org.eclipse.emf.ecore.EMetaObject$" select="Import"/>
* </pre>
* <p>The first line removes all "members" (attribute, method, ...) that matches
* the expression defined by the "^gen$" Dictionary Pattern. The second
* removes the "org.eclipse.emf.ecore.EMetaObject" import.</p>
* <p>The <code>action</code> attribute defines what the sweep rule does with the
* node to be sweep. Besides removing the node (which is the default action), you can use this
* attribute to also rename the node or comment it out. The "rename" action
* requires the <code>newName</code> attribute to be set. This attribute is the name that
* the node is renamed to and can be expressed as <code>"deleted_{0}"</code>, where
* <code>{0}</code> is presents the current name.</p>
*/
public static class SweepRule extends PrefixHandler
{
public static enum Action
{
REMOVE,
RENAME,
COMMENT;
}
protected String name;
protected Class<?> selector;
protected Pattern markup;
protected Pattern parentMarkup;
protected SweepRule.Action action = Action.REMOVE;
protected String newName;
public SweepRule(String classPrefix)
{
super(classPrefix);
}
public SweepRule(String classPrefix, Element element)
{
this(classPrefix);
initialize(element);
}
public void initialize(Element element)
{
if (element.hasAttribute("select"))
{
selector = classForClassName(getClassPrefix(), element.getAttribute("select"));
}
if (element.hasAttribute("markup"))
{
markup= Pattern.compile(element.getAttribute("markup"), Pattern.MULTILINE | Pattern.DOTALL);
}
if (element.hasAttribute("parentMarkup"))
{
parentMarkup= Pattern.compile(element.getAttribute("parentMarkup"), Pattern.MULTILINE | Pattern.DOTALL);
}
if (element.hasAttribute("action"))
{
action = Action.valueOf(element.getAttribute("action").toUpperCase());
if (action == Action.RENAME && element.hasAttribute("newName"))
{
newName = element.getAttribute("newName");
}
}
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public Class<?> getSelector()
{
return selector;
}
public void setSelector(Class<?> selector)
{
this.selector = selector;
}
public Pattern getMarkup()
{
return markup;
}
public void setMarkup(Pattern markup)
{
this.markup = markup;
}
public Pattern getParentMarkup()
{
return parentMarkup;
}
public void setParentMarkup(Pattern parentMarkup)
{
this.parentMarkup = parentMarkup;
}
public SweepRule.Action getAction()
{
return action;
}
public void setAction(SweepRule.Action action)
{
this.action = action;
}
public String getNewName()
{
return newName;
}
public void setNewName(String newName)
{
this.newName = newName;
}
}
/**
* <p>The sort rule is used to ensure that the order of the attributes as declared
* on the source is respected As usual you need to specify a Dictionary Pattern
* to identify the attributes that should be treated. Here's an example:</p>
* <pre>
* <merge:sort markup="^ordered$" select="Member"/>
* </pre>
*/
public static class SortRule extends PrefixHandler
{
protected String name;
protected Class<?> selector;
protected Pattern markup;
public SortRule(String classPrefix)
{
super(classPrefix);
}
public SortRule(String classPrefix, Element element)
{
this(classPrefix);
initialize(element);
}
public void initialize(Element element)
{
if (element.hasAttribute("select"))
{
selector = classForClassName(getClassPrefix(), element.getAttribute("select"));
}
if (element.hasAttribute("markup"))
{
markup= Pattern.compile(element.getAttribute("markup"), Pattern.MULTILINE | Pattern.DOTALL);
}
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public Class<?> getSelector()
{
return selector;
}
public void setSelector(Class<?> selector)
{
this.selector = selector;
}
public Pattern getMarkup()
{
return markup;
}
public void setMarkup(Pattern markup)
{
this.markup = markup;
}
}
static Map<String, Class<?>> classNameToClassMap = new HashMap<String, Class<?>>();
public static Class<?> classForClassName(String classPrefix, String className)
{
if (classPrefix != null)
{
className = classPrefix + className;
}
Class<?> result = classNameToClassMap.get(className);
if (result == null)
{
try
{
result = Class.forName(className);
classNameToClassMap.put(className, result);
}
catch (ClassNotFoundException exception)
{
// We expect this failure when running stand-alone
//
if (EMFPlugin.IS_ECLIPSE_RUNNING)
{
CodeGenPlugin.INSTANCE.log(exception);
}
// JControlModel.class is used with classNames that were not found
//
classNameToClassMap.put(className, JControlModel.class);
}
}
return result == JControlModel.class ?
null :
result;
}
/**
* <p>During the merge, the current state of the nodes of a tree is applied to the nodes of
* another tree. Pairing the nodes of each tree is one of the main activities of the process.</p>
*
* <p>A Match Rule allows a developer to replace the default matching algorithm, based on
* qualified names, to better suite the needs of an specific application. Some examples:</p>
*
* <pre>
* <merge:match markup="^gen$" get="Member/getName"/>
* <merge:match get="Method/getComment" signature="\s*@\s*uuid\s*(\S*)\s*\n"/></pre>
*
* <p>The first match rule is applicable to any Member marked with the expression defined
* by the "^gen$" Dictionary Pattern. It defines that these members are matched
* by their names. The second rule is applicable to any method that has
* <code>@uuid xyz</code> on its comment. In this case, the string <code>xyz</code>
* is be used to match the nodes.</p>
*
* <p>An important remark is that if there is a type conversion during the merge process,
* the match rules should <b>not</b> be used to pair different "kinds" of elements
* like fields and enumerator constants for example. See
* {@link JMerger#convertTarget(org.eclipse.emf.codegen.merge.java.facade.JAbstractType, Class) Jmerger.convert}
* and {@link NodeConverter} for further details on conversions.</p>
*/
public static class MatchRule extends PrefixHandler
{
protected String name;
protected Pattern markup;
protected Feature getFeature;
protected Pattern signature;
protected boolean stopMatching = false;
public MatchRule(String classPrefix)
{
super(classPrefix);
}
public MatchRule(String classPrefix, Element element)
{
this(classPrefix);
initialize(element);
}
public void initialize(Element element)
{
if (element.hasAttribute("markup"))
{
markup= Pattern.compile(element.getAttribute("markup"), Pattern.MULTILINE | Pattern.DOTALL);
}
if (element.hasAttribute("get"))
{
getFeature = createFeature(getClassPrefix(), element.getAttribute("get"), NO_PARAMETER_TYPES);
if (element.hasAttribute("signature"))
{
signature = Pattern.compile(element.getAttribute("signature"), Pattern.MULTILINE | Pattern.DOTALL);
}
}
if (element.hasAttribute("stopMatching"))
{
stopMatching = Boolean.parseBoolean(element.getAttribute("stopMatching"));
}
}
protected Feature createFeature(String classPrefix, String path, Class<?>[] parameterTypes)
{
return new Feature(getClassPrefix(), path, parameterTypes);
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public Feature getGetFeature()
{
return getFeature;
}
public void setGetFeature(Feature getFeature)
{
this.getFeature = getFeature;
}
public Pattern getMarkup()
{
return markup;
}
public void setMarkup(Pattern markup)
{
this.markup = markup;
}
public Pattern getSignature()
{
return signature;
}
public void setSignature(Pattern signature)
{
this.signature = signature;
}
public boolean isStopMatching()
{
return this.stopMatching;
}
public void setStopMatching(boolean stopMatching)
{
this.stopMatching = stopMatching;
}
}
protected FacadeHelper facadeHelper;
protected List<DictionaryPattern> dictionaryPatterns;
protected List<PullRule> pullRules;
protected List<PushRule> pushRules;
protected List<SweepRule> sweepRules;
protected List<SortRule> sortRules;
protected List<MatchRule> matchRules;
protected Pattern blockPattern;
protected Pattern noImportPattern;
protected String redirect;
protected boolean indentIsSet = false;
protected String indent;
protected boolean standardBraceStyleIsSet = false;
protected boolean standardBraceStyle;
public JControlModel()
{
super();
}
protected void setFacadeHelper(FacadeHelper facadeHelper)
{
if (this.facadeHelper != null)
{
this.facadeHelper.setControlModel(null);
if (dictionaryPatterns != null)
{
dictionaryPatterns.clear();
}
if (pullRules != null)
{
pullRules.clear();
}
if (pushRules != null)
{
pushRules.clear();
}
if (sweepRules != null)
{
sweepRules.clear();
}
if (sortRules != null)
{
sortRules.clear();
}
if (matchRules != null)
{
matchRules.clear();
}
blockPattern = null;
noImportPattern = null;
redirect = null;
}
this.facadeHelper = facadeHelper;
if (facadeHelper != null)
{
setClassPrefix(facadeHelper.getClassPrefix());
facadeHelper.setControlModel(this);
}
}
public FacadeHelper getFacadeHelper()
{
return facadeHelper;
}
public boolean convertToStandardBraceStyle()
{
return standardBraceStyle;
}
public void setConvertToStandardBraceStyle(boolean standardBraceStyle)
{
standardBraceStyleIsSet = true;
this.standardBraceStyle = standardBraceStyle;
}
public String getLeadingTabReplacement()
{
return indent;
}
public void setLeadingTabReplacement(String indent)
{
indentIsSet = true;
this.indent = indent;
}
public String getRedirect()
{
return redirect;
}
public Pattern getBlockPattern()
{
return blockPattern;
}
public Pattern getNoImportPattern()
{
return noImportPattern;
}
public List<DictionaryPattern> getDictionaryPatterns()
{
if (dictionaryPatterns == null)
{
dictionaryPatterns = new ArrayList<DictionaryPattern>();
}
return dictionaryPatterns;
}
public List<PullRule> getPullRules()
{
if (pullRules == null)
{
pullRules = new ArrayList<PullRule>();
}
return pullRules;
}
public List<PushRule> getPushRules()
{
if (pushRules == null)
{
pushRules = new ArrayList<PushRule>();
}
return pushRules;
}
public List<SweepRule> getSweepRules()
{
if (sweepRules == null)
{
sweepRules = new ArrayList<SweepRule>();
}
return sweepRules;
}
public List<SortRule> getSortRules()
{
if (sortRules == null)
{
sortRules = new ArrayList<SortRule>();
}
return sortRules;
}
public List<MatchRule> getMatchRules()
{
if (matchRules == null)
{
matchRules = new ArrayList<MatchRule>();
}
return matchRules;
}
public boolean canMerge()
{
FacadeHelper facadeHelper = getFacadeHelper();
return facadeHelper != null && facadeHelper.canMerge();
}
public void initialize(FacadeHelper facadeHelper, String uri)
{
setFacadeHelper(facadeHelper);
initialize(uri);
}
protected void initialize(String uri)
{
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
documentBuilderFactory.setNamespaceAware(true);
documentBuilderFactory.setValidating(false);
try
{
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
Document document = documentBuilder.parse(new InputSource(uri));
initialize(document.getDocumentElement());
}
catch (Exception exception)
{
CodeGenPlugin.INSTANCE.log(exception);
}
}
protected void initialize(Element element)
{
if (element.getLocalName().equals("options"))
{
if (!standardBraceStyleIsSet && "standard".equals(element.getAttributeNS(null, "braceStyle")))
{
standardBraceStyle = true;
}
if (!indentIsSet && element.hasAttributeNS(null, "indent"))
{
indent = element.getAttributeNS(null, "indent");
}
if (element.hasAttributeNS(null, "redirect"))
{
redirect = element.getAttributeNS(null, "redirect");
}
if (element.hasAttributeNS(null, "block"))
{
blockPattern = Pattern.compile(element.getAttributeNS(null, "block"), Pattern.MULTILINE | Pattern.DOTALL);
}
if (element.hasAttributeNS(null, "noImport"))
{
noImportPattern = Pattern.compile(element.getAttributeNS(null, "noImport"), Pattern.MULTILINE | Pattern.DOTALL);
}
String classPrefix = getClassPrefix();
for (Node child = element.getFirstChild(); child != null; child = child.getNextSibling())
{
if (child.getNodeType() == Node.ELEMENT_NODE)
{
Element elementChild = (Element)child;
if (elementChild.getLocalName().equals("dictionaryPattern"))
{
getDictionaryPatterns().add(createDictionaryPattern(classPrefix, elementChild));
}
else if (elementChild.getLocalName().equals("pull"))
{
getPullRules().add(createPullRule(classPrefix, elementChild));
}
else if (elementChild.getLocalName().equals("push"))
{
getPushRules().add(createPushRule(classPrefix, elementChild));
}
else if (elementChild.getLocalName().equals("sweep"))
{
getSweepRules().add(createSweepRule(classPrefix, elementChild));
}
else if (elementChild.getLocalName().equals("sort"))
{
getSortRules().add(createSortRule(classPrefix, elementChild));
}
else if (elementChild.getLocalName().equals("match"))
{
getMatchRules().add(createMatchRule(classPrefix, elementChild));
}
}
}
}
}
protected DictionaryPattern createDictionaryPattern(String classPrefix, Element elementChild)
{
return new DictionaryPattern(classPrefix, elementChild);
}
protected PullRule createPullRule(String classPrefix, Element elementChild)
{
return new PullRule(classPrefix, elementChild);
}
protected PushRule createPushRule(String classPrefix, Element elementChild)
{
return new PushRule(classPrefix, elementChild);
}
protected SweepRule createSweepRule(String classPrefix, Element elementChild)
{
return new SweepRule(classPrefix, elementChild);
}
protected SortRule createSortRule(String classPrefix, Element elementChild)
{
return new SortRule(classPrefix, elementChild);
}
protected MatchRule createMatchRule(String classPrefix, Element elementChild)
{
return new MatchRule(classPrefix, elementChild);
}
}