sharpen.core.CSharpBuilder.java Source code

Java tutorial

Introduction

Here is the source code for sharpen.core.CSharpBuilder.java

Source

/* Copyright (C) 2004 - 2008  Versant Inc.  http://www.db4o.com
    
This file is part of the sharpen open source java to c# translator.
    
sharpen is free software; you can redistribute it and/or modify it under
the terms of version 2 of the GNU General Public License as published
by the Free Software Foundation and as clarified by db4objects' GPL 
interpretation policy, available at
http://www.db4o.com/about/company/legalpolicies/gplinterpretation/
Alternatively you can write to db4objects, Inc., 1900 S Norfolk Street,
Suite 350, San Mateo, CA 94403, USA.
    
sharpen is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
for more details.
    
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. */

package sharpen.core;

import java.security.DomainCombiner;
import java.util.*;
import java.util.regex.*;

import org.eclipse.jdt.core.*;
import org.eclipse.jdt.core.dom.*;

import sharpen.core.Configuration.*;
import sharpen.core.csharp.ast.*;
import sharpen.core.framework.*;
import static sharpen.core.framework.StaticImports.*;

import static sharpen.core.framework.Environments.*;

public class CSharpBuilder extends ASTVisitor {

    private static final String JAVA_LANG_VOID_TYPE = "java.lang.Void.TYPE";

    private static final String JAVA_LANG_BOOLEAN_TYPE = "java.lang.Boolean.TYPE";

    private static final String JAVA_LANG_CHARACTER_TYPE = "java.lang.Character.TYPE";

    private static final String JAVA_LANG_INTEGER_TYPE = "java.lang.Integer.TYPE";

    private static final String JAVA_LANG_LONG_TYPE = "java.lang.Long.TYPE";

    private static final String JAVA_LANG_BYTE_TYPE = "java.lang.Byte.TYPE";

    private static final String JAVA_LANG_SHORT_TYPE = "java.lang.Short.TYPE";

    private static final String JAVA_LANG_FLOAT_TYPE = "java.lang.Float.TYPE";

    private static final String JAVA_LANG_DOUBLE_TYPE = "java.lang.Double.TYPE";

    private static final CSTypeReference OBJECT_TYPE_REFERENCE = new CSTypeReference("object");

    private final CSCompilationUnit _compilationUnit;

    protected CSTypeDeclaration _currentType;

    private CSBlock _currentBlock;

    private CSExpression _currentExpression;

    protected CSMethodBase _currentMethod;

    protected BodyDeclaration _currentBodyDeclaration;

    private CSLabelStatement _currentContinueLabel;

    private static final Pattern SUMMARY_CLOSURE_PATTERN = Pattern.compile("\\.(\\s|$)");

    private static final Pattern HTML_ANCHOR_PATTERN = Pattern.compile("<([aA])\\s+.+>");

    protected CompilationUnit _ast;

    protected Configuration _configuration;

    private ASTResolver _resolver;

    private IVariableBinding _currentExceptionVariable;

    private final DynamicVariable<Boolean> _ignoreExtends = new DynamicVariable<Boolean>(Boolean.FALSE);

    private List<Initializer> _instanceInitializers = new ArrayList<Initializer>();

    private Stack<Set<String>> _blockVariables = new Stack<Set<String>>();
    private Stack<Set<String>> _localBlockVariables = new Stack<Set<String>>();
    private Stack<HashMap<String, String>> _renamedVariables = new Stack<HashMap<String, String>>();

    private ITypeBinding _currentExpectedType;

    protected NamingStrategy namingStrategy() {
        return _configuration.getNamingStrategy();
    }

    protected WarningHandler warningHandler() {
        return _configuration.getWarningHandler();
    }

    public CSharpBuilder() {
        _configuration = my(Configuration.class);
        _ast = my(CompilationUnit.class);
        _resolver = my(ASTResolver.class);
        _compilationUnit = my(CSCompilationUnit.class);
        _compilationUnit.addUsing(new CSUsing("Sharpen"));
    }

    protected CSharpBuilder(CSharpBuilder other) {
        _configuration = other._configuration;
        _ast = other._ast;
        _resolver = other._resolver;
        _compilationUnit = other._compilationUnit;

        _currentType = other._currentType;
        _currentBlock = other._currentBlock;
        _currentExpression = other._currentExpression;
        _currentMethod = other._currentMethod;
        _currentBodyDeclaration = other._currentBodyDeclaration;
    }

    public void setSourceCompilationUnit(CompilationUnit ast) {
        _ast = ast;
    }

    public void run() {
        if (null == warningHandler() || null == _ast) {
            throw new IllegalStateException();
        }
        _ast.accept(this);
        visit(_ast.getCommentList());
    }

    @Override
    public boolean visit(LineComment node) {
        _compilationUnit.addComment(
                new CSLineComment(node.getStartPosition(), getText(node.getStartPosition(), node.getLength())));
        return false;
    }

    private String getText(int startPosition, int length) {
        try {
            return ((ICompilationUnit) _ast.getJavaElement()).getBuffer().getText(startPosition, length);
        } catch (JavaModelException e) {
            throw new RuntimeException(e);
        }
    }

    public CSCompilationUnit compilationUnit() {
        return _compilationUnit;
    }

    public boolean visit(ImportDeclaration node) {
        return false;
    }

    public boolean visit(EnumDeclaration node) {
        if (!SharpenAnnotations.hasIgnoreAnnotation(node)) {
            if (node.bodyDeclarations().isEmpty()) {
                return convSimpleEnum(node);
            } else {
                new CSharpBuilder(CSharpBuilder.this).processTypeDeclaration(node);
                return false;
            }
        }
        return false;
    }

    private boolean convSimpleEnum(EnumDeclaration node) {
        final CSEnum theEnum = new CSEnum(typeName(node));
        mapVisibility(node, theEnum);
        mapJavadoc(node, theEnum);
        addType(node.resolveBinding(), theEnum);

        node.accept(new ASTVisitor() {
            public boolean visit(EnumConstantDeclaration node) {
                theEnum.addValue(identifier(node.getName()));
                return false;
            }

            @Override
            public boolean visit(MethodDeclaration node) {
                if (node.isConstructor() && isPrivate(node)) {
                    return false;
                }
                unsupportedConstruct(node, "Enum can contain only fields and a private constructor.");
                return false;
            }
        });
        return false;
    }

    @Override
    public boolean visit(AnnotationTypeDeclaration node) {
        // TODO: SHA-51
        return false;
    }

    @Override
    public boolean visit(MarkerAnnotation node) {
        // TODO: SHA-51
        return false;
    }

    @Override
    public boolean visit(NormalAnnotation node) {
        // TODO: SHA-51
        return false;
    }

    public boolean visit(final LabeledStatement node) {
        String identifier = node.getLabel().getIdentifier();
        _currentContinueLabel = new CSLabelStatement(continueLabel(identifier));
        try {
            node.getBody().accept(this);
        } finally {
            _currentContinueLabel = null;
        }
        addStatement(new CSLabelStatement(breakLabel(identifier)));
        return false;
    }

    private String breakLabel(String identifier) {
        return identifier + "_break";
    }

    private String continueLabel(String identifier) {
        return identifier + "_continue";
    }

    public boolean visit(SuperFieldAccess node) {
        notImplemented(node);
        return false;
    }

    public boolean visit(MemberRef node) {
        notImplemented(node);
        return false;
    }

    @Override
    public boolean visit(WildcardType node) {
        notImplemented(node);
        return false;
    }

    private void notImplemented(ASTNode node) {
        throw new IllegalArgumentException(sourceInformation(node) + ": " + node.toString());
    }

    public boolean visit(PackageDeclaration node) {
        String namespace = node.getName().toString();
        _compilationUnit.namespace(mappedNamespace(namespace));

        processDisableTags(node, _compilationUnit);
        return false;
    }

    public boolean visit(AnonymousClassDeclaration node) {
        CSAnonymousClassBuilder builder = mapAnonymousClass(node);
        pushExpression(builder.createConstructorInvocation());
        return false;
    }

    private CSAnonymousClassBuilder mapAnonymousClass(AnonymousClassDeclaration node) {
        CSAnonymousClassBuilder builder = new CSAnonymousClassBuilder(this, node);
        _currentType.addMember(builder.type());
        return builder;
    }

    public boolean visit(final TypeDeclaration node) {

        if (processIgnoredType(node)) {
            return false;
        }

        if (processEnumType(node)) {
            return false;
        }

        try {
            my(NameScope.class).enterTypeDeclaration(node);

            _ignoreExtends.using(ignoreExtends(node), new Runnable() {
                public void run() {

                    final ITypeBinding binding = node.resolveBinding();

                    if (processInterfaceWithFields(node, binding)) {
                        return;
                    }

                    processMappableTypeDeclaration(node, binding);
                }
            });
        } finally {
            my(NameScope.class).leaveTypeDeclaration(node);
        }

        return false;
    }

    private void processMappableTypeDeclaration(final TypeDeclaration node, final ITypeBinding binding) {
        if (!binding.isNested()) {
            processTypeDeclaration(node);
            return;
        }

        if (isNonStaticNestedType(binding)) {
            processNonStaticNestedTypeDeclaration(node);
            return;
        }

        new CSharpBuilder(CSharpBuilder.this).processTypeDeclaration(node);
    }

    private boolean processInterfaceWithFields(final TypeDeclaration node, final ITypeBinding binding) {
        if (binding.isInterface()) {
            IVariableBinding[] declaredFields = binding.getDeclaredFields();
            if (declaredFields.length > 0 || binding.getDeclaredTypes().length > 0) {
                String name = typeName(node);
                CSClass constantsType = new CSClass(interfaceStaticsClassName(name, true, binding),
                        CSClassModifier.Static);
                addType(binding, constantsType);
                constantsType.startPosition(node.getStartPosition());
                constantsType.sourceLength(node.getLength());
                mapTypeParameters(node.typeParameters(), constantsType);

                mapVisibility(node, constantsType);
                adjustVisibility(binding.getSuperclass(), constantsType);
                mapDocumentation(node, constantsType);
                processConversionJavadocTags(node, constantsType);

                CSTypeDeclaration saved = _currentType;
                _currentType = constantsType;
                try {
                    for (Object memberNodeObj : node.bodyDeclarations()) {
                        ASTNode memberNode = (ASTNode) memberNodeObj;
                        if (memberNode.getNodeType() != ASTNode.METHOD_DECLARATION) {
                            memberNode.accept(this);
                        }
                    }
                    //flushInstanceInitializers(type, 0);
                } finally {
                    _currentType = saved;
                }

                if (binding.getDeclaredMethods().length != 0 || !name.equals(constantsType.name())) {
                    processMappableTypeDeclaration(node, binding);
                }

                return true;
            }
        }
        return false;
    }

    private boolean processEnumType(TypeDeclaration node) {
        if (!isEnum(node)) {
            return false;
        }
        final CSEnum theEnum = new CSEnum(typeName(node));
        mapVisibility(node, theEnum);
        mapJavadoc(node, theEnum);
        addType(node.resolveBinding(), theEnum);

        node.accept(new ASTVisitor() {
            public boolean visit(VariableDeclarationFragment node) {
                theEnum.addValue(identifier(node.getName()));
                return false;
            }

            @Override
            public boolean visit(MethodDeclaration node) {
                if (node.isConstructor() && isPrivate(node)) {
                    return false;
                }
                unsupportedConstruct(node, "Enum can contain only fields and a private constructor.");
                return false;
            }
        });
        return true;
    }

    protected boolean isPrivate(MethodDeclaration node) {
        return Modifier.isPrivate(node.getModifiers());
    }

    private boolean isEnum(TypeDeclaration node) {
        return containsJavadoc(node, SharpenAnnotations.SHARPEN_ENUM);
    }

    private boolean processIgnoredType(TypeDeclaration node) {
        if (!hasIgnoreOrRemoveAnnotation(node)) {
            return false;
        }
        if (isMainType(node)) {
            compilationUnit().ignore(true);
        }
        return true;
    }

    private boolean hasIgnoreOrRemoveAnnotation(TypeDeclaration node) {
        return SharpenAnnotations.hasIgnoreAnnotation(node) || hasRemoveAnnotation(node);
    }

    private void processNonStaticNestedTypeDeclaration(TypeDeclaration node) {
        new NonStaticNestedClassBuilder(this, node);
    }

    protected CSTypeDeclaration processTypeDeclaration(AbstractTypeDeclaration node) {
        CSTypeDeclaration type = mapTypeDeclaration(node);

        processDisabledType(node, isMainType(node) ? _compilationUnit : type);

        if (_configuration.shouldMakePartial(node.getName().getFullyQualifiedName()))
            type.partial(true);

        ITypeBinding typeBinding = node.resolveBinding();
        addType(typeBinding, type);

        TypeDeclaration tNode = node instanceof TypeDeclaration ? (TypeDeclaration) node : null;

        if (tNode != null)
            mapSuperTypes(tNode, type);

        mapVisibility(node, type);
        adjustVisibility(typeBinding.getSuperclass(), type);
        mapDocumentation(node, type);
        if (tNode != null)
            processConversionJavadocTags(tNode, type);

        if (node instanceof EnumDeclaration) {
            EnumDeclaration eNode = (EnumDeclaration) node;
            CSTypeReferenceExpression typeName = mappedTypeReference(typeBinding);
            for (Object item : eNode.enumConstants()) {
                EnumConstantDeclaration ecd = (EnumConstantDeclaration) item;
                CSMethodInvocationExpression initializer = mapEnumInitializer(ecd, typeName);
                mapArguments(initializer, ecd.arguments());
                CSField field = new CSField(identifier(ecd.getName()), typeName, CSVisibility.Public, initializer);
                field.addModifier(CSFieldModifier.Static);
                field.addModifier(CSFieldModifier.Readonly);
                type.addMember(field);
            }
        }

        mapMembers(node, type);

        if (node instanceof EnumDeclaration) {
            EnumDeclaration eNode = (EnumDeclaration) node;
            CSTypeReferenceExpression typeName = mappedTypeReference(typeBinding);

            CSArrayCreationExpression newArrayExpr = new CSArrayCreationExpression(typeName);
            CSArrayInitializerExpression initializer = new CSArrayInitializerExpression();
            for (Object item : eNode.enumConstants()) {
                EnumConstantDeclaration ecd = (EnumConstantDeclaration) item;
                initializer.addExpression(new CSReferenceExpression(identifier(ecd.getName())));
            }
            newArrayExpr.initializer(initializer);

            CSReturnStatement returnStatment = new CSReturnStatement(0, newArrayExpr);

            CSMethod valuesMethod = new CSMethod(methodName("values"));
            valuesMethod.modifier(CSMethodModifier.Static);
            valuesMethod.visibility(CSVisibility.Public);
            valuesMethod.returnType(new CSArrayTypeReference(typeName, 1));
            valuesMethod.body().addStatement(returnStatment);
            type.addMember(valuesMethod);
        }

        if (tNode != null)
            autoImplementCloneable(tNode, type);

        moveInitializersDependingOnThisReferenceToConstructor(type);

        if (_configuration.junitConversion() && hasTests(type))
            type.addAttribute(new CSAttribute("NUnit.Framework.TestFixture"));

        return type;
    }

    private CSMethodInvocationExpression mapEnumInitializer(EnumConstantDeclaration node,
            CSTypeReferenceExpression typeName) {
        Configuration.MemberMapping mappedConstructor = effectiveMappingFor(node.resolveConstructorBinding());
        if (null == mappedConstructor) {
            return new CSConstructorInvocationExpression(typeName);
        }
        final String mappedName = mappedConstructor.name;
        if (mappedName.length() == 0) {
            pushExpression(mapExpression((Expression) node.arguments().get(0)));
            return null;
        }
        if (mappedName.startsWith("System.Convert.To")) {
            if (optimizeSystemConvert(mappedName, node)) {
                return null;
            }
        }
        return new CSMethodInvocationExpression(new CSReferenceExpression(methodName(mappedName)));
    }

    private boolean optimizeSystemConvert(String mappedConstructor, EnumConstantDeclaration node) {
        String typeName = _configuration.getConvertRelatedWellKnownTypeName(mappedConstructor);
        if (null != typeName) {
            assert 1 == node.arguments().size();
            Expression arg = (Expression) node.arguments().get(0);
            if (arg.resolveTypeBinding() == resolveWellKnownType(typeName)) {
                arg.accept(this);
                return true;
            }
        }
        return false;
    }

    private void processDisabledType(AbstractTypeDeclaration node, CSNode type) {
        final String expression = _configuration.conditionalCompilationExpressionFor(packageNameFor(node));
        if (null != expression) {
            compilationUnit().addEnclosingIfDef(expression);
        }

        processDisableTags(node, type);
    }

    private String packageNameFor(AbstractTypeDeclaration node) {
        ITypeBinding type = node.resolveBinding();
        return type.getPackage().getName();
    }

    protected void flushInstanceInitializers(CSTypeDeclaration type, int startStatementIndex) {

        if (_instanceInitializers.isEmpty()) {
            return;
        }

        ensureConstructorsFor(type);

        int initializerIndex = startStatementIndex;
        for (Initializer node : _instanceInitializers) {
            final CSBlock body = mapInitializer(node);

            for (CSConstructor ctor : type.constructors()) {
                if (ctor.isStatic() || hasChainedThisInvocation(ctor)) {
                    continue;
                }
                ctor.body().addStatement(initializerIndex, body);
            }

            ++initializerIndex;
        }

        _instanceInitializers.clear();
    }

    private CSBlock mapInitializer(Initializer node) {
        final CSConstructor template = new CSConstructor();
        visitBodyDeclarationBlock(node, node.getBody(), template);
        final CSBlock body = template.body();
        return body;
    }

    private boolean hasChainedThisInvocation(CSConstructor ctor) {
        final CSConstructorInvocationExpression chained = ctor.chainedConstructorInvocation();
        return chained != null && chained.expression() instanceof CSThisExpression;
    }

    private void moveInitializersDependingOnThisReferenceToConstructor(CSTypeDeclaration type) {

        final HashSet<String> memberNames = memberNameSetFor(type);

        int index = 0;
        for (CSMember member : copy(type.members())) {
            if (!(member instanceof CSField))
                continue;

            final CSField field = (CSField) member;
            if (!isDependentOnThisOrMember(field, memberNames))
                continue;

            moveFieldInitializerToConstructors(field, type, index++);
        }
    }

    private HashSet<String> memberNameSetFor(CSTypeDeclaration type) {
        final HashSet<String> members = new HashSet<String>();
        for (CSMember member : type.members()) {
            if (member instanceof CSType)
                continue;
            if (isStatic(member))
                continue;
            members.add(member.name());
        }
        return members;
    }

    private boolean isStatic(CSMember member) {
        if (member instanceof CSField)
            return isStatic((CSField) member);
        if (member instanceof CSMethod)
            return isStatic((CSMethod) member);
        return false;
    }

    private boolean isStatic(CSMethod method) {
        return method.modifier() == CSMethodModifier.Static;
    }

    private boolean isStatic(CSField member) {
        final Set<CSFieldModifier> fieldModifiers = member.modifiers();
        return fieldModifiers.contains(CSFieldModifier.Static) || fieldModifiers.contains(CSFieldModifier.Const);
    }

    private CSMember[] copy(final List<CSMember> list) {
        return list.toArray(new CSMember[0]);
    }

    private boolean isDependentOnThisOrMember(CSField field, final Set<String> fields) {
        if (null == field.initializer())
            return false;

        final ByRef<Boolean> foundThisReference = new ByRef<Boolean>(false);
        field.initializer().accept(new CSExpressionVisitor() {
            @Override
            public void visit(CSThisExpression node) {
                foundThisReference.value = true;
            }

            @Override
            public void visit(CSReferenceExpression node) {
                if (fields.contains(node.name())) {
                    foundThisReference.value = true;
                }
            }
        });
        return foundThisReference.value;
    }

    private void moveFieldInitializerToConstructors(CSField field, CSTypeDeclaration type, int index) {
        final CSExpression initializer = field.initializer();
        for (CSConstructor ctor : ensureConstructorsFor(type)) {
            if (ctor.isStatic() || hasChainedThisInvocation(ctor))
                continue;
            ctor.body().addStatement(index, newAssignment(field, initializer));
        }
        field.initializer(null);
    }

    private CSExpression newAssignment(CSField field, final CSExpression initializer) {
        return CSharpCode.newAssignment(CSharpCode.newReference(field), initializer);
    }

    private Iterable<CSConstructor> ensureConstructorsFor(CSTypeDeclaration type) {
        final List<CSConstructor> ctors = type.constructors();
        if (!ctors.isEmpty()) {
            for (CSConstructor ctor : ctors) {
                if (!ctor.isStatic())
                    return ctors;
            }

            addDefaultConstructor(type);

            return type.constructors();
        }
        return Collections.singletonList(addDefaultConstructor(type));
    }

    private CSConstructor addDefaultConstructor(CSTypeDeclaration type) {
        final CSConstructor ctor = CSharpCode.newPublicConstructor();
        type.addMember(ctor);
        return ctor;
    }

    private void autoImplementCloneable(TypeDeclaration node, CSTypeDeclaration type) {

        if (!implementsCloneable(type)) {
            return;
        }

        CSMethod clone = new CSMethod("System.ICloneable.Clone");
        clone.returnType(OBJECT_TYPE_REFERENCE);
        clone.body().addStatement(new CSReturnStatement(-1,
                new CSMethodInvocationExpression(new CSReferenceExpression("MemberwiseClone"))));

        type.addMember(clone);
    }

    private boolean implementsCloneable(CSTypeDeclaration node) {
        for (CSTypeReferenceExpression typeRef : node.baseTypes()) {
            if (typeRef.toString().equals("System.ICloneable")) {
                return true;
            }
        }
        return false;
    }

    private void mapSuperTypes(TypeDeclaration node, CSTypeDeclaration type) {
        if (!_ignoreExtends.value()) {
            mapSuperClass(node, type);
        }
        if (!ignoreImplements(node)) {
            mapSuperInterfaces(node, type);
        }
    }

    private boolean ignoreImplements(TypeDeclaration node) {
        return containsJavadoc(node, SharpenAnnotations.SHARPEN_IGNORE_IMPLEMENTS);
    }

    private boolean ignoreExtends(TypeDeclaration node) {
        return containsJavadoc(node, SharpenAnnotations.SHARPEN_IGNORE_EXTENDS);
    }

    private void processConversionJavadocTags(TypeDeclaration node, CSTypeDeclaration type) {
        processPartialTagElement(node, type);
    }

    private CSTypeDeclaration mapTypeDeclaration(AbstractTypeDeclaration node) {
        CSTypeDeclaration type = typeDeclarationFor(node);
        type.startPosition(node.getStartPosition());
        type.sourceLength(node.getLength());
        if (node instanceof TypeDeclaration)
            mapTypeParameters(((TypeDeclaration) node).typeParameters(), type);
        return checkForMainType(node, type);
    }

    private void mapTypeParameters(final List typeParameters, CSTypeParameterProvider type) {
        for (Object item : typeParameters) {
            type.addTypeParameter(mapTypeParameter((TypeParameter) item));
        }
    }

    private CSTypeParameter mapTypeParameter(TypeParameter item) {
        CSTypeParameter tp = new CSTypeParameter(identifier(item.getName()));
        ITypeBinding tb = item.resolveBinding();
        if (tb != null) {
            ITypeBinding superc = mapTypeParameterExtendedType(tb);
            if (superc != null)
                tp.superClass(mappedTypeReference(superc));
        }
        return tp;
    }

    private CSTypeDeclaration typeDeclarationFor(AbstractTypeDeclaration node) {
        final String typeName = typeName(node);

        if (node instanceof EnumDeclaration)
            return new CSClass(typeName, CSClassModifier.None);

        TypeDeclaration tNode = (TypeDeclaration) node;
        if (tNode.isInterface()) {
            if (isValidCSInterface(tNode.resolveBinding()))
                return new CSInterface(typeName(tNode, true));
            else
                return new CSClass(typeName, CSClassModifier.Abstract);
        }

        if (isStruct(tNode)) {
            return new CSStruct(typeName);
        }
        return new CSClass(typeName, mapClassModifier(tNode.getModifiers()));
    }

    private String typeName(AbstractTypeDeclaration node) {
        return typeName(node, false);
    }

    private String typeName(AbstractTypeDeclaration node, boolean isInterface) {
        String renamed = annotatedRenaming(node);
        if (renamed != null)
            return renamed;
        renamed = mappedTypeName(node.resolveBinding());
        if (renamed != null) {
            int i = renamed.lastIndexOf('.');
            if (i != -1)
                return renamed.substring(i + 1);
            else
                return renamed;
        }
        renamed = node.getName().toString();
        if (isInterface)
            renamed = interfaceName(renamed);
        return renamed;
    }

    private boolean isStruct(TypeDeclaration node) {
        return containsJavadoc(node, SharpenAnnotations.SHARPEN_STRUCT);
    }

    private CSTypeDeclaration checkForMainType(AbstractTypeDeclaration node, CSTypeDeclaration type) {
        if (isMainType(node)) {
            setCompilationUnitElementName(type.name());
        }
        return type;
    }

    private void setCompilationUnitElementName(String name) {
        _compilationUnit.elementName(name + ".cs");
    }

    private boolean isMainType(AbstractTypeDeclaration node) {
        return node.isPackageMemberTypeDeclaration() && Modifier.isPublic(node.getModifiers());
    }

    private void mapSuperClass(TypeDeclaration node, CSTypeDeclaration type) {
        if (handledExtends(node, type))
            return;

        if (null == node.getSuperclassType())
            return;

        final ITypeBinding superClassBinding = node.getSuperclassType().resolveBinding();
        if (null == superClassBinding)
            unresolvedTypeBinding(node.getSuperclassType());

        if (!isLegacyTestFixtureClass(superClassBinding))
            type.addBaseType(mappedTypeReference(superClassBinding));
        else {
            type.addAttribute(new CSAttribute("NUnit.Framework.TestFixture"));
        }
    }

    private boolean isLegacyTestFixtureClass(ITypeBinding type) {
        return (_configuration.junitConversion() && type.getQualifiedName().equals("junit.framework.TestCase"));
    }

    private boolean isLegacyTestFixture(ITypeBinding type) {
        if (!_configuration.junitConversion())
            return false;
        if (isLegacyTestFixtureClass(type))
            return true;
        ITypeBinding base = type.getSuperclass();
        return (base != null) && isLegacyTestFixture(base);
    }

    private boolean hasTests(CSTypeDeclaration type) {
        for (CSMember m : type.members()) {
            if (m instanceof CSMethod) {
                CSMethod met = (CSMethod) m;
                for (CSAttribute at : met.attributes()) {
                    if (at.name().equals("Test") || at.name().equals("NUnit.Framework.Test"))
                        return true;
                }
            }
        }
        return false;
    }

    private boolean handledExtends(TypeDeclaration node, CSTypeDeclaration type) {
        final TagElement replaceExtendsTag = javadocTagFor(node, SharpenAnnotations.SHARPEN_EXTENDS);
        if (null == replaceExtendsTag)
            return false;

        final String baseType = JavadocUtility.singleTextFragmentFrom(replaceExtendsTag);
        type.addBaseType(new CSTypeReference(baseType));
        return true;
    }

    private void mapSuperInterfaces(TypeDeclaration node, CSTypeDeclaration type) {
        final ITypeBinding serializable = resolveWellKnownType("java.io.Serializable");
        for (Object itf : node.superInterfaceTypes()) {
            Type iface = (Type) itf;
            ITypeBinding binding = iface.resolveBinding();
            if (binding == serializable) {
                continue;
            }

            if (binding != null) {
                String identifier = binding.getName();
                if (identifier.charAt(0) != 'I' && identifier.endsWith("Constants")
                        && binding.getDeclaredMethods().length == 0) {
                    continue;
                }
            }

            type.addBaseType(mappedTypeReference(iface));
        }

        if (!type.isInterface() && node.resolveBinding().isSubTypeCompatible(serializable)) {
            type.addAttribute(new CSAttribute("System.Serializable"));
        }
    }

    private ITypeBinding resolveWellKnownType(String typeName) {
        return _ast.getAST().resolveWellKnownType(typeName);
    }

    private void mapMembers(AbstractTypeDeclaration node, CSTypeDeclaration type) {
        CSTypeDeclaration saved = _currentType;
        _currentType = type;
        try {
            if (type.isInterface()) {
                for (Object obj : node.bodyDeclarations()) {
                    ASTNode astNode = (ASTNode) obj;
                    if (astNode.getNodeType() == ASTNode.METHOD_DECLARATION)
                        astNode.accept(this);
                }
            } else {
                visit(node.bodyDeclarations());
                if (node instanceof TypeDeclaration)
                    createInheritedAbstractMemberStubs((TypeDeclaration) node);
                flushInstanceInitializers(type, 0);
            }
        } finally {
            _currentType = saved;
        }
    }

    private void mapVisibility(BodyDeclaration node, CSMember member) {
        member.visibility(mapVisibility(node));
    }

    protected boolean isNonStaticNestedType(ITypeBinding binding) {
        if (binding.isInterface())
            return false;
        if (!binding.isNested())
            return false;
        return !isStatic(binding);
    }

    private boolean isStatic(ITypeBinding binding) {
        return Modifier.isStatic(binding.getModifiers());
    }

    private void addType(ITypeBinding binding, CSType type) {
        if (null != _currentType && !isExtractedNestedType(binding)) {
            _currentType.addMember(type);
        } else {
            _compilationUnit.addType(type);
        }
    }

    private void mapDocumentation(final BodyDeclaration bodyDecl, final CSMember member) {
        my(PreserveFullyQualifiedNamesState.class).using(true, new Runnable() {
            public void run() {
                if (processDocumentationOverlay(member)) {
                    return;
                }

                mapJavadoc(bodyDecl, member);
                mapDeclaredExceptions(bodyDecl, member);

            }
        });
    }

    private void mapDeclaredExceptions(BodyDeclaration bodyDecl, CSMember member) {
        if (!(bodyDecl instanceof MethodDeclaration))
            return;

        MethodDeclaration method = (MethodDeclaration) bodyDecl;
        mapThrownExceptions(method.thrownExceptions(), member);
    }

    private void mapThrownExceptions(List thrownExceptions, CSMember member) {
        for (Object exception : thrownExceptions) {
            mapThrownException((Name) exception, member);
        }
    }

    private void mapThrownException(Name exception, CSMember member) {
        final String typeName = mappedTypeName(exception.resolveTypeBinding());
        if (containsExceptionTagWithCRef(member, typeName))
            return;

        member.addDoc(newTagWithCRef("exception", typeName));
    }

    private boolean containsExceptionTagWithCRef(CSMember member, String cref) {
        for (CSDocNode node : member.docs()) {
            if (!(node instanceof CSDocTagNode))
                continue;

            if (cref.equals(((CSDocTagNode) node).getAttribute("cref"))) {
                return true;
            }
        }
        return false;
    }

    private void mapJavadoc(final BodyDeclaration bodyDecl, final CSMember member) {
        final Javadoc javadoc = bodyDecl.getJavadoc();
        if (null == javadoc) {
            return;
        }

        mapJavadocTags(javadoc, member);
    }

    private boolean processDocumentationOverlay(CSMember node) {
        if (node instanceof CSTypeDeclaration) {
            return processTypeDocumentationOverlay((CSTypeDeclaration) node);
        }
        return processMemberDocumentationOverlay((CSMember) node);
    }

    private boolean processMemberDocumentationOverlay(CSMember node) {
        String overlay = documentationOverlay().forMember(currentTypeQName(), node.signature());
        return processDocumentationOverlay(node, overlay);
    }

    private String currentTypeQName() {
        return qualifiedName(_currentType);
    }

    private boolean processTypeDocumentationOverlay(CSTypeDeclaration node) {
        String overlay = documentationOverlay().forType(qualifiedName(node));
        return processDocumentationOverlay(node, overlay);
    }

    private boolean processDocumentationOverlay(CSMember node, String overlay) {
        if (null == overlay) {
            return false;
        }
        node.addDoc(new CSDocTextOverlay(overlay.trim()));
        return true;
    }

    private DocumentationOverlay documentationOverlay() {
        return _configuration.documentationOverlay();
    }

    private String qualifiedName(CSTypeDeclaration node) {
        if (currentNamespace() == null) {
            return node.name();
        }
        return currentNamespace() + "." + node.name();
    }

    private String currentNamespace() {
        return _compilationUnit.namespace();
    }

    private void mapJavadocTags(final Javadoc javadoc, final CSMember member) {
        for (Object tag : javadoc.tags()) {
            try {
                TagElement element = (TagElement) tag;
                String tagName = element.getTagName();
                if (null == tagName) {
                    mapJavadocSummary(member, element);
                } else {
                    processTagElement(member, element);
                }
            } catch (Exception x) {
                warning((ASTNode) tag, x.getMessage());
                x.printStackTrace();
            }
        }
    }

    private void processTagElement(final CSMember member, TagElement element) {
        if (processSemanticallySignificantTagElement(member, element)) {
            return;
        }
        if (!isConversionTag(element.getTagName())) {
            member.addDoc(mapTagElement(element));
        } else if (isAttributeAnnotation(element)) {
            processAttribute(member, element);
        } else if (isNewAnnotation(element)) {
            member.setNewModifier(true);
        }
    }

    private boolean isAttributeAnnotation(TagElement element) {
        return element.getTagName().equals(SharpenAnnotations.SHARPEN_ATTRIBUTE);
    }

    private boolean isNewAnnotation(TagElement element) {
        return element.getTagName().equals(SharpenAnnotations.SHARPEN_NEW);
    }

    private void processAttribute(CSMember member, TagElement element) {
        String attrType = mappedTypeName(JavadocUtility.singleTextFragmentFrom(element));
        CSAttribute attribute = new CSAttribute(attrType);
        member.addAttribute(attribute);
    }

    private boolean processSemanticallySignificantTagElement(CSMember member, TagElement element) {
        if (element.getTagName().equals("@deprecated")) {
            member.removeAttribute("System.Obsolete");
            member.removeAttribute("System.ObsoleteAttribute");
            member.addAttribute(obsoleteAttributeFromDeprecatedTagElement(element));
            return true;
        }
        return false;
    }

    private CSAttribute obsoleteAttributeFromDeprecatedTagElement(TagElement element) {

        CSAttribute attribute = new CSAttribute(mappedTypeName("System.ObsoleteAttribute"));
        if (element.fragments().isEmpty()) {
            return attribute;
        }
        attribute.addArgument(new CSStringLiteralExpression(toLiteralStringForm(getWholeText(element))));
        return attribute;
    }

    private String getWholeText(TagElement element) {
        StringBuilder builder = new StringBuilder();

        for (ASTNode fragment : (List<ASTNode>) element.fragments()) {
            if (fragment instanceof TextElement) {
                TextElement textElement = (TextElement) fragment;
                String text = textElement.getText();
                appendWithSpaceIfRequired(builder, text);
            } else if (fragment instanceof TagElement) {
                builder.append(getWholeText((TagElement) fragment));
            } else if (fragment instanceof MethodRef) {
                builder.append(mapCRefTarget(fragment));
            } else if (fragment instanceof MemberRef) {
                builder.append(mapCRefTarget(fragment));
            } else if (fragment instanceof Name) {
                builder.append(mapCRefTarget(fragment));
            } else {
                break;
            }
        }
        return builder.toString().trim();
    }

    private void appendWithSpaceIfRequired(StringBuilder builder, String text) {
        if (builder.length() > 0 && builder.charAt(builder.length() - 1) != ' ' && text.startsWith(" ") == false) {
            builder.append(" ");
        }
        builder.append(text);
    }

    private String toLiteralStringForm(String s) {
        // TODO: deal with escaping sequences here
        return "@\"" + s.replace("\"", "\"\"") + "\"";
    }

    private boolean isConversionTag(String tagName) {
        return tagName.startsWith("@sharpen.");
    }

    private void processPartialTagElement(TypeDeclaration node, CSTypeDeclaration member) {
        TagElement element = javadocTagFor(node, SharpenAnnotations.SHARPEN_PARTIAL);
        if (null == element)
            return;
        ((CSTypeDeclaration) member).partial(true);
    }

    private TagElement javadocTagFor(PackageDeclaration node, final String withName) {
        return JavadocUtility.getJavadocTag(node, withName);
    }

    private TagElement javadocTagFor(BodyDeclaration node, final String withName) {
        return JavadocUtility.getJavadocTag(node, withName);
    }

    private void mapJavadocSummary(final CSMember member, TagElement element) {
        List<String> summary = getFirstSentence(element);
        if (null != summary) {
            CSDocTagNode summaryNode = new CSDocTagNode("summary");
            for (String fragment : summary) {
                summaryNode.addFragment(new CSDocTextNode(fragment));
            }
            member.addDoc(summaryNode);
            member.addDoc(createTagNode("remarks", element));
        } else {
            member.addDoc(createTagNode("summary", element));
        }
    }

    private List<String> getFirstSentence(TagElement element) {
        List<String> fragments = new LinkedList<String>();
        for (Object fragment : element.fragments()) {
            if (fragment instanceof TextElement) {
                TextElement textElement = (TextElement) fragment;
                String text = textElement.getText();
                int index = findSentenceClosure(text);
                if (index > -1) {
                    fragments.add(text.substring(0, index + 1));
                    return fragments;
                } else {
                    fragments.add(text);
                }
            } else {
                break;
            }
        }
        return null;
    }

    private int findSentenceClosure(String text) {
        Matcher matcher = SUMMARY_CLOSURE_PATTERN.matcher(text);
        return matcher.find() ? matcher.start() : -1;
    }

    private CSDocNode mapTagElement(TagElement element) {
        String tagName = element.getTagName();
        if (TagElement.TAG_PARAM.equals(tagName)) {
            return mapTagParam(element);
        } else if (TagElement.TAG_RETURN.equals(tagName)) {
            return createTagNode("returns", element);
        } else if (TagElement.TAG_LINK.equals(tagName)) {
            return mapTagLink(element);
        } else if (TagElement.TAG_THROWS.equals(tagName)) {
            return mapTagThrows(element);
        } else if (TagElement.TAG_SEE.equals(tagName)) {
            return mapTagWithCRef("seealso", element);
        }
        return createTagNode(tagName.substring(1), element);
    }

    private CSDocNode mapTagThrows(TagElement element) {
        return mapTagWithCRef("exception", element);
    }

    private CSDocNode mapTagLink(TagElement element) {
        return mapTagWithCRef("see", element);
    }

    private CSDocNode mapTagWithCRef(String tagName, TagElement element) {
        final List fragments = element.fragments();
        if (fragments.isEmpty()) {
            return invalidTagWithCRef(element, tagName, element);
        }
        final ASTNode linkTarget = (ASTNode) fragments.get(0);
        String cref = mapCRefTarget(linkTarget);
        if (null == cref) {
            return invalidTagWithCRef(linkTarget, tagName, element);
        }
        CSDocTagNode node = newTagWithCRef(tagName, cref);
        if (fragments.size() > 1) {
            if (isLinkWithSimpleLabel(fragments, linkTarget)) {
                node.addTextFragment(unqualifiedName(cref));
            } else {
                collectFragments(node, fragments, 1);
            }
        } else {
            //TODO: Move the XML encoding to the right place (CSharpPrinter)
            node.addTextFragment(cref.replace("{", "&lt;").replace("}", "&gt;"));
        }
        return node;
    }

    private ASTNode documentedNodeAttachedTo(TagElement element) {
        ASTNode attachedToNode = element;
        while (attachedToNode instanceof TagElement || attachedToNode instanceof Javadoc) {
            attachedToNode = attachedToNode.getParent();
        }
        return attachedToNode;
    }

    private CSDocNode invalidTagWithCRef(final ASTNode linkTarget, String tagName, TagElement element) {
        warning(linkTarget, "Tag '" + element.getTagName() + "' demands a valid cref target.");
        CSDocNode newTag = createTagNode(tagName, element);
        return newTag;
    }

    private CSDocTagNode newTagWithCRef(String tagName, String cref) {
        CSDocTagNode node = new CSDocTagNode(tagName);
        node.addAttribute("cref", cref);
        return node;
    }

    private boolean isLinkWithSimpleLabel(List<ASTNode> fragments, final ASTNode linkTarget) {
        if (fragments.size() != 2)
            return false;
        if (!JavadocUtility.isTextFragment(fragments, 1))
            return false;
        final String link = linkTarget.toString();
        final String label = JavadocUtility.textFragment(fragments, 1);
        return label.equals(link) || label.equals(unqualifiedName(link));
    }

    private String mapCRefTarget(final ASTNode crefTarget) {
        return new CRefBuilder(crefTarget).build();
    }

    private CSDocNode mapTagParam(TagElement element) {

        List fragments = element.fragments();

        if (!(fragments.get(0) instanceof SimpleName))
            return new CSDocTagNode("?");
        SimpleName name = (SimpleName) fragments.get(0);
        if (null == name.resolveBinding()) {
            warning(name, "Parameter '" + name + "' not found.");
        }

        CSDocTagNode param = isPropertyNode(documentedNodeAttachedTo(element)) ? new CSDocTagNode("value")
                : newCSDocTag(fixIdentifierNameFor(identifier(name), element));

        collectFragments(param, fragments, 1);
        return param;
    }

    private CSDocTagNode newCSDocTag(final String paramName) {
        CSDocTagNode param;
        param = new CSDocTagNode("param");
        param.addAttribute("name", paramName);
        return param;
    }

    private boolean isPropertyNode(ASTNode node) {
        if (node.getNodeType() != ASTNode.METHOD_DECLARATION) {
            return false;
        }

        return isProperty((MethodDeclaration) node);
    }

    private String fixIdentifierNameFor(String identifier, TagElement element) {
        return removeAtSign(identifier);
    }

    private String removeAtSign(String identifier) {
        return identifier.startsWith("@") ? identifier.substring(1) : identifier;
    }

    private void collectFragments(CSDocTagNode node, List fragments, int index) {
        for (int i = index; i < fragments.size(); ++i) {
            node.addFragment(mapTagElementFragment((ASTNode) fragments.get(i)));
        }
    }

    private CSDocNode mapTextElement(TextElement element) {
        final String text = element.getText();
        if (HTML_ANCHOR_PATTERN.matcher(text).find()) {
            warning(element, "Caution: HTML anchors can result in broken links. Consider using @link instead.");
        }
        return new CSDocTextNode(text);
    }

    private CSDocNode createTagNode(String tagName, TagElement element) {
        CSDocTagNode summary = new CSDocTagNode(tagName);
        for (Object f : element.fragments()) {
            summary.addFragment(mapTagElementFragment((ASTNode) f));
        }
        return summary;
    }

    private CSDocNode mapTagElementFragment(ASTNode node) {
        switch (node.getNodeType()) {
        case ASTNode.TAG_ELEMENT:
            return mapTagElement((TagElement) node);
        case ASTNode.TEXT_ELEMENT:
            return mapTextElement((TextElement) node);
        }
        warning(node, "Documentation node not supported: " + node.getClass() + ": " + node);
        return new CSDocTextNode(node.toString());
    }

    public boolean visit(FieldDeclaration node) {

        if (SharpenAnnotations.hasIgnoreAnnotation(node)) {
            return false;
        }

        ITypeBinding fieldType = node.getType().resolveBinding();
        CSTypeReferenceExpression typeName = mappedTypeReference(fieldType);
        CSVisibility visibility = mapVisibility(node);
        if (((VariableDeclarationFragment) node.fragments().get(0)).resolveBinding().getDeclaringClass()
                .isInterface())
            visibility = CSVisibility.Public;

        for (Object item : node.fragments()) {
            VariableDeclarationFragment fragment = (VariableDeclarationFragment) item;
            ITypeBinding thisFieldType = fieldType;
            CSTypeReferenceExpression thisTypeName = typeName;
            if (fragment.getExtraDimensions() > 0) {
                thisFieldType = fieldType.createArrayType(fragment.getExtraDimensions());
                thisTypeName = mappedTypeReference(thisFieldType);
            }
            ITypeBinding saved = pushExpectedType(thisFieldType);
            CSField field = mapFieldDeclarationFragment(node, fragment, thisTypeName, visibility);
            popExpectedType(saved);
            adjustVisibility(thisFieldType, field);
            _currentType.addMember(field);
        }

        return false;
    }

    private CSField mapFieldDeclarationFragment(FieldDeclaration node, VariableDeclarationFragment fragment,
            CSTypeReferenceExpression fieldType, CSVisibility fieldVisibility) {
        CSField field = new CSField(fieldName(fragment), fieldType, fieldVisibility, mapFieldInitializer(fragment));
        if (isConstField(node, fragment)) {
            field.addModifier(CSFieldModifier.Const);
        } else {
            processFieldModifiers(field, node.getModifiers(),
                    fragment.resolveBinding().getDeclaringClass().isInterface());
        }
        mapDocumentation(node, field);
        mapAnnotations(node, field);
        return field;
    }

    private void mapAnnotations(BodyDeclaration node, CSMember member) {
        for (Object m : node.modifiers()) {
            if (!(m instanceof Annotation)) {
                continue;
            }
            if (isIgnoredAnnotation((Annotation) m)) {
                continue;
            }
            if (m instanceof MarkerAnnotation) {
                mapMarkerAnnotation((MarkerAnnotation) m, member);
            }
        }
    }

    private boolean isIgnoredAnnotation(Annotation m) {
        return _configuration.isIgnoredAnnotation(qualifiedName(m.resolveAnnotationBinding().getAnnotationType()));
    }

    private void mapMarkerAnnotation(MarkerAnnotation annotation, CSMember member) {
        final IAnnotationBinding binding = annotation.resolveAnnotationBinding();
        final CSAttribute attribute = new CSAttribute(mappedTypeName(binding.getAnnotationType()));
        member.addAttribute(attribute);
    }

    protected String fieldName(VariableDeclarationFragment fragment) {
        String name = mappedFieldName(fragment.getName());
        return name != null ? name : identifier(fragment.getName());
    }

    protected CSExpression mapFieldInitializer(VariableDeclarationFragment fragment) {
        return mapExpression(fragment.getInitializer());
    }

    private boolean isConstField(FieldDeclaration node, VariableDeclarationFragment fragment) {
        boolean isStaticFinal = Modifier.isFinal(node.getModifiers()) && Modifier.isStatic(node.getModifiers());
        if (!isStaticFinal && !fragment.resolveBinding().getDeclaringClass().isInterface()) {
            return false;
        }

        Type type = node.getType();
        boolean isPrimitiveOrString = type.isPrimitiveType();
        if (!type.isPrimitiveType() && (type.isSimpleType() || type.isQualifiedType())) {
            ITypeBinding binding = type.resolveBinding();
            if (binding != null) {
                String name = binding.getQualifiedName();
                isPrimitiveOrString = name.equals("java.lang.String");
            }
        }

        return isPrimitiveOrString && hasConstValue(fragment);
    }

    private boolean hasConstValue(VariableDeclarationFragment fragment) {
        Object constValue = fragment.resolveBinding().getConstantValue();
        if (null != constValue) {
            return !("".equals(constValue) && my(Configuration.class).mapEmptyStringLiteral());
        }
        return false;
    }

    private void processFieldModifiers(CSField field, int modifiers, boolean inInterface) {
        if (Modifier.isStatic(modifiers) || inInterface) {
            field.addModifier(CSFieldModifier.Static);
        }
        if (Modifier.isFinal(modifiers) || inInterface) {
            field.addModifier(CSFieldModifier.Readonly);
        }
        if (Modifier.isTransient(modifiers)) {
            field.addAttribute(new CSAttribute(mappedTypeName("System.NonSerialized")));
        }
        if (Modifier.isVolatile(modifiers)) {
            field.addModifier(CSFieldModifier.Volatile);
        }

    }

    private boolean isDestructor(MethodDeclaration node) {
        return node.getName().toString().equals("finalize");
    }

    public boolean visit(Initializer node) {
        if (Modifier.isStatic(node.getModifiers())) {
            CSConstructor ctor = new CSConstructor(CSConstructorModifier.Static);
            _currentType.addMember(ctor);
            visitBodyDeclarationBlock(node, node.getBody(), ctor);
        } else {
            _instanceInitializers.add(node);
        }
        return false;
    }

    public boolean visit(MethodDeclaration node) {
        if (SharpenAnnotations.hasIgnoreAnnotation(node) || isRemoved(node)) {
            return false;
        }

        if (isEvent(node)) {
            processEventDeclaration(node);
            return false;
        }

        if (isMappedToProperty(node)) {
            processMappedPropertyDeclaration(node);
            return false;
        }

        if (isTaggedAsProperty(node)) {
            processPropertyDeclaration(node);
            return false;
        }

        if (isIndexer(node)) {
            processIndexerDeclaration(node);
            return false;
        }

        processMethodDeclaration(node);

        return false;
    }

    private void processIndexerDeclaration(MethodDeclaration node) {
        processPropertyDeclaration(node, CSProperty.INDEXER);
    }

    private boolean isIndexer(MethodDeclaration node) {
        return isTaggedDeclaration(node, SharpenAnnotations.SHARPEN_INDEXER);
    }

    private boolean isRemoved(MethodDeclaration node) {
        return hasRemoveAnnotation(node) || isRemoved(node.resolveBinding());
    }

    private boolean hasRemoveAnnotation(BodyDeclaration node) {
        return containsJavadoc(node, SharpenAnnotations.SHARPEN_REMOVE);
    }

    private boolean isRemoved(final IMethodBinding binding) {
        return _configuration.isRemoved(qualifiedName(binding));
    }

    public static boolean containsJavadoc(BodyDeclaration node, final String tag) {
        return JavadocUtility.containsJavadoc(node, tag);
    }

    private void processPropertyDeclaration(MethodDeclaration node) {
        processPropertyDeclaration(node, propertyName(node));
    }

    private void processMappedPropertyDeclaration(MethodDeclaration node) {
        processPropertyDeclaration(node, mappedMethodName(node));
    }

    private void processPropertyDeclaration(MethodDeclaration node, final String name) {
        mapPropertyDeclaration(node, producePropertyFor(node, name));
    }

    private CSProperty producePropertyFor(MethodDeclaration node, final String name) {
        CSProperty existingProperty = findProperty(node, name);
        if (existingProperty != null) {
            return existingProperty;
        }
        CSProperty property = newPropertyFor(node, name);
        _currentType.addMember(property);
        return property;
    }

    private CSProperty findProperty(MethodDeclaration node, final String name) {
        CSMember existingProperty = _currentType.getMember(name);
        if (existingProperty != null) {
            if (!(existingProperty instanceof CSProperty)) {
                throw new IllegalArgumentException(
                        sourceInformation(node) + ": Previously declared member redeclared as property.");
            }
        }
        return (CSProperty) existingProperty;
    }

    private CSProperty mapPropertyDeclaration(MethodDeclaration node, CSProperty property) {
        final CSBlock block = mapBody(node);

        if (isGetter(node)) {
            property.getter(block);
        } else {
            property.setter(block);
            mapImplicitSetterParameter(node, property);
        }
        mapMetaMemberAttributes(node, property);
        mapParameters(node, property);
        return property;
    }

    private void mapImplicitSetterParameter(MethodDeclaration node, CSProperty property) {
        final String parameterName = parameter(node, 0).getName().toString();
        if (parameterName.equals("value")) {
            return;
        }

        property.setter().addStatement(0, newVariableDeclarationExpression(parameterName, property.type(),
                new CSReferenceExpression("value")));
    }

    private CSDeclarationExpression newVariableDeclarationExpression(final String name,
            final CSTypeReferenceExpression type, final CSReferenceExpression initializer) {
        return new CSDeclarationExpression(type, name, initializer);
    }

    private CSProperty newPropertyFor(MethodDeclaration node, final String propName) {
        final CSTypeReferenceExpression propertyType = isGetter(node) ? mappedReturnType(node)
                : mappedTypeReference(lastParameter(node).getType());
        CSProperty p = new CSProperty(propName, propertyType);
        return p;
    }

    private CSBlock mapBody(MethodDeclaration node) {
        final CSBlock block = new CSBlock();
        processBlock(node, node.getBody(), block);
        return block;
    }

    private boolean isGetter(MethodDeclaration node) {
        return !"void".equals(node.getReturnType2().toString());
    }

    private SingleVariableDeclaration lastParameter(MethodDeclaration node) {
        return parameter(node, node.parameters().size() - 1);
    }

    private String propertyName(MethodDeclaration node) {
        return my(Annotations.class).annotatedPropertyName(node);
    }

    private String propertyName(IMethodBinding binding) {
        return propertyName(declaringNode(binding));
    }

    private boolean isProperty(MethodDeclaration node) {
        return isTaggedAsProperty(node) || isMappedToProperty(node);
    }

    private boolean isTaggedAsProperty(MethodDeclaration node) {
        return isTaggedDeclaration(node, SharpenAnnotations.SHARPEN_PROPERTY);
    }

    private boolean isTaggedDeclaration(MethodDeclaration node, final String tag) {
        return effectiveAnnotationFor(node, tag) != null;
    }

    private void processMethodDeclaration(MethodDeclaration node) {
        if (isDestructor(node)) {
            mapMethodParts(node, new CSDestructor());
            return;
        }

        if (node.isConstructor()) {
            mapMethodParts(node, new CSConstructor());
            return;
        }

        CSMethod method = new CSMethod(mappedMethodDeclarationName(node));
        method.returnType(mappedReturnType(node));
        method.modifier(mapMethodModifier(node));
        mapTypeParameters(node.typeParameters(), method);
        mapMethodParts(node, method);

        if (_configuration.junitConversion() && isLegacyTestFixture(node.resolveBinding().getDeclaringClass())) {
            if (method.name().startsWith("Test") && method.visibility() == CSVisibility.Public)
                method.addAttribute(new CSAttribute("NUnit.Framework.Test"));
            if (isLegacyTestFixtureClass(node.resolveBinding().getDeclaringClass().getSuperclass())) {
                if (method.name().equals("SetUp")) {
                    method.addAttribute(new CSAttribute("NUnit.Framework.SetUp"));
                    method.modifier(CSMethodModifier.Virtual);
                    cleanBaseSetupCalls(method);
                } else if (method.name().equals("TearDown")) {
                    method.addAttribute(new CSAttribute("NUnit.Framework.TearDown"));
                    method.modifier(CSMethodModifier.Virtual);
                    cleanBaseSetupCalls(method);
                }
            }
        }
    }

    private void cleanBaseSetupCalls(CSMethod method) {
        ArrayList<CSStatement> toDelete = new ArrayList<CSStatement>();
        for (CSStatement st : method.body().statements()) {
            if (st instanceof CSExpressionStatement) {
                CSExpressionStatement es = (CSExpressionStatement) st;
                if (es.expression() instanceof CSMethodInvocationExpression) {
                    CSMethodInvocationExpression mie = (CSMethodInvocationExpression) es.expression();
                    if (mie.expression() instanceof CSMemberReferenceExpression) {
                        CSMemberReferenceExpression mr = (CSMemberReferenceExpression) mie.expression();
                        if ((mr.expression() instanceof CSBaseExpression)
                                && (mr.name().equals("SetUp") || mr.name().equals("TearDown")))
                            toDelete.add(st);
                    }
                }
            }
        }
        for (CSStatement st : toDelete)
            method.body().removeStatement(st);
    }

    private void mapMethodParts(MethodDeclaration node, CSMethodBase method) {

        _currentType.addMember(method);

        method.startPosition(node.getStartPosition());
        method.isVarArgs(node.isVarargs());
        mapParameters(node, method);
        mapAnnotations(node, method);
        mapDocumentation(node, method);
        visitBodyDeclarationBlock(node, node.getBody(), method);

        IMethodBinding overriden = getOverridedMethod(node);
        if (overriden != null) {
            CSVisibility vis = mapVisibility(overriden.getModifiers());
            if (vis == CSVisibility.ProtectedInternal && !overriden.getDeclaringClass().isFromSource())
                vis = CSVisibility.Protected;
            method.visibility(vis);
        } else if (node.resolveBinding().getDeclaringClass().isInterface())
            method.visibility(CSVisibility.Public);
        else
            mapVisibility(node, method);
    }

    private String mappedMethodDeclarationName(MethodDeclaration node) {
        final String mappedName = mappedMethodName(node);
        if (null == mappedName || 0 == mappedName.length()) {
            return methodName(node.getName().toString());
        } else if (mappedName.contains(".")) {
            int lastDot = mappedName.lastIndexOf('.');
            return mappedName.substring(lastDot + 1);
        }
        return mappedName;
    }

    private void mapParameters(MethodDeclaration node, CSParameterized method) {
        if (method instanceof CSMethod) {
            mapMethodParameters(node, (CSMethod) method);
            return;
        }
        for (Object p : node.parameters()) {
            mapParameter((SingleVariableDeclaration) p, method);
        }
    }

    private void mapParameter(SingleVariableDeclaration parameter, CSParameterized method) {
        if (method instanceof CSMethod) {
            IVariableBinding vb = parameter.resolveBinding();
            ITypeBinding[] ta = vb.getType().getTypeArguments();
            if (ta.length > 0 && ta[0].getName().startsWith("?")) {
                ITypeBinding extended = mapTypeParameterExtendedType(ta[0]);
                CSMethod met = (CSMethod) method;
                String genericArg = "_T" + met.typeParameters().size();
                CSTypeParameter tp = new CSTypeParameter(genericArg);
                if (extended != null)
                    tp.superClass(mappedTypeReference(extended));
                met.addTypeParameter(tp);

                CSTypeReference tr = new CSTypeReference(mappedTypeName(vb.getType()));
                tr.addTypeArgument(new CSTypeReference(genericArg));
                method.addParameter(new CSVariableDeclaration(identifier(vb.getName()), tr));
                return;
            }
        }
        method.addParameter(createParameter(parameter));
    }

    ITypeBinding mapTypeParameterExtendedType(ITypeBinding tb) {
        ITypeBinding superc = tb.getSuperclass();
        if (superc != null && !superc.getQualifiedName().equals("java.lang.Object")
                && !superc.getQualifiedName().equals("java.lang.Enum<?>")) {
            return superc;
        }
        ITypeBinding[] ints = tb.getInterfaces();
        if (ints.length > 0)
            return ints[0];
        return null;
    }

    private void mapMethodParameters(MethodDeclaration node, CSMethod method) {
        for (Object o : node.parameters()) {
            SingleVariableDeclaration p = (SingleVariableDeclaration) o;
            ITypeBinding parameterType = p.getType().resolveBinding();
            if (isGenericRuntimeParameterIdiom(node.resolveBinding(), parameterType)) {

                // System.Type <p.name> = typeof(<T>);
                method.body()
                        .addStatement(new CSDeclarationStatement(p.getStartPosition(),
                                new CSVariableDeclaration(identifier(p.getName()),
                                        new CSTypeReference("System.Type"),
                                        new CSTypeofExpression(genericRuntimeTypeIdiomType(parameterType)))));

            } else {

                mapParameter(p, method);
            }
        }
    }

    private CSTypeReferenceExpression genericRuntimeTypeIdiomType(ITypeBinding parameterType) {
        return mappedTypeReference(parameterType.getTypeArguments()[0]);
    }

    private boolean isGenericRuntimeParameterIdiom(IMethodBinding method, ITypeBinding parameterType) {
        if (!parameterType.isParameterizedType()) {
            return false;
        }
        if (!"java.lang.Class".equals(qualifiedName(parameterType))) {
            return false;
        }
        // detecting if the T in Class<T> comes from the method itself
        final ITypeBinding classTypeArgument = parameterType.getTypeArguments()[0];
        return classTypeArgument.getDeclaringMethod() == method;
    }

    private CSTypeReferenceExpression mappedReturnType(MethodDeclaration node) {
        IMethodBinding overriden = getOverridedMethod(node);
        if (overriden != null)
            return mappedTypeReference(overriden.getReturnType());
        return mappedTypeReference(node.getReturnType2());
    }

    private void processEventDeclaration(MethodDeclaration node) {
        CSTypeReference eventHandlerType = new CSTypeReference(getEventHandlerTypeName(node));
        CSEvent event = createEventFromMethod(node, eventHandlerType);
        mapMetaMemberAttributes(node, event);
        if (_currentType.isInterface())
            return;

        VariableDeclarationFragment field = getEventBackingField(node);
        CSField backingField = (CSField) _currentType.getMember(field.getName().toString());
        backingField.type(eventHandlerType);

        // clean field
        backingField.initializer(null);
        backingField.removeModifier(CSFieldModifier.Readonly);

        final CSBlock addBlock = createEventBlock(backingField, "System.Delegate.Combine");
        String onAddMethod = getEventOnAddMethod(node);
        if (onAddMethod != null) {
            addBlock.addStatement(new CSMethodInvocationExpression(new CSReferenceExpression(onAddMethod)));
        }
        event.setAddBlock(addBlock);
        event.setRemoveBlock(createEventBlock(backingField, "System.Delegate.Remove"));
    }

    private String getEventOnAddMethod(MethodDeclaration node) {
        final TagElement onAddTag = javadocTagFor(node, SharpenAnnotations.SHARPEN_EVENT_ON_ADD);
        if (null == onAddTag)
            return null;
        return methodName(JavadocUtility.singleTextFragmentFrom(onAddTag));
    }

    private String getEventHandlerTypeName(MethodDeclaration node) {
        final String eventArgsType = getEventArgsType(node);
        return buildEventHandlerTypeName(node, eventArgsType);
    }

    private void mapMetaMemberAttributes(MethodDeclaration node, CSMetaMember metaMember) {
        mapVisibility(node, metaMember);
        metaMember.modifier(mapMethodModifier(node));
        mapDocumentation(node, metaMember);
    }

    private CSBlock createEventBlock(CSField backingField, String delegateMethod) {
        CSBlock block = new CSBlock();
        block.addStatement(new CSInfixExpression("=", new CSReferenceExpression(backingField.name()),
                new CSCastExpression(backingField.type(),
                        new CSMethodInvocationExpression(new CSReferenceExpression(delegateMethod),
                                new CSReferenceExpression(backingField.name()),
                                new CSReferenceExpression("value")))));
        return block;
    }

    private static final class CheckVariableUseVisitor extends ASTVisitor {

        private final IVariableBinding _var;
        private boolean _used;

        private CheckVariableUseVisitor(IVariableBinding var) {
            this._var = var;
        }

        @Override
        public boolean visit(SimpleName name) {
            IBinding binding = name.resolveBinding();
            if (binding == null) {
                return false;
            }
            if (binding.equals(_var)) {
                _used = true;
            }

            return false;
        }

        public boolean used() {
            return _used;
        }
    }

    private static final class FieldAccessFinder extends ASTVisitor {
        public IBinding field;

        @Override
        public boolean visit(SimpleName node) {
            field = node.resolveBinding();
            return false;
        }
    }

    private VariableDeclarationFragment getEventBackingField(MethodDeclaration node) {
        FieldAccessFinder finder = new FieldAccessFinder();
        node.accept(finder);
        return findDeclaringNode(finder.field);
    }

    private CSEvent createEventFromMethod(MethodDeclaration node, CSTypeReference eventHandlerType) {
        String eventName = methodName(node);
        CSEvent event = new CSEvent(eventName, eventHandlerType);
        _currentType.addMember(event);
        return event;
    }

    private String methodName(MethodDeclaration node) {
        return methodName(node.getName().toString());
    }

    private String unqualifiedName(String typeName) {
        int index = typeName.lastIndexOf('.');
        if (index < 0)
            return typeName;
        return typeName.substring(index + 1);
    }

    private String buildEventHandlerTypeName(ASTNode node, String eventArgsTypeName) {
        if (!eventArgsTypeName.endsWith("EventArgs")) {
            warning(node, SharpenAnnotations.SHARPEN_EVENT + " type name must end with 'EventArgs'");
            return eventArgsTypeName + "EventHandler";
        }

        return "System.EventHandler<" + eventArgsTypeName + ">";
    }

    private String getEventArgsType(MethodDeclaration node) {
        TagElement tag = eventTagFor(node);
        if (null == tag)
            return null;
        return mappedTypeName(JavadocUtility.singleTextFragmentFrom(tag));
    }

    private TagElement eventTagFor(MethodDeclaration node) {
        return effectiveAnnotationFor(node, SharpenAnnotations.SHARPEN_EVENT);
    }

    private TagElement effectiveAnnotationFor(MethodDeclaration node, final String annotation) {
        return my(Annotations.class).effectiveAnnotationFor(node, annotation);
    }

    private <T extends ASTNode> T findDeclaringNode(IBinding binding) {
        return (T) my(Bindings.class).findDeclaringNode(binding);
    }

    private void visitBodyDeclarationBlock(BodyDeclaration node, Block block, CSMethodBase method) {
        CSMethodBase saved = _currentMethod;
        _currentMethod = method;

        processDisableTags(node, method);
        processBlock(node, block, method.body());
        _currentMethod = saved;
    }

    private void processDisableTags(PackageDeclaration packageDeclaration, CSNode csNode) {
        TagElement tag = javadocTagFor(packageDeclaration, SharpenAnnotations.SHARPEN_IF);
        if (null == tag)
            return;

        csNode.addEnclosingIfDef(JavadocUtility.singleTextFragmentFrom(tag));
    }

    private void processDisableTags(BodyDeclaration node, CSNode csNode) {
        TagElement tag = javadocTagFor(node, SharpenAnnotations.SHARPEN_IF);
        if (null == tag)
            return;

        csNode.addEnclosingIfDef(JavadocUtility.singleTextFragmentFrom(tag));
    }

    private void processBlock(BodyDeclaration node, Block block, final CSBlock targetBlock) {
        if (containsJavadoc(node, SharpenAnnotations.SHARPEN_REMOVE_FIRST)) {
            block.statements().remove(0);
        }

        BodyDeclaration savedDeclaration = _currentBodyDeclaration;
        _currentBodyDeclaration = node;

        if (Modifier.isSynchronized(node.getModifiers())) {
            CSLockStatement lock = new CSLockStatement(node.getStartPosition(), getLockTarget(node));
            targetBlock.addStatement(lock);
            visitBlock(lock.body(), block);
        } else {
            visitBlock(targetBlock, block);
        }
        _currentBodyDeclaration = savedDeclaration;
    }

    private CSExpression getLockTarget(BodyDeclaration node) {
        return Modifier.isStatic(node.getModifiers())
                ? new CSTypeofExpression(new CSTypeReference(_currentType.name()))
                : new CSThisExpression();
    }

    @Override
    public boolean visit(AssertStatement node) {
        Expression expression = node.getExpression();

        _compilationUnit.addUsing(new CSUsing("System.Diagnostics"));
        CSTypeReferenceExpression debugClassRef = new CSTypeReference("Debug");
        CSMemberReferenceExpression debugAssertMethodRef = new CSMemberReferenceExpression(debugClassRef, "Assert");

        CSMethodInvocationExpression debugAssertMethodInvocation = new CSMethodInvocationExpression(
                debugAssertMethodRef, mapExpression(expression));

        if (node.getMessage() != null)
            debugAssertMethodInvocation.addArgument(mapExpression(node.getMessage()));

        _currentBlock.addStatement(debugAssertMethodInvocation);

        return false;
    }

    public boolean visit(ConstructorInvocation node) {
        addChainedConstructorInvocation(new CSThisExpression(), node.arguments());
        return false;
    }

    private void addChainedConstructorInvocation(CSExpression target, List arguments) {
        CSConstructorInvocationExpression cie = new CSConstructorInvocationExpression(target);
        mapArguments(cie, arguments);
        ((CSConstructor) _currentMethod).chainedConstructorInvocation(cie);
    }

    public boolean visit(SuperConstructorInvocation node) {
        if (null != node.getExpression()) {
            notImplemented(node);
        }
        addChainedConstructorInvocation(new CSBaseExpression(), node.arguments());
        return false;
    }

    private <T extends ASTNode> void visitBlock(CSBlock block, T node) {
        if (null == node) {
            return;
        }

        CSBlock saved = _currentBlock;
        _currentBlock = block;

        _currentContinueLabel = null;

        node.accept(this);
        _currentBlock = saved;
    }

    public boolean visit(VariableDeclarationExpression node) {
        CSTypeReferenceExpression mappedTypeReference = mappedTypeReference(node.getType());

        int extraDimensions = Integer.MAX_VALUE;
        for (Object f : node.fragments()) {
            VariableDeclarationFragment variable = (VariableDeclarationFragment) f;
            int ved = variable.getExtraDimensions();
            if (ved < extraDimensions)
                extraDimensions = ved;
        }

        if (extraDimensions > 0 && extraDimensions < Integer.MAX_VALUE) {
            if (mappedTypeReference instanceof CSArrayTypeReference) {
                CSArrayTypeReference arrayTypeReference = (CSArrayTypeReference) mappedTypeReference;
                mappedTypeReference = new CSArrayTypeReference(arrayTypeReference.elementType(),
                        arrayTypeReference.dimensions() + extraDimensions);
            } else {
                mappedTypeReference = new CSArrayTypeReference(mappedTypeReference, extraDimensions);
            }
        }

        CSDeclarationExpression expr = new CSDeclarationExpression(mappedTypeReference);

        for (Object f : node.fragments()) {
            VariableDeclarationFragment variable = (VariableDeclarationFragment) f;
            if (variable.getExtraDimensions() != extraDimensions)
                warning(variable, "Variable has unsupported extra dimensions");
            expr.addFragment(formatVariableName(variable.resolveBinding()),
                    mapExpression(variable.getInitializer()));
        }
        pushExpression(expr);
        return false;
    }

    public boolean visit(VariableDeclarationStatement node) {
        for (Object f : node.fragments()) {
            VariableDeclarationFragment variable = (VariableDeclarationFragment) f;
            addStatement(new CSDeclarationStatement(node.getStartPosition(), createVariableDeclaration(variable)));
        }
        return false;
    }

    private CSVariableDeclaration createVariableDeclaration(VariableDeclarationFragment variable) {
        IVariableBinding binding = variable.resolveBinding();
        ITypeBinding saved = pushExpectedType(binding.getType());
        CSExpression initializer = mapExpression(variable.getInitializer());
        popExpectedType(saved);
        return createVariableDeclaration(binding, initializer);
    }

    private CSVariableDeclaration createVariableDeclaration(IVariableBinding binding, CSExpression initializer) {
        return new CSVariableDeclaration(formatVariableName(binding), mappedTypeReference(binding.getType()),
                initializer);
    }

    private String formatVariableName(IVariableBinding binding) {
        String name = binding.getName();
        if (_blockVariables.size() > 0) {
            if (_blockVariables.peek().contains(name)) {
                int count = 1;
                while (_blockVariables.peek().contains(name + "_" + count)) {
                    count++;
                }
                _renamedVariables.peek().put(name, name + "_" + count);
                name = name + "_" + count;
            }
            _localBlockVariables.peek().add(name);
            for (Set<String> s : _blockVariables)
                s.add(name);
        }
        return identifier(name);
    }

    public boolean visit(ExpressionStatement node) {
        if (isRemovedMethodInvocation(node.getExpression())) {
            return false;
        }

        addStatement(new CSExpressionStatement(node.getStartPosition(), mapExpression(node.getExpression())));
        return false;
    }

    private boolean isRemovedMethodInvocation(Expression expression) {
        if (!(expression instanceof MethodInvocation)) {
            return false;
        }

        MethodInvocation invocation = (MethodInvocation) expression;
        return isTaggedMethodInvocation(invocation, SharpenAnnotations.SHARPEN_REMOVE)
                || isRemoved(invocation.resolveMethodBinding());

    }

    public boolean isEnumOrdinalMethodInvocation(MethodInvocation node) {
        return node.getName().getIdentifier().equals("ordinal") && node.getExpression() != null
                && node.getExpression().resolveTypeBinding().isEnum();
    }

    public boolean isEnumNameMethodInvocation(MethodInvocation node) {
        return node.getName().getIdentifier().equals("name") && node.getExpression() != null
                && node.getExpression().resolveTypeBinding().isEnum();
    }

    public boolean visit(IfStatement node) {
        Expression expression = node.getExpression();

        Object constValue = constValue(expression);
        if (null != constValue) {
            // dead branch elimination
            if (isTrue(constValue)) {
                node.getThenStatement().accept(this);
            } else {
                if (null != node.getElseStatement()) {
                    node.getElseStatement().accept(this);
                }
            }
        } else {
            CSIfStatement stmt = new CSIfStatement(node.getStartPosition(), mapExpression(expression));
            visitBlock(stmt.trueBlock(), node.getThenStatement());
            visitBlock(stmt.falseBlock(), node.getElseStatement());
            addStatement(stmt);
        }
        return false;
    }

    private boolean isTrue(Object constValue) {
        return ((Boolean) constValue).booleanValue();
    }

    private Object constValue(Expression expression) {
        switch (expression.getNodeType()) {
        case ASTNode.PREFIX_EXPRESSION:
            return constValue((PrefixExpression) expression);
        case ASTNode.SIMPLE_NAME:
        case ASTNode.QUALIFIED_NAME:
            return constValue((Name) expression);
        }
        return null;
    }

    public Object constValue(PrefixExpression expression) {
        if (PrefixExpression.Operator.NOT == expression.getOperator()) {
            Object value = constValue(expression.getOperand());
            if (null != value) {
                return isTrue(value) ? Boolean.FALSE : Boolean.TRUE;
            }
        }
        return null;
    }

    public Object constValue(Name expression) {
        IBinding binding = expression.resolveBinding();
        if (IBinding.VARIABLE == binding.getKind()) {
            return ((IVariableBinding) binding).getConstantValue();
        }
        return null;
    }

    public boolean visit(final WhileStatement node) {
        consumeContinueLabel(new Function<CSBlock>() {
            public CSBlock apply() {
                CSWhileStatement stmt = new CSWhileStatement(node.getStartPosition(),
                        mapExpression(node.getExpression()));
                visitBlock(stmt.body(), node.getBody());
                addStatement(stmt);
                return stmt.body();
            }
        });
        return false;
    }

    public boolean visit(final DoStatement node) {
        consumeContinueLabel(new Function<CSBlock>() {
            public CSBlock apply() {
                CSDoStatement stmt = new CSDoStatement(node.getStartPosition(),
                        mapExpression(node.getExpression()));
                visitBlock(stmt.body(), node.getBody());
                addStatement(stmt);
                return stmt.body();
            }
        });
        return false;
    }

    public boolean visit(TryStatement node) {
        CSTryStatement stmt = new CSTryStatement(node.getStartPosition());
        visitBlock(stmt.body(), node.getBody());
        for (Object o : node.catchClauses()) {
            CatchClause clause = (CatchClause) o;
            if (!_configuration
                    .isIgnoredExceptionType(qualifiedName(clause.getException().getType().resolveBinding()))) {
                stmt.addCatchClause(mapCatchClause(clause));
            }
        }
        if (null != node.getFinally()) {
            CSBlock finallyBlock = new CSBlock();
            visitBlock(finallyBlock, node.getFinally());
            stmt.finallyBlock(finallyBlock);
        }

        if (null != stmt.finallyBlock() || !stmt.catchClauses().isEmpty()) {

            addStatement(stmt);
        } else {

            _currentBlock.addAll(stmt.body());
        }
        return false;
    }

    private CSCatchClause mapCatchClause(CatchClause node) {
        IVariableBinding oldExceptionVariable = _currentExceptionVariable;
        _currentExceptionVariable = node.getException().resolveBinding();
        try {
            CheckVariableUseVisitor check = new CheckVariableUseVisitor(_currentExceptionVariable);
            node.getBody().accept(check);

            // The exception variable is declared in a new scope
            pushScope();

            CSCatchClause clause;
            if (isEmptyCatch(node, check)) {
                clause = new CSCatchClause();
            } else {
                clause = new CSCatchClause(createVariableDeclaration(_currentExceptionVariable, null));
            }
            clause.anonymous(!check.used());
            visitBlock(clause.body(), node.getBody());
            return clause;
        } finally {
            _currentExceptionVariable = oldExceptionVariable;
            popScope();
        }
    }

    private boolean isEmptyCatch(CatchClause clause, CheckVariableUseVisitor check) {
        if (check.used())
            return false;
        return isThrowable(clause.getException().resolveBinding().getType());
    }

    private boolean isThrowable(ITypeBinding declaringClass) {
        return "java.lang.Throwable".equals(qualifiedName(declaringClass));
    }

    public boolean visit(ThrowStatement node) {
        addStatement(mapThrowStatement(node));
        return false;
    }

    private CSThrowStatement mapThrowStatement(ThrowStatement node) {
        Expression exception = node.getExpression();
        if (isCurrentExceptionVariable(exception)) {
            return new CSThrowStatement(node.getStartPosition(), null);
        }
        return new CSThrowStatement(node.getStartPosition(), mapExpression(exception));
    }

    private boolean isCurrentExceptionVariable(Expression exception) {
        if (!(exception instanceof SimpleName)) {
            return false;
        }
        return ((SimpleName) exception).resolveBinding() == _currentExceptionVariable;
    }

    public boolean visit(BreakStatement node) {
        SimpleName labelName = node.getLabel();
        if (labelName != null) {
            addStatement(new CSGotoStatement(node.getStartPosition(), breakLabel(labelName.getIdentifier())));
            return false;
        }
        addStatement(new CSBreakStatement(node.getStartPosition()));
        return false;
    }

    public boolean visit(ContinueStatement node) {
        SimpleName labelName = node.getLabel();
        if (labelName != null) {
            addStatement(new CSGotoStatement(node.getStartPosition(), continueLabel(labelName.getIdentifier())));
            return false;
        }
        addStatement(new CSContinueStatement(node.getStartPosition()));
        return false;
    }

    public boolean visit(SynchronizedStatement node) {
        CSLockStatement stmt = new CSLockStatement(node.getStartPosition(), mapExpression(node.getExpression()));
        visitBlock(stmt.body(), node.getBody());
        addStatement(stmt);
        return false;
    }

    public boolean visit(ReturnStatement node) {
        addStatement(new CSReturnStatement(node.getStartPosition(), mapExpression(node.getExpression())));
        return false;
    }

    public boolean visit(NumberLiteral node) {

        String token = node.getToken();
        if (token.endsWith("."))
            token = token + "0";
        CSExpression literal = new CSNumberLiteralExpression(token);

        if (expectingType("byte") && token.startsWith("-")) {
            literal = uncheckedCast("byte", literal);
        } else if (token.startsWith("0x")) {
            if (token.endsWith("l") || token.endsWith("L")) {
                literal = uncheckedCast("long", literal);
            } else {
                literal = uncheckedCast("int", literal);
            }

        } else if (token.startsWith("0") && token.indexOf('.') == -1
                && Character.isDigit(token.charAt(token.length() - 1))) {
            try {
                int n = Integer.parseInt(token, 8);
                if (n != 0)
                    literal = new CSNumberLiteralExpression("0x" + Integer.toHexString(n));
            } catch (NumberFormatException ex) {
            }
        }

        pushExpression(literal);
        return false;
    }

    private CSUncheckedExpression uncheckedCast(String type, CSExpression expression) {
        return new CSUncheckedExpression(
                new CSCastExpression(new CSTypeReference(type), new CSParenthesizedExpression(expression)));
    }

    public boolean visit(StringLiteral node) {
        String value = node.getLiteralValue();
        if (value != null && value.length() == 0) {
            if (my(Configuration.class).mapEmptyStringLiteral())
                pushExpression(new CSReferenceExpression("string.Empty"));
            else
                pushExpression(new CSStringLiteralExpression("\"\""));
        } else {
            pushExpression(new CSStringLiteralExpression(convertStringLiteral(value)));
        }
        return false;
    }

    private static String convertStringLiteral(String stringLiteral) {
        StringBuilder sb = new StringBuilder();
        sb.append('"');
        if (needsEscaping(stringLiteral)) {
            for (int i = 0; i < stringLiteral.length(); i++) {
                String s = convertStringChar(stringLiteral.charAt(i));
                if (s == null)
                    sb.append(stringLiteral.charAt(i));
                else
                    sb.append(s);
            }
        } else {
            sb.append(stringLiteral);
        }
        sb.append('"');
        return sb.toString();
    }

    private static boolean needsEscaping(String s) {
        for (int i = 0; i < s.length(); i++)
            if (!isSafeChar(s.charAt(i)))
                return true;
        return false;
    }

    private static boolean isSafeChar(char c) {
        if (c >= 32 && c < 256 && isPrintableChar(c)) {
            switch (c) {
            case '\\':
            case '"':
                return false;
            default:
                return true;
            }
        }
        return false;
    }

    private static String convertStringChar(char c) {
        switch (c) {
        case 0:
            return "\\0";
        case '\t':
            return "\\t";
        case '\n':
            return "\\n";
        case '\r':
            return "\\r";
        case '\b':
            return "\\b";
        case '\f':
            return "\\f";
        case '\\':
            return "\\\\";
        case '"':
            return "\\\"";
        default:
            if (c >= 32 && c < 256 && isPrintableChar(c))
                return null;
            else
                return "\\u" + String.format("%04x", (int) c);
        }
    }

    public boolean visit(CharacterLiteral node) {
        CSExpression expr = new CSCharLiteralExpression(convertCharLiteral(node.charValue()));
        if (expectingType("byte")) {
            expr = new CSCastExpression(new CSTypeReference("byte"), new CSParenthesizedExpression(expr));
        }
        pushExpression(expr);
        return false;
    }

    private static String convertCharLiteral(char charLiteral) {
        switch (charLiteral) {
        case 0:
            return "\'\\0\'";
        case '\t':
            return "\'\\t\'";
        case '\n':
            return "\'\\n\'";
        case '\r':
            return "\'\\r\'";
        case '\b':
            return "\'\\b\'";
        case '\f':
            return "\'\\f\'";
        case '\\':
            return "\'\\\\\'";
        case '\'':
            return "\'\\'\'";
        default:
            String escapedValue;
            if (charLiteral >= 32 && charLiteral < 256 && isPrintableChar(charLiteral))
                escapedValue = String.valueOf(charLiteral);
            else
                escapedValue = "\\x" + String.format("%x", (int) charLiteral);
            return "\'" + escapedValue + "\'";
        }
    }

    public static boolean isPrintableChar(char c) {
        Character.UnicodeBlock block = Character.UnicodeBlock.of(c);
        return (!Character.isISOControl(c)) && c != java.awt.event.KeyEvent.CHAR_UNDEFINED && block != null
                && block != Character.UnicodeBlock.SPECIALS;
    }

    private boolean expectingType(String name) {
        return (_currentExpectedType != null && _currentExpectedType.getName().equals(name));
    }

    public boolean visit(NullLiteral node) {
        pushExpression(new CSNullLiteralExpression());
        return false;
    }

    public boolean visit(BooleanLiteral node) {
        pushExpression(new CSBoolLiteralExpression(node.booleanValue()));
        return false;
    }

    public boolean visit(ThisExpression node) {
        pushExpression(new CSThisExpression());
        return false;
    }

    public boolean visit(ArrayAccess node) {
        pushExpression(new CSIndexedExpression(mapExpression(node.getArray()), mapExpression(node.getIndex())));
        return false;
    }

    public boolean visit(ArrayCreation node) {
        ITypeBinding saved = pushExpectedType(node.getType().getElementType().resolveBinding());
        if (node.dimensions().size() > 1) {
            if (null != node.getInitializer()) {
                notImplemented(node);
            }
            pushExpression(unfoldMultiArrayCreation(node));
        } else {
            pushExpression(mapSingleArrayCreation(node));
        }
        popExpectedType(saved);
        return false;
    }

    /**
     * Unfolds java multi array creation shortcut "new String[2][3][2]" into
     * explicitly array creation "new string[][][] { new string[][] { new
     * string[2], new string[2], new string[2] }, new string[][] { new
     * string[2], new string[2], new string[2] } }"
     */
    private CSExpression unfoldMultiArrayCreation(ArrayCreation node) {
        boolean allDimsConst = true;
        for (int i = 0; i < node.dimensions().size() - 1; ++i) {
            if (getConstantDimensionExpression(node.dimensions().get(i)) == null) {
                allDimsConst = false;
                break;
            }
        }
        if (allDimsConst) {
            return unfoldMultiArray((ArrayType) node.getType().getComponentType(), node.dimensions(), 0);
        } else {
            return createMultiArray(node);
        }
    }

    private CSArrayCreationExpression unfoldMultiArray(ArrayType type, List dimensions, int dimensionIndex) {
        final CSArrayCreationExpression expression = new CSArrayCreationExpression(mappedTypeReference(type));
        expression.initializer(new CSArrayInitializerExpression());
        int length = resolveIntValue(dimensions.get(dimensionIndex));
        if (dimensionIndex < lastIndex(dimensions) - 1) {
            for (int i = 0; i < length; ++i) {
                expression.initializer().addExpression(
                        unfoldMultiArray((ArrayType) type.getComponentType(), dimensions, dimensionIndex + 1));
            }
        } else {
            Expression innerLength = (Expression) dimensions.get(dimensionIndex + 1);
            CSTypeReferenceExpression innerType = mappedTypeReference(type.getComponentType());
            for (int i = 0; i < length; ++i) {
                expression.initializer()
                        .addExpression(new CSArrayCreationExpression(innerType, mapExpression(innerLength)));
            }
        }
        return expression;
    }

    private CSExpression createMultiArray(ArrayCreation node) {
        Type type = node.getType().getElementType();
        String statement = String.format("Arrays.Create<%s>(%s", mappedTypeReference(type),
                node.dimensions().get(0));
        for (int i = 1; i < node.dimensions().size(); ++i) {
            statement = String.format("%s, %s", statement, node.dimensions().get(i));
        }
        statement = statement.concat(")");
        return new CSStringLiteralExpression(statement);
    }

    private Object getConstantDimensionExpression(Object expression) {
        return ((Expression) expression).resolveConstantExpressionValue();
    }

    private int resolveIntValue(Object expression) {
        return ((Number) getConstantDimensionExpression(expression)).intValue();
    }

    private int lastIndex(List<?> dimensions) {
        return dimensions.size() - 1;
    }

    private CSArrayCreationExpression mapSingleArrayCreation(ArrayCreation node) {
        CSArrayCreationExpression expression = new CSArrayCreationExpression(
                mappedTypeReference(componentType(node.getType())));
        if (!node.dimensions().isEmpty()) {
            expression.length(mapExpression((Expression) node.dimensions().get(0)));
        }
        expression.initializer(mapArrayInitializer(node));
        return expression;
    }

    private CSArrayInitializerExpression mapArrayInitializer(ArrayCreation node) {
        return (CSArrayInitializerExpression) mapExpression(node.getInitializer());
    }

    public boolean visit(ArrayInitializer node) {
        if (isImplicitelyTypedArrayInitializer(node)) {
            CSArrayCreationExpression ace = new CSArrayCreationExpression(
                    mappedTypeReference(node.resolveTypeBinding().getComponentType()));
            ITypeBinding saved = pushExpectedType(node.resolveTypeBinding().getElementType());
            ace.initializer(mapArrayInitializer(node));
            popExpectedType(saved);
            pushExpression(ace);
            return false;
        }

        pushExpression(mapArrayInitializer(node));
        return false;
    }

    private CSArrayInitializerExpression mapArrayInitializer(ArrayInitializer node) {
        CSArrayInitializerExpression initializer = new CSArrayInitializerExpression();
        for (Object e : node.expressions()) {
            initializer.addExpression(mapExpression((Expression) e));
        }
        return initializer;
    }

    private boolean isImplicitelyTypedArrayInitializer(ArrayInitializer node) {
        return !(node.getParent() instanceof ArrayCreation);
    }

    public ITypeBinding componentType(ArrayType type) {
        return type.getComponentType().resolveBinding();
    }

    @Override
    public boolean visit(EnhancedForStatement node) {
        CSForEachStatement stmt = new CSForEachStatement(node.getStartPosition(),
                mapExpression(node.getExpression()));
        stmt.variable(createParameter(node.getParameter()));
        visitBlock(stmt.body(), node.getBody());
        addStatement(stmt);
        return false;
    }

    public boolean visit(final ForStatement node) {
        consumeContinueLabel(new Function<CSBlock>() {
            public CSBlock apply() {
                ArrayList<CSExpression> initializers = new ArrayList<CSExpression>();
                for (Object i : node.initializers()) {
                    initializers.add(mapExpression((Expression) i));
                }
                CSForStatement stmt = new CSForStatement(node.getStartPosition(),
                        mapExpression(node.getExpression()));
                for (CSExpression i : initializers) {
                    stmt.addInitializer(i);
                }
                for (Object u : node.updaters()) {
                    stmt.addUpdater(mapExpression((Expression) u));
                }
                visitBlock(stmt.body(), node.getBody());
                addStatement(stmt);
                return stmt.body();
            }
        });
        return false;
    }

    private void consumeContinueLabel(Function<CSBlock> func) {
        CSLabelStatement label = _currentContinueLabel;
        _currentContinueLabel = null;
        CSBlock body = func.apply();
        if (label != null) {
            body.addStatement(label);
        }
    }

    public boolean visit(SwitchStatement node) {
        _currentContinueLabel = null;
        CSBlock saved = _currentBlock;

        ITypeBinding switchType = node.getExpression().resolveTypeBinding();
        CSSwitchStatement mappedNode = new CSSwitchStatement(node.getStartPosition(),
                mapExpression(node.getExpression()));
        addStatement(mappedNode);

        CSCaseClause defaultClause = null;
        CSCaseClause current = null;
        CSBlock openCaseBlock = null;
        _currentBlock = null;
        for (ASTNode element : Types.<Iterable<ASTNode>>cast(node.statements())) {
            if (ASTNode.SWITCH_CASE == element.getNodeType()) {
                if (null == current) {
                    if (_currentBlock != null) {
                        List<CSStatement> stats = _currentBlock.statements();
                        CSStatement lastStmt = stats.size() > 0 ? stats.get(stats.size() - 1) : null;
                        if (!(lastStmt instanceof CSThrowStatement) && !(lastStmt instanceof CSReturnStatement)
                                && !(lastStmt instanceof CSBreakStatement)
                                && !(lastStmt instanceof CSGotoStatement))
                            openCaseBlock = _currentBlock;
                    }
                    current = new CSCaseClause();
                    mappedNode.addCase(current);
                    _currentBlock = current.body();
                }
                SwitchCase sc = (SwitchCase) element;
                if (sc.isDefault()) {
                    defaultClause = current;
                    current.isDefault(true);
                    if (openCaseBlock != null)
                        openCaseBlock.addStatement(new CSGotoStatement(Integer.MIN_VALUE, "default"));
                } else {
                    ITypeBinding stype = pushExpectedType(switchType);
                    CSExpression caseExpression = mapExpression(sc.getExpression());
                    current.addExpression(caseExpression);
                    popExpectedType(stype);
                    if (openCaseBlock != null)
                        openCaseBlock.addStatement(new CSGotoStatement(Integer.MIN_VALUE, caseExpression));
                }
                openCaseBlock = null;
            } else {
                element.accept(this);
                current = null;
            }
        }

        if (openCaseBlock != null)
            openCaseBlock.addStatement(new CSBreakStatement(Integer.MIN_VALUE));

        if (null != defaultClause) {
            List<CSStatement> stats = defaultClause.body().statements();

            CSStatement lastStmt = stats.size() > 0 ? stats.get(stats.size() - 1) : null;
            if (!(lastStmt instanceof CSThrowStatement)) {
                defaultClause.body().addStatement(new CSBreakStatement(Integer.MIN_VALUE));
            }
        }

        _currentBlock = saved;
        return false;
    }

    public boolean visit(CastExpression node) {
        pushExpression(
                new CSCastExpression(mappedTypeReference(node.getType()), mapExpression(node.getExpression())));
        // Make all byte casts unchecked
        if (node.getType().resolveBinding().getName().equals("byte"))
            pushExpression(new CSUncheckedExpression(popExpression()));
        return false;
    }

    public boolean visit(PrefixExpression node) {
        CSExpression expr;
        expr = new CSPrefixExpression(node.getOperator().toString(), mapExpression(node.getOperand()));
        if (expectingType("byte") && node.getOperator() == PrefixExpression.Operator.MINUS) {
            expr = uncheckedCast("byte", expr);
        }
        pushExpression(expr);
        return false;
    }

    public boolean visit(PostfixExpression node) {
        pushExpression(new CSPostfixExpression(node.getOperator().toString(), mapExpression(node.getOperand())));
        return false;
    }

    public boolean visit(InfixExpression node) {

        CSExpression left = mapExpression(node.getLeftOperand());
        CSExpression right = mapExpression(node.getRightOperand());
        String type = node.getLeftOperand().resolveTypeBinding().getQualifiedName();
        if (node.getOperator() == InfixExpression.Operator.RIGHT_SHIFT_UNSIGNED) {
            if (type.equals("byte")) {
                pushExpression(new CSInfixExpression(">>", left, right));
            } else {
                CSExpression cast = new CSCastExpression(new CSTypeReference("u" + type), left);
                cast = new CSParenthesizedExpression(cast);
                CSExpression shiftResult = new CSInfixExpression(">>", cast, right);
                shiftResult = new CSParenthesizedExpression(shiftResult);
                pushExpression(new CSCastExpression(new CSTypeReference(type), shiftResult));
            }
            return false;
        }
        if (type.equals("byte") && (node.getOperator() == InfixExpression.Operator.LESS
                || node.getOperator() == InfixExpression.Operator.LESS_EQUALS)) {
            left = new CSCastExpression(new CSTypeReference("sbyte"), left);
            left = new CSParenthesizedExpression(left);
        }
        String operator = node.getOperator().toString();
        pushExpression(new CSInfixExpression(operator, left, right));
        pushExtendedOperands(operator, node);
        return false;
    }

    private void pushExtendedOperands(String operator, InfixExpression node) {
        for (Object x : node.extendedOperands()) {
            pushExpression(new CSInfixExpression(operator, popExpression(), mapExpression((Expression) x)));
        }
    }

    public boolean visit(ParenthesizedExpression node) {
        pushExpression(new CSParenthesizedExpression(mapExpression(node.getExpression())));
        return false;
    }

    public boolean visit(ConditionalExpression node) {
        pushExpression(new CSConditionalExpression(mapExpression(node.getExpression()),
                mapExpression(node.getThenExpression()), mapExpression(node.getElseExpression())));
        return false;
    }

    public boolean visit(InstanceofExpression node) {
        pushExpression(new CSInfixExpression("is", mapExpression(node.getLeftOperand()),
                mappedTypeReference(node.getRightOperand().resolveBinding())));
        return false;
    }

    public boolean visit(Assignment node) {
        Expression lhs = node.getLeftHandSide();
        Expression rhs = node.getRightHandSide();
        ITypeBinding lhsType = lhs.resolveTypeBinding();
        ITypeBinding saved = pushExpectedType(lhsType);

        if (node.getOperator() == Assignment.Operator.RIGHT_SHIFT_UNSIGNED_ASSIGN) {
            String type = lhsType.getQualifiedName();
            if (type == "byte") {
                pushExpression(new CSInfixExpression(">>", mapExpression(lhs),
                        mapExpression(lhs.resolveTypeBinding(), rhs)));
            } else {
                CSExpression mappedLhs = mapExpression(lhs);
                CSExpression cast = new CSCastExpression(new CSTypeReference("u" + type), mappedLhs);
                cast = new CSParenthesizedExpression(cast);
                CSExpression shiftResult = new CSInfixExpression(">>", cast, mapExpression(rhs));
                shiftResult = new CSParenthesizedExpression(shiftResult);
                shiftResult = new CSCastExpression(new CSTypeReference(type), shiftResult);
                pushExpression(new CSInfixExpression("=", mappedLhs, shiftResult));
            }
        } else {
            pushExpression(new CSInfixExpression(node.getOperator().toString(), mapExpression(lhs),
                    mapExpression(lhs.resolveTypeBinding(), rhs)));
        }
        popExpectedType(saved);
        return false;
    }

    private CSExpression mapExpression(ITypeBinding expectedType, Expression expression) {
        if (expectedType != null)
            return castIfNeeded(expectedType, expression.resolveTypeBinding(), mapExpression(expression));
        else
            return mapExpression(expression);
    }

    private CSExpression castIfNeeded(ITypeBinding expectedType, ITypeBinding actualType, CSExpression expression) {
        if (!_configuration.mapIteratorToEnumerator() && expectedType.getName().startsWith("Iterable<")
                && isGenericCollection(actualType)) {
            return new CSMethodInvocationExpression(new CSMemberReferenceExpression(expression, "AsIterable"));
        }
        if (expectedType != actualType && isSubclassOf(expectedType, actualType))
            return new CSCastExpression(mappedTypeReference(expectedType), expression);

        ITypeBinding charType = resolveWellKnownType("char");
        if (expectedType != charType)
            return expression;
        if (actualType == expectedType)
            return expression;
        return new CSCastExpression(mappedTypeReference(expectedType), expression);
    }

    private boolean isGenericCollection(ITypeBinding t) {
        return t.getName().startsWith("List<") || t.getName().startsWith("Set<");
    }

    private boolean isSubclassOf(ITypeBinding t, ITypeBinding tbase) {
        while (t != null) {
            if (t.isEqualTo(tbase))
                return true;
            t = t.getSuperclass();
        }
        return false;
    }

    public boolean visit(ClassInstanceCreation node) {
        if (null != node.getAnonymousClassDeclaration()) {
            node.getAnonymousClassDeclaration().accept(this);
            return false;
        }

        CSMethodInvocationExpression expression = mapConstructorInvocation(node);
        if (null == expression) {
            return false;
        }

        if (isNonStaticNestedTypeCreation(node)) {
            expression.addArgument(new CSThisExpression());
        }

        mapArguments(expression, node.arguments());
        pushExpression(expression);
        return false;
    }

    private boolean isNonStaticNestedTypeCreation(ClassInstanceCreation node) {
        return isNonStaticNestedType(node.resolveTypeBinding());
    }

    private CSMethodInvocationExpression mapConstructorInvocation(ClassInstanceCreation node) {
        Configuration.MemberMapping mappedConstructor = effectiveMappingFor(node.resolveConstructorBinding());
        if (null == mappedConstructor) {
            return new CSConstructorInvocationExpression(mappedTypeReference(node.resolveTypeBinding()));
        }
        final String mappedName = mappedConstructor.name;
        if (mappedName.length() == 0) {
            pushExpression(mapExpression((Expression) node.arguments().get(0)));
            return null;
        }
        if (mappedName.startsWith("System.Convert.To")) {
            if (optimizeSystemConvert(mappedName, node)) {
                return null;
            }
        }
        return new CSMethodInvocationExpression(new CSReferenceExpression(methodName(mappedName)));
    }

    private boolean optimizeSystemConvert(String mappedConstructor, ClassInstanceCreation node) {
        String typeName = _configuration.getConvertRelatedWellKnownTypeName(mappedConstructor);
        if (null != typeName) {
            assert 1 == node.arguments().size();
            Expression arg = (Expression) node.arguments().get(0);
            if (arg.resolveTypeBinding() == resolveWellKnownType(typeName)) {
                arg.accept(this);
                return true;
            }
        }
        return false;
    }

    public boolean visit(TypeLiteral node) {

        if (isReferenceToRemovedType(node.getType())) {
            pushExpression(new CSRemovedExpression(node.toString()));
            return false;
        }

        pushTypeOfExpression(mappedTypeReference(node.getType()));
        return false;
    }

    private boolean isReferenceToRemovedType(Type node) {
        BodyDeclaration typeDeclaration = findDeclaringNode(node.resolveBinding());
        if (null == typeDeclaration)
            return false;
        return hasRemoveAnnotation(typeDeclaration);
    }

    private void pushTypeOfExpression(CSTypeReferenceExpression type) {
        if (_configuration.nativeTypeSystem()) {
            pushExpression(new CSTypeofExpression(type));
        } else {
            pushGetClassForTypeExpression(type);
        }
    }

    private void pushGetClassForTypeExpression(final CSTypeReferenceExpression typeName) {
        CSMethodInvocationExpression mie = new CSMethodInvocationExpression(
                new CSReferenceExpression(methodName(_configuration.getRuntimeTypeName() + ".getClassForType")));
        mie.addArgument(new CSTypeofExpression(typeName));
        pushExpression(mie);
    }

    public boolean visit(MethodInvocation node) {

        IMethodBinding binding = originalMethodBinding(node.resolveMethodBinding());
        Configuration.MemberMapping mapping = mappingForInvocation(node, binding);

        if (null != mapping) {
            processMappedMethodInvocation(node, binding, mapping);
        } else {
            processUnmappedMethodInvocation(node);
        }

        return false;
    }

    public boolean visit(SuperMethodInvocation node) {
        if (null != node.getQualifier()) {
            notImplemented(node);
        }

        IMethodBinding binding = originalMethodBinding(node.resolveMethodBinding());
        Configuration.MemberMapping mapping = mappingForInvocation(node, binding);
        CSExpression target = new CSMemberReferenceExpression(new CSBaseExpression(), mappedMethodName(binding));

        if (mapping != null && mapping.kind != MemberKind.Method) {
            pushExpression(target);
            return false;
        }

        CSMethodInvocationExpression mie = new CSMethodInvocationExpression(target);
        mapArguments(mie, node.arguments());
        pushExpression(mie);
        return false;
    }

    private Configuration.MemberMapping mappingForInvocation(ASTNode node, IMethodBinding binding) {
        Configuration.MemberMapping mapping = effectiveMappingFor(binding);

        if (null == mapping) {
            if (isIndexer(binding)) {
                mapping = new MemberMapping(null, MemberKind.Indexer);
            } else if (isTaggedMethodInvocation(binding, SharpenAnnotations.SHARPEN_EVENT)) {
                mapping = new MemberMapping(binding.getName(), MemberKind.Property);
            } else if (isTaggedMethodInvocation(binding, SharpenAnnotations.SHARPEN_PROPERTY)) {
                mapping = new MemberMapping(propertyName(binding), MemberKind.Property);
            }
        }
        return mapping;
    }

    private boolean isIndexer(final IMethodBinding binding) {
        return isTaggedMethod(binding, SharpenAnnotations.SHARPEN_INDEXER);
    }

    private boolean isTaggedMethod(final IMethodBinding binding, final String tag) {
        final MethodDeclaration declaration = declaringNode(binding);
        if (null == declaration) {
            return false;
        }
        return isTaggedDeclaration(declaration, tag);
    }

    private IMethodBinding originalMethodBinding(IMethodBinding binding) {
        IMethodBinding original = BindingUtils.findMethodDefininition(binding, my(CompilationUnit.class).getAST());
        if (null != original)
            return original;
        return binding;
    }

    private void processUnmappedMethodInvocation(MethodInvocation node) {

        if (isMappedEventSubscription(node)) {
            processMappedEventSubscription(node);
            return;
        }

        if (isEventSubscription(node)) {
            processEventSubscription(node);
            return;
        }

        if (isRemovedMethodInvocation(node)) {
            processRemovedInvocation(node);
            return;
        }

        if (isUnwrapInvocation(node)) {
            processUnwrapInvocation(node);
            return;
        }

        if (isMacro(node)) {
            processMacroInvocation(node);
            return;
        }

        if (isEnumOrdinalMethodInvocation(node)) {
            processEnumOrdinalMethodInvocation(node);
            return;
        }

        if (isEnumNameMethodInvocation(node)) {
            processEnumNameMethodInvocation(node);
            return;
        }

        processOrdinaryMethodInvocation(node);
    }

    private boolean isMacro(MethodInvocation node) {
        return isTaggedMethodInvocation(node, SharpenAnnotations.SHARPEN_MACRO);
    }

    private void processMacroInvocation(MethodInvocation node) {
        final MethodDeclaration declaration = declaringNode(node.resolveMethodBinding());
        final TagElement macro = effectiveAnnotationFor(declaration, SharpenAnnotations.SHARPEN_MACRO);
        final CSMacro code = new CSMacro(JavadocUtility.singleTextFragmentFrom(macro));

        code.addVariable("expression", mapExpression(node.getExpression()));
        code.addVariable("arguments", mapExpressions(node.arguments()));

        pushExpression(new CSMacroExpression(code));
    }

    private List<CSExpression> mapExpressions(List expressions) {
        final ArrayList<CSExpression> result = new ArrayList<CSExpression>(expressions.size());
        for (Object expression : expressions) {
            result.add(mapExpression((Expression) expression));
        }
        return result;
    }

    private boolean isUnwrapInvocation(MethodInvocation node) {
        return isTaggedMethodInvocation(node, SharpenAnnotations.SHARPEN_UNWRAP);
    }

    private void processUnwrapInvocation(MethodInvocation node) {
        final List arguments = node.arguments();
        if (arguments.size() != 1) {
            unsupportedConstruct(node,
                    SharpenAnnotations.SHARPEN_UNWRAP + " only works against single argument methods.");
        }
        pushExpression(mapExpression((Expression) arguments.get(0)));
    }

    private void processOrdinaryMethodInvocation(MethodInvocation node) {
        IMethodBinding method = node.resolveMethodBinding();
        CSExpression targetExpression = mapMethodTargetExpression(node);
        if ((method.getModifiers() & Modifier.STATIC) != 0
                && !(targetExpression instanceof CSTypeReferenceExpression) && node.getExpression() != null)
            targetExpression = mappedTypeReference(node.getExpression().resolveTypeBinding());

        String name = resolveTargetMethodName(targetExpression, node);
        CSExpression target = null == targetExpression ? new CSReferenceExpression(name)
                : new CSMemberReferenceExpression(targetExpression, name);
        CSMethodInvocationExpression mie = new CSMethodInvocationExpression(target);
        mapMethodInvocationArguments(mie, node);
        mapTypeArguments(mie, node);

        IMethodBinding base = getOverridedMethod(method);
        if (base != null && base.getReturnType() != method.getReturnType()
                && !(node.getParent() instanceof ExpressionStatement))
            pushExpression(new CSParenthesizedExpression(
                    new CSCastExpression(mappedTypeReference(method.getReturnType()), mie)));
        else
            pushExpression(mie);
    }

    private String resolveTargetMethodName(CSExpression targetExpression, MethodInvocation node) {
        final IMethodBinding method = staticImportMethodBinding(node.getName(), _ast.imports());
        if (method != null && targetExpression == null) {
            return mappedTypeName(method.getDeclaringClass()) + "." + mappedMethodName(node.resolveMethodBinding());
        }
        return mappedMethodName(node.resolveMethodBinding());
    }

    private void mapTypeArguments(CSMethodInvocationExpression mie, MethodInvocation node) {
        for (Object o : node.typeArguments()) {
            mie.addTypeArgument(mappedTypeReference((Type) o));
        }
    }

    private void processMappedEventSubscription(MethodInvocation node) {

        final MethodInvocation event = (MethodInvocation) node.getExpression();
        final String eventArgsType = _configuration.mappedEvent(qualifiedName(event));
        final String eventHandlerType = buildEventHandlerTypeName(node, eventArgsType);
        mapEventSubscription(node, eventArgsType, eventHandlerType);
    }

    private void processRemovedInvocation(MethodInvocation node) {
        TagElement element = javadocTagFor(declaringNode(node.resolveMethodBinding()),
                SharpenAnnotations.SHARPEN_REMOVE);

        String exchangeValue = JavadocUtility.singleTextFragmentFrom(element);
        pushExpression(new CSReferenceExpression(exchangeValue));
    }

    private void processEnumOrdinalMethodInvocation(MethodInvocation node) {
        CSExpression exp = mapExpression(node.getExpression());
        pushExpression(new CSCastExpression(new CSTypeReference("int"), new CSParenthesizedExpression(exp)));
    }

    private void processEnumNameMethodInvocation(MethodInvocation node) {
        CSExpression exp = mapExpression(node.getExpression());
        pushExpression(new CSMethodInvocationExpression(new CSMemberReferenceExpression(exp, "ToString")));
    }

    private void mapMethodInvocationArguments(CSMethodInvocationExpression mie, MethodInvocation node) {
        final List arguments = node.arguments();
        final IMethodBinding actualMethod = node.resolveMethodBinding();
        final ITypeBinding[] actualTypes = actualMethod.getParameterTypes();
        final IMethodBinding originalMethod = actualMethod.getMethodDeclaration();
        final ITypeBinding[] originalTypes = originalMethod.getParameterTypes();
        for (int i = 0; i < arguments.size(); ++i) {
            final Expression arg = (Expression) arguments.get(i);
            if (i < originalTypes.length && isGenericRuntimeParameterIdiom(originalMethod, originalTypes[i])
                    && isClassLiteral(arg)) {
                mie.addTypeArgument(genericRuntimeTypeIdiomType(actualTypes[i]));
            } else {
                addArgument(mie, arg, i < actualTypes.length ? actualTypes[i] : null);
            }
        }
        adjustJUnitArguments(mie, node);
    }

    private void adjustJUnitArguments(CSMethodInvocationExpression mie, MethodInvocation node) {
        if (!_configuration.junitConversion())
            return;
        ITypeBinding t = node.resolveMethodBinding().getDeclaringClass();
        if (t.getQualifiedName().equals("junit.framework.Assert")
                || t.getQualifiedName().equals("org.junit.Assert")) {
            String method = node.getName().getIdentifier();
            int np = -1;

            if (method.equals("assertTrue") || method.equals("assertFalse") || method.equals("assertNull")
                    || method.equals("assertNotNull"))
                np = 1;
            else if (method.equals("fail"))
                np = 0;
            else if (method.startsWith("assert"))
                np = 2;

            if (np == -1)
                return;

            if (mie.arguments().size() == np + 1) {
                // Move the comment argument to the end
                mie.addArgument(mie.arguments().get(0));
                mie.removeArgument(0);
            }

            if (method.equals("assertSame")) {
                boolean useEquals = false;
                final List arguments = node.arguments();
                for (int i = 0; i < arguments.size(); ++i) {
                    final Expression arg = (Expression) arguments.get(i);
                    ITypeBinding b = arg.resolveTypeBinding();
                    if (b.isEnum()) {
                        useEquals = true;
                        break;
                    }
                }
                if (useEquals) {
                    CSReferenceExpression mref = (CSReferenceExpression) mie.expression();
                    mref.name("NUnit.Framework.Assert.AreEqual");
                }
            }
        }
    }

    private boolean isClassLiteral(Expression arg) {
        return arg.getNodeType() == ASTNode.TYPE_LITERAL;
    }

    private void processEventSubscription(MethodInvocation node) {

        final MethodDeclaration addListener = declaringNode(node.resolveMethodBinding());
        assertValidEventAddListener(node, addListener);

        final MethodInvocation eventInvocation = (MethodInvocation) node.getExpression();

        final MethodDeclaration eventDeclaration = declaringNode(eventInvocation.resolveMethodBinding());
        mapEventSubscription(node, getEventArgsType(eventDeclaration), getEventHandlerTypeName(eventDeclaration));
    }

    private void mapEventSubscription(MethodInvocation node, final String eventArgsType,
            final String eventHandlerType) {
        final CSAnonymousClassBuilder listenerBuilder = mapAnonymousEventListener(node);
        final CSMemberReferenceExpression handlerMethodRef = new CSMemberReferenceExpression(
                listenerBuilder.createConstructorInvocation(), eventListenerMethodName(listenerBuilder));

        final CSReferenceExpression delegateType = new CSReferenceExpression(eventHandlerType);

        patchEventListener(listenerBuilder, eventArgsType);

        CSConstructorInvocationExpression delegateConstruction = new CSConstructorInvocationExpression(
                delegateType);
        delegateConstruction.addArgument(handlerMethodRef);

        pushExpression(new CSInfixExpression("+=", mapMethodTargetExpression(node), delegateConstruction));
    }

    private CSAnonymousClassBuilder mapAnonymousEventListener(MethodInvocation node) {
        ClassInstanceCreation creation = (ClassInstanceCreation) node.arguments().get(0);
        return mapAnonymousClass(creation.getAnonymousClassDeclaration());
    }

    private String eventListenerMethodName(final CSAnonymousClassBuilder listenerBuilder) {
        return mappedMethodName(getFirstMethod(listenerBuilder.anonymousBaseType()));
    }

    private void patchEventListener(CSAnonymousClassBuilder listenerBuilder, String eventArgsType) {
        final CSClass type = listenerBuilder.type();
        type.clearBaseTypes();

        final CSMethod handlerMethod = (CSMethod) type.getMember(eventListenerMethodName(listenerBuilder));
        handlerMethod.parameters().get(0).type(OBJECT_TYPE_REFERENCE);
        handlerMethod.parameters().get(0).name("sender");
        handlerMethod.parameters().get(1).type(new CSTypeReference(eventArgsType));

    }

    private IMethodBinding getFirstMethod(ITypeBinding listenerType) {
        return listenerType.getDeclaredMethods()[0];
    }

    private void assertValidEventAddListener(ASTNode source, MethodDeclaration addListener) {
        if (isValidEventAddListener(addListener))
            return;

        unsupportedConstruct(source,
                SharpenAnnotations.SHARPEN_EVENT_ADD + " must take lone single method interface argument");
    }

    private boolean isValidEventAddListener(MethodDeclaration addListener) {
        if (1 != addListener.parameters().size())
            return false;

        final ITypeBinding type = getFirstParameterType(addListener);
        if (!type.isInterface())
            return false;

        return type.getDeclaredMethods().length == 1;
    }

    private ITypeBinding getFirstParameterType(MethodDeclaration addListener) {
        return parameter(addListener, 0).getType().resolveBinding();
    }

    private SingleVariableDeclaration parameter(MethodDeclaration method, final int index) {
        return (SingleVariableDeclaration) method.parameters().get(index);
    }

    private boolean isEventSubscription(MethodInvocation node) {
        return isTaggedMethodInvocation(node, SharpenAnnotations.SHARPEN_EVENT_ADD);
    }

    private boolean isMappedEventSubscription(MethodInvocation node) {
        return _configuration.isMappedEventAdd(qualifiedName(node));
    }

    private String qualifiedName(MethodInvocation node) {
        return qualifiedName(node.resolveMethodBinding());
    }

    private boolean isTaggedMethodInvocation(MethodInvocation node, final String tag) {
        return isTaggedMethodInvocation(node.resolveMethodBinding(), tag);
    }

    private boolean isTaggedMethodInvocation(final IMethodBinding binding, final String tag) {
        final MethodDeclaration method = declaringNode(originalMethodBinding(binding));
        if (null == method) {
            return false;
        }
        return containsJavadoc(method, tag);
    }

    @SuppressWarnings("unchecked")
    private void processMappedMethodInvocation(MethodInvocation node, IMethodBinding binding,
            Configuration.MemberMapping mapping) {

        if (mapping.kind == MemberKind.Indexer) {
            processIndexerInvocation(node, binding, mapping);
            return;
        }

        String name = mappedMethodName(binding);
        if (0 == name.length()) {
            final Expression expression = node.getExpression();
            final CSExpression target = expression != null ? mapExpression(expression) : new CSThisExpression(); // see
            // collections/EntrySet1
            pushExpression(target);
            return;
        }

        boolean isMappingToStaticMethod = isMappingToStaticMember(name);

        List<Expression> arguments = node.arguments();
        CSExpression expression = mapMethodTargetExpression(node);
        CSExpression target = null;

        if (null == expression || isMappingToStaticMethod) {
            target = new CSReferenceExpression(name);
        } else {
            if (BindingUtils.isStatic(binding) && arguments.size() > 0) {
                // mapping static method to instance member
                // typical example is String.valueOf(arg) => arg.ToString()
                target = new CSMemberReferenceExpression(parensIfNeeded(mapExpression(arguments.get(0))), name);
                arguments = arguments.subList(1, arguments.size());
            } else {
                target = new CSMemberReferenceExpression(expression, name);
            }
        }

        if (mapping.kind != MemberKind.Method) {
            IMethodBinding originalBinding = node.resolveMethodBinding();
            if (binding != originalBinding && originalBinding.getReturnType() != binding.getReturnType()
                    && !(node.getParent() instanceof ExpressionStatement))
                target = new CSParenthesizedExpression(
                        new CSCastExpression(mappedTypeReference(originalBinding.getReturnType()), target));
            switch (arguments.size()) {
            case 0:
                pushExpression(target);
                break;

            case 1:
                pushExpression(new CSInfixExpression("=", target, mapExpression(arguments.get(0))));
                break;

            default:
                unsupportedConstruct(node, "Method invocation with more than 1 argument mapped to property");
                break;
            }
            return;
        }

        CSMethodInvocationExpression mie = new CSMethodInvocationExpression(target);
        if (isMappingToStaticMethod && isInstanceMethod(binding)) {
            if (null == expression) {
                mie.addArgument(new CSThisExpression());
            } else {
                mie.addArgument(expression);
            }
        }
        mapArguments(mie, arguments);
        adjustJUnitArguments(mie, node);
        pushExpression(mie);
    }

    private void processIndexerInvocation(MethodInvocation node, IMethodBinding binding, MemberMapping mapping) {
        if (node.arguments().size() == 1) {
            processIndexerGetter(node);
        } else {
            processIndexerSetter(node);
        }
    }

    private void processIndexerSetter(MethodInvocation node) {
        // target(arg0 ... argN) => target[arg0... argN-1] = argN;

        final CSIndexedExpression indexer = new CSIndexedExpression(mapIndexerTarget(node));
        final List arguments = node.arguments();
        final Expression lastArgument = (Expression) arguments.get(arguments.size() - 1);
        for (int i = 0; i < arguments.size() - 1; ++i) {
            indexer.addIndex(mapExpression((Expression) arguments.get(i)));
        }
        pushExpression(CSharpCode.newAssignment(indexer, mapExpression(lastArgument)));

    }

    private void processIndexerGetter(MethodInvocation node) {
        final Expression singleArgument = (Expression) node.arguments().get(0);
        pushExpression(new CSIndexedExpression(mapIndexerTarget(node), mapExpression(singleArgument)));
    }

    private CSExpression mapIndexerTarget(MethodInvocation node) {
        if (node.getExpression() == null) {
            return new CSThisExpression();
        }
        return mapMethodTargetExpression(node);
    }

    private CSExpression parensIfNeeded(CSExpression expression) {
        if (expression instanceof CSInfixExpression || expression instanceof CSPrefixExpression
                || expression instanceof CSPostfixExpression) {

            return new CSParenthesizedExpression(expression);
        }
        return expression;
    }

    protected CSExpression mapMethodTargetExpression(MethodInvocation node) {
        return mapExpression(node.getExpression());
    }

    private boolean isInstanceMethod(IMethodBinding binding) {
        return !BindingUtils.isStatic(binding);
    }

    private boolean isMappingToStaticMember(String name) {
        return -1 != name.indexOf('.');
    }

    protected void mapArguments(CSMethodInvocationExpression mie, List arguments) {
        for (Object arg : arguments) {
            addArgument(mie, (Expression) arg, null);
        }
    }

    private void addArgument(CSMethodInvocationExpression mie, Expression arg, ITypeBinding expectedType) {
        mie.addArgument(mapExpression(expectedType, arg));
    }

    public boolean visit(FieldAccess node) {
        String name = mappedFieldName(node);
        if (null == node.getExpression()) {
            pushExpression(new CSReferenceExpression(name));
        } else {
            pushExpression(new CSMemberReferenceExpression(mapExpression(node.getExpression()), name));
        }
        return false;
    }

    String mapVariableName(String name) {
        if (_renamedVariables.size() > 0) {
            String vname = name;
            if (vname.startsWith("@"))
                vname = vname.substring(1);
            String newName = _renamedVariables.peek().get(vname);
            if (newName != null)
                return newName;
        }
        return name;
    }

    private boolean isBoolLiteral(String name) {
        return name.equals("true") || name.equals("false");
    }

    private String mappedFieldName(FieldAccess node) {
        String name = mappedFieldName(node.getName());
        if (null != name)
            return name;
        return identifier(node.getName());
    }

    public boolean visit(SimpleName node) {
        if (isTypeReference(node)) {
            pushTypeReference(node.resolveTypeBinding());
        } else if (_currentExpression == null) {
            String ident = mappedFieldName(node);
            if (ident == null)
                ident = mapVariableName(identifier(node));
            IBinding b = node.resolveBinding();
            IVariableBinding vb = b instanceof IVariableBinding ? (IVariableBinding) b : null;
            if (vb != null) {
                ITypeBinding cls = vb.getDeclaringClass();
                if (cls != null) {
                    if (isStaticImport(vb, _ast.imports())) {
                        if (cls != null) {
                            pushExpression(new CSMemberReferenceExpression(mappedTypeReference(cls), ident));
                            return false;
                        }
                    } else if (cls.isEnum() && ident.indexOf('.') == -1) {
                        pushExpression(new CSMemberReferenceExpression(mappedTypeReference(cls), ident));
                        return false;
                    } else if (cls.isInterface()) {
                        CSTypeReferenceExpression interfaceReference = mappedTypeReference(cls, true);
                        pushExpression(new CSMemberReferenceExpression(interfaceReference, ident));
                        return false;
                    }
                }
            }
            pushExpression(new CSReferenceExpression(ident));
        }
        return false;
    }

    private void addStatement(CSStatement statement) {
        _currentBlock.addStatement(statement);
    }

    private void pushTypeReference(ITypeBinding typeBinding) {
        pushExpression(mappedTypeReference(typeBinding));
    }

    protected CSReferenceExpression createTypeReference(ITypeBinding typeBinding) {
        return new CSReferenceExpression(mappedTypeName(typeBinding));
    }

    private boolean isTypeReference(Name node) {
        final IBinding binding = node.resolveBinding();
        if (null == binding) {
            unresolvedTypeBinding(node);
            return false;
        }
        return IBinding.TYPE == binding.getKind();
    }

    public boolean visit(QualifiedName node) {
        if (isTypeReference(node)) {
            pushTypeReference(node.resolveTypeBinding());
        } else {
            String primitiveTypeRef = checkForPrimitiveTypeReference(node);
            if (primitiveTypeRef != null) {
                pushTypeOfExpression(new CSTypeReference(primitiveTypeRef));
            } else {
                handleRegularQualifiedName(node);
            }
        }
        return false;
    }

    private void handleRegularQualifiedName(QualifiedName node) {
        String mapped = mappedFieldName(node);
        if (null != mapped) {
            if (isBoolLiteral(mapped)) {
                pushExpression(new CSBoolLiteralExpression(Boolean.parseBoolean(mapped)));
                return;
            }
            if (isMappingToStaticMember(mapped)) {
                pushExpression(new CSReferenceExpression(mapped));
            } else {
                pushMemberReferenceExpression(node.getQualifier(), mapped);
            }
        } else {
            Name qualifier = node.getQualifier();
            String name = identifier(node.getName().getIdentifier());
            pushMemberReferenceExpression(qualifier, name);
        }
    }

    private String checkForPrimitiveTypeReference(QualifiedName node) {
        String name = qualifiedName(node);
        if (name.equals(JAVA_LANG_VOID_TYPE))
            return "void";
        if (name.equals(JAVA_LANG_BOOLEAN_TYPE))
            return "bool";
        if (name.equals(JAVA_LANG_BYTE_TYPE)) {
            return _configuration.mapByteToSbyte() ? "sbyte" : "byte";
        }
        if (name.equals(JAVA_LANG_CHARACTER_TYPE))
            return "char";
        if (name.equals(JAVA_LANG_SHORT_TYPE))
            return "short";
        if (name.equals(JAVA_LANG_INTEGER_TYPE))
            return "int";
        if (name.equals(JAVA_LANG_LONG_TYPE))
            return "long";
        if (name.equals(JAVA_LANG_FLOAT_TYPE))
            return "float";
        if (name.equals(JAVA_LANG_DOUBLE_TYPE))
            return "double";
        return null;
    }

    private String qualifiedName(QualifiedName node) {
        IVariableBinding binding = variableBinding(node);
        if (binding == null)
            return node.toString();
        return BindingUtils.qualifiedName(binding);
    }

    private void pushMemberReferenceExpression(Name qualifier, String name) {
        CSExpression expr = mapExpression(qualifier);
        if (expr instanceof CSTypeReference) {
            String typeName = ((CSTypeReference) expr).typeName();
            String fixedTypeName = interfaceStaticsClassName(typeName, true, qualifier.resolveTypeBinding());
            if (!fixedTypeName.equals(typeName)) {
                expr = new CSTypeReference(fixedTypeName);
            }
        }
        pushExpression(new CSMemberReferenceExpression(expr, name));
    }

    private IVariableBinding variableBinding(Name node) {
        if (node.resolveBinding() instanceof IVariableBinding) {
            return (IVariableBinding) node.resolveBinding();
        }
        return null;
    }

    private String mappedFieldName(Name node) {
        IVariableBinding binding = variableBinding(node);
        return null == binding ? null : my(Mappings.class).mappedFieldName(binding);
    }

    protected CSExpression mapExpression(Expression expression) {
        if (null == expression)
            return null;

        try {
            expression.accept(this);
            return popExpression();
        } catch (Exception e) {
            unsupportedConstruct(expression, e);
            return null; // we'll never get here
        }
    }

    private void unsupportedConstruct(ASTNode node, Exception cause) {
        unsupportedConstruct(node, "failed to map: '" + node + "'", cause);
    }

    private void unsupportedConstruct(ASTNode node, String message) {
        unsupportedConstruct(node, message, null);
    }

    private void unsupportedConstruct(ASTNode node, final String message, Exception cause) {
        throw new IllegalArgumentException(sourceInformation(node) + ": " + message, cause);
    }

    private ITypeBinding pushExpectedType(ITypeBinding type) {
        ITypeBinding old = _currentExpectedType;
        _currentExpectedType = type;
        return old;
    }

    private void popExpectedType(ITypeBinding saved) {
        _currentExpectedType = saved;
    }

    protected void pushExpression(CSExpression expression) {
        if (null != _currentExpression) {
            throw new IllegalStateException();
        }
        _currentExpression = expression;
    }

    private CSExpression popExpression() {
        if (null == _currentExpression) {
            throw new IllegalStateException();
        }
        CSExpression found = _currentExpression;
        _currentExpression = null;
        return found;
    }

    private CSVariableDeclaration createParameter(SingleVariableDeclaration declaration) {
        return createVariableDeclaration(declaration.resolveBinding(), null);
    }

    protected void visit(List nodes) {
        for (Object node : nodes) {
            ((ASTNode) node).accept(this);
        }
    }

    private void createInheritedAbstractMemberStubs(TypeDeclaration node) {
        if (node.isInterface())
            return;

        ITypeBinding binding = node.resolveBinding();
        if (!Modifier.isAbstract(node.getModifiers()))
            return;

        Set<ITypeBinding> interfaces = new LinkedHashSet<ITypeBinding>();
        collectInterfaces(interfaces, binding);
        for (ITypeBinding baseType : interfaces) {
            createInheritedAbstractMemberStubs(binding, baseType);
        }
    }

    private void collectInterfaces(Set<ITypeBinding> interfaceList, ITypeBinding binding) {
        ITypeBinding[] interfaces = binding.getInterfaces();
        for (int i = 0; i < interfaces.length; ++i) {
            ITypeBinding interfaceBinding = interfaces[i];
            if (interfaceList.contains(interfaceBinding)) {
                continue;
            }
            collectInterfaces(interfaceList, interfaceBinding);
            interfaceList.add(interfaceBinding);
        }
    }

    private void createInheritedAbstractMemberStubs(ITypeBinding type, ITypeBinding baseType) {
        IMethodBinding[] methods = baseType.getDeclaredMethods();
        for (int i = 0; i < methods.length; ++i) {
            IMethodBinding method = methods[i];
            if (!Modifier.isAbstract(method.getModifiers())) {
                continue;
            }
            if (null != BindingUtils.findOverriddenMethodInTypeOrSuperclasses(type, method)) {
                continue;
            }
            if (isIgnored(originalMethodBinding(method))) {
                continue;
            }
            if (stubIsProperty(method)) {
                _currentType.addMember(createAbstractPropertyStub(method));
            } else {
                CSMethod newMethod = createAbstractMethodStub(method);
                //the same method might be defined in multiple interfaces
                //but only a single stub must be created for those
                if (!_currentType.members().contains(newMethod)) {
                    _currentType.addMember(newMethod);
                }
            }
        }
    }

    private boolean isIgnored(IMethodBinding binding) {
        final MethodDeclaration dec = declaringNode(binding);
        return dec != null && SharpenAnnotations.hasIgnoreAnnotation(dec);
    }

    private boolean stubIsProperty(IMethodBinding method) {
        final MethodDeclaration dec = declaringNode(method);
        return dec != null && isProperty(dec);
    }

    private MethodDeclaration declaringNode(IMethodBinding method) {
        return findDeclaringNode(method);
    }

    private CSProperty createAbstractPropertyStub(IMethodBinding method) {
        CSProperty stub = newAbstractPropertyStubFor(method);
        safeProcessDisableTags(method, stub);

        return stub;
    }

    private CSProperty newAbstractPropertyStubFor(IMethodBinding method) {
        CSProperty stub = new CSProperty(mappedMethodName(method), mappedTypeReference(method.getReturnType()));
        stub.modifier(CSMethodModifier.Abstract);
        stub.visibility(mapVisibility(method.getModifiers()));
        stub.getter(new CSBlock());
        return stub;
    }

    private CSMethod createAbstractMethodStub(IMethodBinding method) {
        CSMethod stub = newAbstractMethodStubFor(method);
        safeProcessDisableTags(method, stub);

        return stub;
    }

    private CSMethod newAbstractMethodStubFor(IMethodBinding method) {
        CSMethod stub = new CSMethod(mappedMethodName(method));

        stub.modifier(CSMethodModifier.Abstract);
        stub.visibility(mapVisibility(method.getModifiers()));
        stub.returnType(mappedTypeReference(method.getReturnType()));

        ITypeBinding[] parameters = method.getParameterTypes();
        for (int i = 0; i < parameters.length; ++i) {
            stub.addParameter(new CSVariableDeclaration("arg" + (i + 1), mappedTypeReference(parameters[i])));
        }
        return stub;
    }

    private void safeProcessDisableTags(IMethodBinding method, CSMember member) {
        final MethodDeclaration node = declaringNode(method);
        if (node == null)
            return;

        processDisableTags(node, member);
    }

    CSMethodModifier mapMethodModifier(MethodDeclaration method) {
        if (_currentType.isInterface() || method.resolveBinding().getDeclaringClass().isInterface()) {
            return CSMethodModifier.Abstract;
        }
        int modifiers = method.getModifiers();
        if (Modifier.isStatic(modifiers)) {
            return CSMethodModifier.Static;
        }
        if (Modifier.isPrivate(modifiers)) {
            return CSMethodModifier.None;
        }

        boolean override = isOverride(method);
        if (Modifier.isAbstract(modifiers)) {
            return override ? CSMethodModifier.AbstractOverride : CSMethodModifier.Abstract;
        }
        boolean isFinal = Modifier.isFinal(modifiers);
        if (override) {
            return isFinal ? CSMethodModifier.Sealed
                    : modifierIfNewAnnotationNotApplied(method, CSMethodModifier.Override);
        }
        return isFinal || _currentType.isSealed() ? CSMethodModifier.None : CSMethodModifier.Virtual;
    }

    private CSMethodModifier modifierIfNewAnnotationNotApplied(MethodDeclaration method,
            CSMethodModifier modifier) {
        return containsJavadoc(method, SharpenAnnotations.SHARPEN_NEW) ? CSMethodModifier.None : modifier;
    }

    private boolean isExtractedNestedType(ITypeBinding type) {
        return _configuration.typeHasMapping(BindingUtils.typeMappingKey(type));
    }

    private boolean isOverride(MethodDeclaration method) {
        return null != getOverridedMethod(method);
    }

    private IMethodBinding getOverridedMethod(MethodDeclaration method) {
        return getOverridedMethod(method.resolveBinding());
    }

    private IMethodBinding getOverridedMethod(IMethodBinding methodBinding) {
        ITypeBinding superclass = _ignoreExtends.value() ? resolveWellKnownType("java.lang.Object")
                : methodBinding.getDeclaringClass().getSuperclass();
        if (null != superclass) {
            IMethodBinding result = BindingUtils.findOverriddenMethodInHierarchy(superclass, methodBinding);
            if (null != result)
                return result;
        }
        ITypeBinding[] baseInterfaces = methodBinding.getDeclaringClass().getInterfaces();
        if (baseInterfaces.length == 1 && !isValidCSInterface(baseInterfaces[0])) {
            // Base interface generated as a class
            return BindingUtils.findOverriddenMethodInType(baseInterfaces[0], methodBinding);
        }
        return null;
    }

    private boolean isValidCSInterface(ITypeBinding type) {
        if (type.getTypeDeclaration().getQualifiedName().equals("java.util.Iterator")
                || type.getTypeDeclaration().getQualifiedName().equals("java.lang.Iterable"))
            return false;
        return true;
    }

    CSClassModifier mapClassModifier(int modifiers) {
        if (Modifier.isAbstract(modifiers)) {
            return CSClassModifier.Abstract;
        }
        if (Modifier.isFinal(modifiers)) {
            return CSClassModifier.Sealed;
        }
        return CSClassModifier.None;
    }

    void adjustVisibility(ITypeBinding memberType, CSMember member) {
        if (memberType == null)
            return;
        CSVisibility typeVisibility = mapVisibility(memberType.getModifiers());
        if (typeVisibility == CSVisibility.Protected && member.visibility() == CSVisibility.Internal)
            member.visibility(CSVisibility.Protected);
    }

    CSVisibility mapVisibility(BodyDeclaration node) {
        if (containsJavadoc(node, SharpenAnnotations.SHARPEN_INTERNAL)) {
            return CSVisibility.Internal;
        }

        if (containsJavadoc(node, SharpenAnnotations.SHARPEN_PRIVATE)) {
            return CSVisibility.Private;
        }

        if (containsJavadoc(node, SharpenAnnotations.SHARPEN_PROTECTED)) {
            return CSVisibility.Protected;
        }

        if (containsJavadoc(node, SharpenAnnotations.SHARPEN_PUBLIC)) {
            return CSVisibility.Public;
        }

        return mapVisibility(node.getModifiers());
    }

    CSVisibility mapVisibility(int modifiers) {
        if (Modifier.isPublic(modifiers)) {
            return CSVisibility.Public;
        }
        if (Modifier.isProtected(modifiers)) {
            return _configuration.mapProtectedToProtectedInternal() ? CSVisibility.ProtectedInternal
                    : CSVisibility.Protected;
        }
        if (Modifier.isPrivate(modifiers)) {
            return CSVisibility.Private;
        }
        return CSVisibility.Internal;
    }

    protected CSTypeReferenceExpression mappedTypeReference(Type type) {
        return mappedTypeReference(type.resolveBinding());
    }

    private CSTypeReferenceExpression mappedMacroTypeReference(ITypeBinding typeUsage,
            final TypeDeclaration typeDeclaration) {

        final CSMacro macro = new CSMacro(JavadocUtility
                .singleTextFragmentFrom(javadocTagFor(typeDeclaration, SharpenAnnotations.SHARPEN_MACRO)));

        final ITypeBinding[] typeArguments = typeUsage.getTypeArguments();
        if (typeArguments.length > 0) {
            final ITypeBinding[] typeParameters = typeUsage.getTypeDeclaration().getTypeParameters();
            for (int i = 0; i < typeParameters.length; i++) {
                macro.addVariable(typeParameters[i].getName(), mappedTypeReference(typeArguments[i]));
            }
        }

        return new CSMacroTypeReference(macro);
    }

    private boolean isMacroType(final ASTNode declaration) {
        return declaration instanceof TypeDeclaration
                && containsJavadoc((TypeDeclaration) declaration, SharpenAnnotations.SHARPEN_MACRO);
    }

    protected CSTypeReferenceExpression mappedTypeReference(ITypeBinding type) {
        return mappedTypeReference(type, false);
    }

    protected CSTypeReferenceExpression mappedTypeReference(ITypeBinding type, boolean mapInterfaceStaticsClass) {
        final ASTNode declaration = findDeclaringNode(type);
        if (isMacroType(declaration)) {
            return mappedMacroTypeReference(type, (TypeDeclaration) declaration);
        }

        if (type.isArray()) {
            return mappedArrayTypeReference(type);
        }
        if (type.isWildcardType()) {
            return mappedWildcardTypeReference(type);
        }
        String mappedName = mappedTypeName(type);
        mappedName = interfaceStaticsClassName(mappedName, mapInterfaceStaticsClass, type);
        final CSTypeReference typeRef = new CSTypeReference(mappedName);
        if (isJavaLangClass(type)) {
            return typeRef;
        }
        for (ITypeBinding arg : type.getTypeArguments()) {
            typeRef.addTypeArgument(mappedTypeReference(arg));
        }
        return typeRef;
    }

    private String interfaceStaticsClassName(String typeName, boolean modifyLastPart, ITypeBinding type) {
        if (!modifyLastPart && typeName.indexOf('.') < 0) {
            return typeName;
        } else {
            String[] parts = typeName.split("\\.");
            int start = parts.length - 1;
            if (!modifyLastPart) {
                start--;
                type = type.getDeclaringClass();
            }
            for (int i = start; i >= 0; i--) {
                if (type != null && type.isInterface()) {
                    type = type.getDeclaringClass();
                    parts[i] = interfaceStaticsClassNameGen(parts[i]);
                }
            }

            StringBuilder sb = new StringBuilder();
            boolean addDot = false;
            for (String name : parts) {
                if (addDot)
                    sb.append('.');
                else
                    addDot = true;

                sb.append(name);
            }

            return sb.toString();
        }
    }

    private String interfaceStaticsClassNameGen(String name) {
        if (name.length() > 2 && name.charAt(0) == 'I' && Character.isUpperCase(name.charAt(1)))
            name = name.substring(1);

        String suffix = "Constants";
        if (!name.endsWith(suffix))
            return name + suffix;
        else
            return name;
    }

    private boolean isJavaLangClass(ITypeBinding type) {
        return type.getErasure() == javaLangClassBinding();
    }

    private ITypeBinding javaLangClassBinding() {
        return resolveWellKnownType("java.lang.Class");
    }

    private CSTypeReferenceExpression mappedWildcardTypeReference(ITypeBinding type) {
        final ITypeBinding bound = type.getBound();
        return bound != null ? mappedTypeReference(bound) : OBJECT_TYPE_REFERENCE;
    }

    private CSTypeReferenceExpression mappedArrayTypeReference(ITypeBinding type) {
        return new CSArrayTypeReference(mappedTypeReference(type.getElementType()), type.getDimensions());

    }

    protected final String mappedTypeName(ITypeBinding type) {
        return my(Mappings.class).mappedTypeName(type);
    }

    private static String qualifiedName(ITypeBinding type) {
        return BindingUtils.qualifiedName(type);
    }

    private String interfaceName(String name) {
        return my(Configuration.class).toInterfaceName(name);
    }

    private String mappedTypeName(String typeName) {
        return mappedTypeName(typeName, typeName);
    }

    private String mappedTypeName(String typeName, String defaultValue) {
        return _configuration.mappedTypeName(typeName, defaultValue);
    }

    private String annotatedRenaming(BodyDeclaration node) {
        return my(Annotations.class).annotatedRenaming(node);
    }

    protected String mappedMethodName(MethodDeclaration node) {
        return mappedMethodName(node.resolveBinding());
    }

    protected final String mappedMethodName(IMethodBinding binding) {
        return my(Mappings.class).mappedMethodName(binding);
    }

    private String qualifiedName(IMethodBinding actual) {
        return BindingUtils.qualifiedName(actual);
    }

    private boolean isEvent(MethodDeclaration declaring) {
        return eventTagFor(declaring) != null;
    }

    private boolean isMappedToProperty(MethodDeclaration original) {
        final MemberMapping mapping = effectiveMappingFor(original.resolveBinding());
        if (null == mapping)
            return false;
        return mapping.kind == MemberKind.Property;
    }

    private MemberMapping effectiveMappingFor(IMethodBinding binding) {
        return my(Mappings.class).effectiveMappingFor(binding);
    }

    private String methodName(String name) {
        return namingStrategy().methodName(name);
    }

    protected String identifier(SimpleName name) {
        return identifier(name.toString());
    }

    protected String identifier(String name) {
        return namingStrategy().identifier(name);
    }

    private void unresolvedTypeBinding(ASTNode node) {
        warning(node, "unresolved type binding for node: " + node);
    }

    public boolean visit(CompilationUnit node) {
        return true;
    }

    private void warning(ASTNode node, String message) {
        warningHandler().warning(node, message);
    }

    protected final String sourceInformation(ASTNode node) {
        return ASTUtility.sourceInformation(_ast, node);
    }

    @SuppressWarnings("deprecation")
    protected int lineNumber(ASTNode node) {
        return _ast.lineNumber(node.getStartPosition());
    }

    public void setASTResolver(ASTResolver resolver) {
        _resolver = resolver;
    }

    private String mappedNamespace(String namespace) {
        return _configuration.mappedNamespace(namespace);
    }

    @Override
    public boolean visit(Block node) {
        if (isBlockInsideBlock(node)) {
            CSBlock parent = _currentBlock;
            _currentBlock = new CSBlock();
            _currentBlock.parent(parent);
            parent.addStatement(_currentBlock);
        }
        _currentContinueLabel = null;
        pushScope();
        return super.visit(node);
    }

    @Override
    public void endVisit(Block node) {
        if (isBlockInsideBlock(node)) {
            _currentBlock = (CSBlock) _currentBlock.parent();
        }
        popScope();
        super.endVisit(node);
    }

    boolean isBlockInsideBlock(Block node) {
        return node.getParent() instanceof Block;
    }

    void pushScope() {
        HashSet<String> newLocalVars = new HashSet<String>();
        if (_localBlockVariables.size() > 0)
            newLocalVars.addAll(_localBlockVariables.peek());
        _localBlockVariables.push(newLocalVars);

        HashSet<String> newBlockVars = new HashSet<String>();
        newBlockVars.addAll(newLocalVars);
        _blockVariables.push(newBlockVars);

        HashMap<String, String> newRenamed = new HashMap<String, String>();
        if (_renamedVariables.size() > 0)
            newRenamed.putAll(_renamedVariables.peek());
        _renamedVariables.push(newRenamed);
    }

    void popScope() {
        _blockVariables.pop();
        _localBlockVariables.pop();
        _renamedVariables.pop();
    }
}