/**
* 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;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.emf.codegen.merge.java.facade.FacadeVisitor;
import org.eclipse.emf.codegen.merge.java.facade.JAbstractType;
import org.eclipse.emf.codegen.merge.java.facade.JAnnotation;
import org.eclipse.emf.codegen.merge.java.facade.JAnnotationTypeMember;
import org.eclipse.emf.codegen.merge.java.facade.JCompilationUnit;
import org.eclipse.emf.codegen.merge.java.facade.JEnumConstant;
import org.eclipse.emf.codegen.merge.java.facade.JField;
import org.eclipse.emf.codegen.merge.java.facade.JImport;
import org.eclipse.emf.codegen.merge.java.facade.JInitializer;
import org.eclipse.emf.codegen.merge.java.facade.JMethod;
import org.eclipse.emf.codegen.merge.java.facade.JNode;
import org.eclipse.emf.codegen.merge.java.facade.JPackage;
/**
* A dictionary of signatures and {@link JNode}s.
*/
public class JPatternDictionary extends FacadeVisitor
{
protected static final Pattern COMMENT = Pattern.compile("/\\*.*?\\*/", Pattern.MULTILINE | Pattern.DOTALL);
protected static final Object [] NO_ARGUMENTS = new Object[0];
protected static final boolean DEBUG = JMerger.DEBUG;
protected JControlModel controlModel;
protected JPackage jPackage;
protected Map<String, JAnnotation> annotationMap;
protected Map<String, JAnnotationTypeMember> annotationTypeMemberMap;
protected Map<String, JEnumConstant> enumConstantMap;
protected Map<String, JImport> importMap;
protected Map<String, JAbstractType> abstractTypeMap;
protected Map<String, JInitializer> initializerMap;
protected Map<String, JField> fieldMap;
protected Map<String, JMethod> methodMap;
protected Map<String, Collection<JNode>> markupMap;
protected Set<String> noImportSet;
protected Map<JNode, String> nodeIdentifierMap;
public JPatternDictionary(JCompilationUnit compilationUnit, JControlModel controlModel)
{
super();
this.controlModel = controlModel;
start(compilationUnit);
if (DEBUG)
{
System.out.println("Maps of compilation unit: " + compilationUnit.getName());
dumpMaps();
}
}
/**
* Resets this JPatternDictionary. After calling this method, it is necessary to invoke
* {@link #start(JNode)} passing a compilation unit to reuse this instance.
*/
public void reset()
{
jPackage = null;
if (annotationMap != null)
{
annotationMap.clear();
annotationMap = null;
}
if (annotationTypeMemberMap != null)
{
annotationTypeMemberMap.clear();
annotationMap = null;
}
if (enumConstantMap != null)
{
enumConstantMap.clear();
enumConstantMap = null;
}
if (importMap != null)
{
importMap.clear();
importMap = null;
}
if (abstractTypeMap != null)
{
abstractTypeMap.clear();
abstractTypeMap = null;
}
if (initializerMap != null)
{
initializerMap.clear();
initializerMap = null;
}
if (fieldMap != null)
{
fieldMap.clear();
fieldMap = null;
}
if (methodMap != null)
{
methodMap.clear();
methodMap = null;
}
if (markupMap != null)
{
markupMap.clear();
markupMap = null;
}
if (noImportSet != null)
{
noImportSet.clear();
noImportSet = null;
}
if (nodeIdentifierMap != null)
{
nodeIdentifierMap.clear();
nodeIdentifierMap = null;
}
}
public JPackage getJPackage()
{
return jPackage;
}
public Map<String, ? extends JNode> getNodeMap(JNode node)
{
if (node instanceof JAnnotation) return getAnnotationMap();
if (node instanceof JMethod) return getMethodMap();
if (node instanceof JField) return getFieldMap();
if (node instanceof JImport) return getImportMap();
if (node instanceof JEnumConstant) return getEnumConstantMap();
if (node instanceof JAbstractType) return getAbstractTypeMap();
if (node instanceof JAnnotationTypeMember) return getAnnotationTypeMemberMap();
if (node instanceof JInitializer) return getInitializerMap();
return Collections.emptyMap();
}
public Map<String, JImport> getImportMap()
{
if (importMap == null)
{
importMap = new HashMap<String, JImport>();
}
return importMap;
}
public Map<String, JAbstractType> getAbstractTypeMap()
{
if (abstractTypeMap == null)
{
abstractTypeMap = new HashMap<String, JAbstractType>();
}
return abstractTypeMap;
}
public Map<String, JInitializer> getInitializerMap()
{
if (initializerMap == null)
{
initializerMap = new HashMap<String, JInitializer>();
}
return initializerMap;
}
public Map<String, JField> getFieldMap()
{
if (fieldMap == null)
{
fieldMap = new HashMap<String, JField>();
}
return fieldMap;
}
public Map<String, JMethod> getMethodMap()
{
if (methodMap == null)
{
methodMap = new HashMap<String, JMethod>();
}
return methodMap;
}
public Map<String, JAnnotation> getAnnotationMap()
{
if (annotationMap == null)
{
annotationMap = new HashMap<String, JAnnotation>();
}
return annotationMap;
}
public Map<String, JAnnotationTypeMember> getAnnotationTypeMemberMap()
{
if (annotationTypeMemberMap == null)
{
annotationTypeMemberMap = new HashMap<String, JAnnotationTypeMember>();
}
return annotationTypeMemberMap;
}
public Map<String, JEnumConstant> getEnumConstantMap()
{
if (enumConstantMap == null)
{
enumConstantMap = new HashMap<String, JEnumConstant>();
}
return enumConstantMap;
}
public Map<String, Collection<JNode>> getMarkupMap()
{
if (markupMap == null)
{
markupMap = new HashMap<String, Collection<JNode>>();
}
return markupMap;
}
/**
* Determines if the node is marked up based on the node and parent markup.
* <p>
* If both patterns are <code>null</code>, the node is marked up.
* <p>
* If both patterns are not <code>null</code>, the node is marked up if the node itself and its
* parent is marked up. If the node does not have a parent, the node is marked up if the node itself is marked up.
* <p>
* If only <code>markupPattern</code> or <code>parentMarkupPattern</code> is set, the node is marked up
* if the node or its parent is marked up respectively.
*
* @param markupPattern
* @param parentMarkupPattern
* @param node
*
* @see #isMarkedUp(Pattern, JNode)
*/
public boolean isMarkedUp(Pattern markupPattern, Pattern parentMarkupPattern, JNode node)
{
return
(markupPattern == null || isMarkedUp(markupPattern, node)) &&
(parentMarkupPattern == null || node.getParent() == null || isMarkedUp(parentMarkupPattern, node.getParent()));
}
public boolean isMarkedUp(Pattern markupPattern, JNode node)
{
if (markupPattern == null)
{
return true;
}
else
{
for (Map.Entry<String, Collection<JNode>> markupEntry : getMarkupMap().entrySet())
{
String key = markupEntry.getKey();
if (key != null && markupPattern.matcher(key).find())
{
if (markupEntry.getValue().contains(node))
{
return true;
}
}
}
return false;
}
}
protected Set<String> getNoImporterSet()
{
if (noImportSet == null)
{
noImportSet = new HashSet<String>();
}
return noImportSet;
}
public boolean isNoImport(JImport jImport)
{
return noImportSet != null && getNoImporterSet().contains(getNodeIdentifier(jImport));
}
@Override
protected boolean visit(JCompilationUnit compilationUnit)
{
if (controlModel.getNoImportPattern() != null)
{
// Optimize the performance of applying the import regular
// expressions locating the last import line
//
String contents = compilationUnit.getContents();
int lastIndex = contents.length() - 1;
int endIndex = contents.lastIndexOf("import");
while (endIndex >= 0)
{
int index = endIndex + "import".length() - 1;
if (index == lastIndex || !Character.isWhitespace(contents.charAt(index+1)))
{
endIndex = contents.lastIndexOf("import", endIndex-1);
}
else
{
index = contents.indexOf(';', index);
if (index >= 0)
{
for (int length = contents.length(); index < length; ++index)
{
char character = contents.charAt(index);
if (character == '\n' || character == '\r')
{
break;
}
}
endIndex = index+1;
break;
}
else
{
endIndex -= "import".length();
}
}
}
if (endIndex > 0 && endIndex < contents.length())
{
contents = contents.substring(0, endIndex);
}
Matcher matcher = controlModel.getNoImportPattern().matcher(contents);
while (matcher.find())
{
getNoImporterSet().add(matcher.group(1));
}
}
return super.visit(compilationUnit);
}
@Override
protected boolean visit(JPackage jPackage)
{
this.jPackage = jPackage;
return super.visit(jPackage);
}
@Override
protected boolean visit(JAbstractType abstractType)
{
getAbstractTypeMap().put(getNodeIdentifier(abstractType), abstractType);
return super.visit(abstractType);
}
@Override
protected boolean visit(JImport jImport)
{
getImportMap().put(getNodeIdentifier(jImport), jImport);
return super.visit(jImport);
}
@Override
protected boolean visit(JInitializer initializer)
{
getInitializerMap().put(getNodeIdentifier(initializer), initializer);
return super.visit(initializer);
}
@Override
protected boolean visit(JField field)
{
getFieldMap().put(getNodeIdentifier(field), field);
return super.visit(field);
}
@Override
protected boolean visit(JMethod method)
{
getMethodMap().put(getNodeIdentifier(method), method);
return super.visit(method);
}
@Override
protected boolean visit(JAnnotation annotation)
{
getAnnotationMap().put(getNodeIdentifier(annotation), annotation);
return super.visit(annotation);
}
@Override
protected boolean visit(JAnnotationTypeMember annotationTypeMember)
{
getAnnotationTypeMemberMap().put(getNodeIdentifier(annotationTypeMember), annotationTypeMember);
return super.visit(annotationTypeMember);
}
@Override
protected boolean visit(JEnumConstant enumConstant)
{
getEnumConstantMap().put(getNodeIdentifier(enumConstant), enumConstant);
return super.visit(enumConstant);
}
@Override
protected void beforeVisit(JNode node)
{
Method previousMethod = null;
String previousSelection = null;
for (JControlModel.DictionaryPattern dictionaryPattern : controlModel.getDictionaryPatterns())
{
JControlModel.Feature feature = dictionaryPattern.getSelectorFeature();
Method method = feature.getFeatureMethod();
String selection = null;
if (feature.getFeatureClass() != null && feature.getFeatureClass().isInstance(node))
{
try
{
if (method.equals(previousMethod))
{
selection = previousSelection;
}
else
{
selection = (String)method.invoke(node, NO_ARGUMENTS);
if (controlModel.getFacadeHelper() == null || controlModel.getFacadeHelper().canYieldWrongJavadoc())
{
selection = checkSelection(selection, dictionaryPattern, node);
}
previousMethod = method;
previousSelection = selection;
}
if (selection != null)
{
markupNode(selection, dictionaryPattern, node);
}
}
catch (IllegalAccessException exception)
{
if (DEBUG)
{
exception.printStackTrace();
}
}
catch (InvocationTargetException exception)
{
if (DEBUG)
{
exception.printStackTrace();
}
}
}
}
}
/**
* Checks the selection to fix the problem with facade implementations that assign
* wrong javadoc to the node.
*
* @param selection
* @param dictionaryPattern
* @param node
* @return <code>null</code> if the node should be skipped
*/
protected String checkSelection(String selection, JControlModel.DictionaryPattern dictionaryPattern, JNode node)
{
if (selection != null
&& !(node instanceof JAbstractType)
&& dictionaryPattern.getSelectorFeature().getFeatureMethod().getName().equals("getComment"))
{
String contents = node.getContents();
if (contents != null)
{
for (int start = 0, end = contents.length(), count = 0; start < end; )
{
contents = contents.substring(start, end);
Matcher matcher = COMMENT.matcher(contents);
if (matcher.find())
{
if (++count > 1)
{
// Ignore the further-most javadoc
//
selection = contents;
}
start += matcher.end(0) + 1;
end = contents.length();
}
else
{
break;
}
}
}
}
return selection;
}
/**
* Matches pattern in dictionary pattern against selection, and marks up node
* with all matching groups.
* <p>
* If pattern matches selection, but no groups are defined, node is marked up with
* dictionary pattern name.
*
* @param selection
* @param dictionaryPattern
* @param node
*/
protected void markupNode(String selection, JControlModel.DictionaryPattern dictionaryPattern, JNode node)
{
Pattern pattern = dictionaryPattern.getPattern();
if (pattern.pattern().startsWith("@"))
{
int index = selection.indexOf('@');
if (index != -1)
{
selection = selection.substring(index, selection.length());
}
}
Matcher matcher = pattern.matcher(selection);
if (matcher.find())
{
if (matcher.groupCount() > 0)
{
for (int i = 1; i <= matcher.groupCount(); ++i)
{
String markup = matcher.group(i);
markupNode(markup, node);
}
}
else
{
// if there are no groups defined or matched, but the whole pattern matches,
// then markup nodes with pattern name
//
String markup = dictionaryPattern.getName();
if (markup != null && !"".equals(markup))
{
markupNode(markup, node);
}
}
}
}
/**
* Marks up node with the given markup string.
*
* @param markup
* @param node
*/
protected void markupNode(String markup, JNode node)
{
Collection<JNode> collection = getMarkupMap().get(markup);
if (collection == null)
{
collection = new HashSet<JNode>();
getMarkupMap().put(markup, collection);
}
collection.add(node);
}
// /**
// * Markup node same as the other node if the markup pattern in dictionary pattern
// * matches markup of other node.
// * <br>
// * This method allows copying markup from parent using Node/getParent pull rule.
// *
// * @param node
// * @param dictionaryPattern
// * @param otherNode
// */
// private void addMarkupFromOtherNode(JNode node, JControlModel.DictionaryPattern dictionaryPattern, JNode otherNode)
// {
// Pattern markupPattern = dictionaryPattern.getPattern();
// for (Map.Entry<String, Collection<JNode>> markupEntry : getMarkupMap().entrySet())
// {
// String key = markupEntry.getKey();
// if (key != null && markupPattern.matcher(key).find())
// {
// Collection<JNode> markedUpNodes = markupEntry.getValue();
// if (markedUpNodes.contains(otherNode))
// {
// markedUpNodes.add(node);
// }
// }
// }
// }
protected void dumpMaps()
{
try
{
Field[] fields = this.getClass().getDeclaredFields();
for (int i = 0; i < fields.length; i++)
{
if (fields[i].getName().endsWith("Map") && Map.class.isAssignableFrom(fields[i].getType()))
{
Map< ? , ? > map = (Map< ? , ? >)fields[i].get(this);
String mapString = String.format("%s = %s\n", fields[i].getName(), dumpMap("\t", map));
System.out.print(mapString);
}
}
}
catch (Exception e)
{
if (DEBUG)
{
e.printStackTrace();
}
}
}
protected static <K, V> String dumpMap(String lineIndent, Map<K, V> map)
{
int columnWidth = 10;
if (map != null)
{
StringBuilder sb = new StringBuilder("\n");
for (Map.Entry<K, V> entry : map.entrySet())
{
K key = entry.getKey();
String keyString = (key == null ? null : key.toString());
if (keyString != null && columnWidth < keyString.length())
{
columnWidth = keyString.length() + 25;
}
sb.append(String.format("%s%-" + columnWidth + "s = ", lineIndent, keyString));
if (entry.getValue() instanceof Collection<?>)
{
sb.append("\n");
Collection<?> values = (Collection<?>)entry.getValue();
for (Object element : values)
{
sb.append(String.format("%s%<s%s\n", lineIndent, element.toString()));
}
}
else
{
sb.append(String.format("%s\n", entry.getValue()));
}
}
return sb.toString();
}
else
{
return null;
}
}
public String getNodeIdentifier(JNode node)
{
String identifier = nodeIdentifierMap == null ? null : nodeIdentifierMap.get(node);
if (identifier == null)
{
StringBuilder sb = new StringBuilder();
for (JControlModel.MatchRule matchRule : controlModel.getMatchRules())
{
if (isMarkedUp(matchRule.getMarkup(), node) &&
matchRule.getGetFeature().getFeatureClass().isInstance(node))
{
try
{
Method getMethod = matchRule.getGetFeature().getFeatureMethod();
Object value = getMethod.invoke(node, JMerger.NO_ARGUMENTS);
if (value instanceof String)
{
String stringValue = (String)value;
Pattern signature = matchRule.getSignature();
if (signature != null)
{
Matcher matcher = signature.matcher(stringValue);
stringValue = matcher.find() && matcher.groupCount() == 1 ?
stringValue = matcher.group(1)
: null;
}
if (stringValue != null && stringValue.length() > 0)
{
sb.append(stringValue);
if (matchRule.isStopMatching())
{
break;
}
}
}
}
catch (Exception e)
{
// Ignore
}
}
}
identifier = sb.length() > 0 ? sb.toString() : getDefaultNodeIdentifier(node);
if (nodeIdentifierMap == null)
{
nodeIdentifierMap = new HashMap<JNode, String>();
}
nodeIdentifierMap.put(node, identifier);
}
return identifier;
}
protected String getDefaultNodeIdentifier(JNode node)
{
return node.getQualifiedName();
}
public JNode getNode(String nodeIdentifier)
{
if (nodeIdentifier != null && nodeIdentifierMap != null)
{
for (Map.Entry<JNode, String> entry : nodeIdentifierMap.entrySet())
{
if (nodeIdentifier.equals(entry.getValue()))
{
return entry.getKey();
}
}
}
return null;
}
}