/* Copyright (C) 2009 Versant Inc. http://www.db4o.com */
package sharpen.core.internal;
import java.util.*;
import org.eclipse.jdt.core.dom.*;
import sharpen.core.*;
import sharpen.core.Configuration.*;
import sharpen.core.csharp.ast.*;
import sharpen.core.framework.*;
import static sharpen.core.framework.Environments.*;
public class MappingsImpl implements Mappings {
private final List<String> _namespaces = new ArrayList<String>();
private final Configuration _configuration = my(Configuration.class);
private final CSCompilationUnit _compilationUnit = my(CSCompilationUnit.class);
private final NameScope _nameScope = my(NameScope.class);
private final Annotations _annotations = my(Annotations.class);
private final Bindings _bindings = my(Bindings.class);
private final PreserveFullyQualifiedNamesState _preserveFQNState = my(PreserveFullyQualifiedNamesState.class);
private String _currentNamespace;
private Map<ITypeBinding, Map<IMethodBinding, String>> _constructorMethods = new HashMap<>();
private Map<String, List<IMethodBinding>> _overloadLookup = new HashMap<String, List<IMethodBinding>>();
private Map<IMethodBinding,String> _methodOverloads = new HashMap<>();
@Override
public String methodOverload(IMethodBinding binding, String name) {
// check if method already registered
if(_methodOverloads.containsKey(binding)) {
return _methodOverloads.get(binding);
}
String fqn = binding.getDeclaringClass().getQualifiedName() + "." + binding.getName();
// create overload lookup table if needed
List<IMethodBinding> overloads;
if(!_overloadLookup.containsKey(fqn)) {
overloads = new ArrayList<>();
_overloadLookup.put(fqn, overloads);
}
else {
overloads = _overloadLookup.get(fqn);
}
// add current method to registered overloads
overloads.add(binding);
// create method name for new overload
String methodName;
if(overloads.size() == 1) {
methodName = name;
}
else {
methodName = name + (overloads.size() - 1);
}
_methodOverloads.put(binding, methodName);
return methodName;
}
@Override
public String constructorMethod(ITypeBinding type, IMethodBinding ctor) {
// TODO: we will need to check how to handle ctor overloading if the base class is a built in type
/*if(!type.isFromSource()) {
return null;
}*/
if(!_constructorMethods.containsKey(type)) {
_constructorMethods.put(type, new HashMap<IMethodBinding, String>());
}
Map<IMethodBinding, String> names = _constructorMethods.get(type);
if(!names.containsKey(ctor)) {
names.put(ctor, "__ctor" + names.size());
}
return names.get(ctor);
}
public String mappedFieldName(IVariableBinding binding) {
if (!binding.isField())
return null;
Configuration.MemberMapping mapping = _configuration.mappedMember(BindingUtils.qualifiedName(binding));
return mapping != null
? mapping.name
: null;
}
public String mappedMethodName(IMethodBinding binding) {
Configuration.MemberMapping mapping = effectiveMappingFor(binding);
return computeMethodName(binding, mapping);
}
public String mappedTypeName(ITypeBinding type) {
if (type.isArray() || type.isWildcardType()) {
return type.getQualifiedName();
}
if (!hasMapping(type)) {
String annotatedRenaming = annotatedRenaming(type);
if (annotatedRenaming != null) {
return registerMappedType(type, fullyQualifyIfNeeded(annotatedRenaming, type));
}
if(type.isNested()) {
String fully = qualifiedName(type);
String namespace = namespace(namespace(fully));
String mappedNamespace = _configuration.mappedNamespace(namespace);
String name = fully.substring(fully.lastIndexOf('.'), fully.length());
return registerMappedType(type, mappedNamespace + "." + name);
}
}
String mappedTypeName = mappedTypeName(BindingUtils.typeMappingKey(type), qualifiedName(type));
if (mappedTypeName.length() == 0)
mappedTypeName = "_T" + Math.abs(type.getKey().hashCode());
if (shouldPrefixInterface(type)) {
return registerMappedType(type, mappedInterfaceName(mappedTypeName));
}
return registerMappedType(type, mappedTypeName);
}
private String fullyQualifyIfNeeded(String typeName, ITypeBinding type) {
if (isFullyQualified(typeName)) {
return typeName;
}
final String originalNamespace = namespace(qualifiedName(type));
final String mappedNamespace = _configuration.mappedNamespace(originalNamespace);
if (originalNamespace.equals(mappedNamespace)) {
return typeName;
}
return mappedNamespace + "." + typeName;
}
private String namespace(final String typeName) {
return substringBeforeLast(typeName, '.');
}
private String substringBeforeLast(String s, char marker) {
return s.substring(0, s.lastIndexOf(marker));
}
private boolean isFullyQualified(String typeName) {
return typeName.contains(".");
}
private String annotatedRenaming(ITypeBinding type) {
if (type.isTypeVariable()) return null;
final ASTNode node = findDeclaringNode(type);
AbstractTypeDeclaration typeDeclaration = node instanceof AbstractTypeDeclaration ? (AbstractTypeDeclaration) node: null;
return (typeDeclaration != null && isAnnotatedWith(typeDeclaration, SharpenAnnotations.SHARPEN_RENAME))
? annotatedRenaming(typeDeclaration)
: null;
}
private boolean shouldPrefixInterface(ITypeBinding type) {
return _configuration.nativeInterfaces()
&& type.isInterface()
&& !type.isAnnotation()
&& !hasMapping(type);
}
private String registerMappedType(ITypeBinding type, String fullName) {
if (_preserveFQNState.value())
return fullName;
if (!_configuration.organizeUsings())
return fullName;
int pos = fullName.lastIndexOf(".");
if (pos == -1)
return fullName;
if (!hasMapping(type)) {
pos = nameSpaceLength(type, fullName, pos);
}
String namespace = fullName.substring(0, pos);
registerNamespace(namespace);
String name = fullName.substring(pos + 1);
if (keepFullyQualified(name))
return fullName;
if(type.isNested()) {
}
if(!type.getPackage().getName().equals(_currentNamespace)) {
_compilationUnit.addUsing(new CSUsing(fullName));
}
return name;
}
private int nameSpaceLength(ITypeBinding type, String fullName, int pos) {
while (type.isNested()) {
pos = fullName.lastIndexOf(".", pos - 1);
type = type.getDeclaringClass();
}
return pos;
}
private boolean keepFullyQualified(String name) {
return _configuration.shouldFullyQualifyTypeName(name)
|| _namespaces.contains(name)
|| _nameScope.contains(name);
}
private void registerNamespace(String namespace) {
if (_namespaces.contains(namespace))
return;
int pos = namespace.lastIndexOf(".");
if (pos == -1) {
_namespaces.add(namespace);
return;
}
_namespaces.add(namespace.substring(pos + 1));
registerNamespace(namespace.substring(0, pos));
}
private boolean hasMapping(ITypeBinding type) {
if(type.isNested()) {
return true;
}
return _configuration.typeHasMapping(BindingUtils.typeMappingKey(type));
}
private String mappedInterfaceName(String name) {
int pos = name.lastIndexOf('.');
return name.substring(0, pos) + "." + interfaceName(name.substring(pos + 1));
}
private String interfaceName(String name) {
return _configuration.toInterfaceName(name);
}
public Configuration.MemberMapping effectiveMappingFor(final IMethodBinding binding) {
final MemberMapping mapping = configuredMappingFor(binding);
if (null != mapping)
return mapping;
boolean declaringClassIgnoresExtends = isDeclaringClassIgnoringExtends(binding);
//TODO: Always check method in current class first (irrespective to sharpen.ignore.implements/extends)?
//TODO: Check ignore implements also
MethodDeclaration method = (MethodDeclaration) (declaringClassIgnoresExtends
? findDeclaringNode(binding)
: findDeclaringNode(originalMethodBinding(binding))) ;
if (method == null)
return null;
if (isAnnotatedWith(method, SharpenAnnotations.SHARPEN_EVENT))
return new MemberMapping(binding.getName(), MemberKind.Property);
if (isAnnotatedWith(method, SharpenAnnotations.SHARPEN_PROPERTY))
return new MemberMapping(annotatedPropertyName(method), MemberKind.Property);
if (isAnnotatedWith(method, SharpenAnnotations.SHARPEN_RENAME))
return new MemberMapping(annotatedRenaming(method), MemberKind.Method);
//TODO: Check originalMethodBinding if declaringClassIgnoresExtends == true
// and we reach this point?
return null;
}
private boolean isDeclaringClassIgnoringExtends(final IMethodBinding binding) {
ITypeBinding declaringClassBinding = binding.getDeclaringClass();
if (declaringClassBinding.isAnonymous()) return false;
try {
AbstractTypeDeclaration declaringClass = findDeclaringNode(declaringClassBinding);
return declaringClass == null
? false
: isAnnotatedWith(declaringClass, SharpenAnnotations.SHARPEN_IGNORE_EXTENDS);
}
catch(Throwable e) {
return false;
}
}
private String annotatedRenaming(BodyDeclaration method) {
return _annotations.annotatedRenaming(method);
}
private String annotatedPropertyName(MethodDeclaration node) {
return _annotations.annotatedPropertyName(node);
}
private <T extends ASTNode> T findDeclaringNode(IBinding binding) {
return (T) _bindings.findDeclaringNode(binding);
}
private boolean isAnnotatedWith(final BodyDeclaration node, final String annotation) {
return JavadocUtility.containsJavadoc(node, annotation);
}
private Configuration.MemberMapping configuredMappingFor(final IMethodBinding binding) {
final IMethodBinding actual = originalMethodBinding(binding);
final MemberMapping mapping = _configuration.mappedMember(BindingUtils.qualifiedSignature(actual));
if (null != mapping)
return mapping;
return _configuration.mappedMember(qualifiedName(actual));
}
private String qualifiedName(IMethodBinding actual) {
return BindingUtils.qualifiedName(actual);
}
private String qualifiedName(ITypeBinding type) {
return BindingUtils.qualifiedName(type);
}
private String computeMethodName(IMethodBinding binding, Configuration.MemberMapping mapping) {
if (isStaticVoidMain(binding))
return "main";
String name = isNameMapping(mapping)
? mapping.name
: binding.getName();
return methodName(name);
}
private boolean isStaticVoidMain(IMethodBinding binding) {
return isStatic(binding) && "main".equals(binding.getName());
}
private boolean isStatic(IMethodBinding binding) {
return Modifier.isStatic(binding.getModifiers());
}
private boolean isNameMapping(Configuration.MemberMapping mapping) {
return null != mapping && null != mapping.name;
}
private String methodName(String name) {
return _configuration.getNamingStrategy().methodName(name);
}
private IMethodBinding originalMethodBinding(IMethodBinding binding) {
return _bindings.originalBindingFor(binding);
}
private String mappedTypeName(String typeName, String defaultValue) {
return _configuration.mappedTypeName(typeName, defaultValue);
}
@Override
public String currentNamespace() {
return _currentNamespace;
}
@Override
public void currentNamespace(String currentNamespace) {
_currentNamespace = currentNamespace;
}
}