/* 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 org.eclipse.jdt.core.dom.*; import sharpen.core.csharp.ast.*; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; public class CSAnonymousClassBuilder extends AbstractNestedClassBuilder { private AnonymousClassDeclaration _node; private CSClass _type; private CSConstructor _constructor; private Set<IVariableBinding> _capturedVariables = new LinkedHashSet<IVariableBinding>(); private CSharpBuilder _parent; private Set<VariableDeclarationFragment> _fieldInitializers = new LinkedHashSet<VariableDeclarationFragment>(); public CSAnonymousClassBuilder(CSharpBuilder builder, AnonymousClassDeclaration node) { super(builder); _parent = builder; _node = node; run(); } public CSClass type() { return _type; } public CSConstructor constructor() { return _constructor; } public Set<IVariableBinding> capturedVariables() { return _capturedVariables; } public CSConstructorInvocationExpression createConstructorInvocation() { CSExpression typeRef = new CSReferenceExpression(_type.name()); CSConstructorInvocationExpression invocation = new CSConstructorInvocationExpression(typeRef); if (isEnclosingReferenceRequired()) { invocation.addArgument(new CSThisExpression()); } addCapturedVariables(invocation); addBaseConstructorArguments(invocation); return invocation; } private void addCapturedVariables(CSConstructorInvocationExpression invocation) { for (IVariableBinding variable : _capturedVariables) { String mapped = mappedVariableName(variable); String name = mapped != null ? mapped : identifier(variable.getName()); invocation.addArgument(new CSReferenceExpression(name)); } } private void addBaseConstructorArguments(CSConstructorInvocationExpression invocation) { final List arguments = constructorArguments(); if (arguments.isEmpty()) { return; } final IMethodBinding ctor = constructorBinding(); final ITypeBinding[] ctorParameterTypes = ctor.getParameterTypes(); final CSConstructorInvocationExpression cci; if (_constructor.chainedConstructorInvocation() != null) cci = _constructor.chainedConstructorInvocation(); else { cci = new CSConstructorInvocationExpression(new CSBaseExpression()); _constructor.chainedConstructorInvocation(cci); } for (int i=0; i<ctorParameterTypes.length; ++i) { ITypeBinding parameterType = ctorParameterTypes[i]; Expression argument = (Expression)arguments.get(i); String parameterName = "baseArg" + (i + 1); _constructor.addParameter(parameterName, mappedTypeReference(parameterType)); cci.addArgument(new CSReferenceExpression(parameterName)); invocation.addArgument(_parent.mapExpression(argument)); } } private List constructorArguments() { if (_node.getParent() instanceof EnumConstantDeclaration) { final EnumConstantDeclaration node = (EnumConstantDeclaration) _node.getParent(); return node.arguments(); } else { final ClassInstanceCreation node = (ClassInstanceCreation) _node.getParent(); return node.arguments(); } } private IMethodBinding constructorBinding() { if (_node.getParent() instanceof EnumConstantDeclaration) { final EnumConstantDeclaration node = (EnumConstantDeclaration) _node.getParent(); return node.resolveConstructorBinding(); } else { final ClassInstanceCreation node = (ClassInstanceCreation) _node.getParent(); return node.resolveConstructorBinding(); } } @Override public void run() { captureExternalLocalVariables(); setUpAnonymousType(); setUpConstructor(); processAnonymousBody(); flushFieldInitializers(); int capturedVariableCount = flushCapturedVariables(); flushInstanceInitializers(_type, capturedVariableCount); } private void flushFieldInitializers() { for (VariableDeclarationFragment field : _fieldInitializers) { addToConstructor(createFieldAssignment(fieldName(field), mapExpression(field.getInitializer()))); } } @Override protected CSExpression mapFieldInitializer(VariableDeclarationFragment fragment) { if (Modifier.isStatic(fragment.resolveBinding().getModifiers())) return super.mapFieldInitializer(fragment); if (fragment.getInitializer() != null) { _fieldInitializers.add(fragment); } return null; } private void processAnonymousBody() { CSTypeDeclaration saved = _currentType; _currentType = _type; visit(_node.bodyDeclarations()); _currentType = saved; } @Override public boolean visit(AnonymousClassDeclaration node) { CSAnonymousClassBuilder builder = new CSAnonymousClassBuilder(this, node); if (builder.isEnclosingReferenceRequired()) { requireEnclosingReference(); } captureNeededVariables(builder); pushExpression(builder.createConstructorInvocation()); _currentType.addMember(builder.type()); return false; } private void captureNeededVariables(CSAnonymousClassBuilder builder) { IMethodBinding currentMethod = currentMethodDeclarationBinding(); for (IVariableBinding variable : builder.capturedVariables()) { IMethodBinding method = variable.getDeclaringMethod(); if (method != currentMethod) { _capturedVariables.add(variable); } } } private IMethodBinding currentMethodDeclarationBinding() { return _currentBodyDeclaration instanceof MethodDeclaration ? ((MethodDeclaration)_currentBodyDeclaration).resolveBinding() : null; } private void addFieldParameter(String name, CSTypeReferenceExpression type) { addFieldParameter(CSharpCode.newPrivateReadonlyField(name, type)); } private void addFieldParameter(CSField field) { _type.addMember(field); String parameterName = field.name(); _constructor.addParameter(parameterName, field.type()); addToConstructor(createFieldAssignment(field.name(), parameterName)); } private void addToConstructor(final CSExpression expression) { _constructor.body().addStatement(expression); } private String anonymousBaseTypeName() { return mappedTypeName(anonymousBaseType()); } public ITypeBinding anonymousBaseType() { ITypeBinding binding = nestedTypeBinding(); return binding.getInterfaces().length > 0 ? binding.getInterfaces()[0] : binding.getSuperclass(); } @Override protected ITypeBinding nestedTypeBinding() { return _node.resolveBinding(); } private String anonymousInnerClassName() { String baseTypeName = anonymousBaseTypeName(); return "_" + removeTypeArguments(simpleName(baseTypeName)) + "_" + lineNumber(_node); } private String simpleName(String typeName) { final int index = typeName.lastIndexOf('.'); if (index < 0) return typeName; return typeName.substring(index + 1); } private String removeTypeArguments(String typeName) { final int index = typeName.indexOf('<'); if (index < 0) return typeName; return typeName.substring(0, index); } private void setUpAnonymousType() { _type = classForAnonymousType(); } private CSClass classForAnonymousType() { CSClass type = new CSClass(anonymousInnerClassName(), CSClassModifier.Sealed); type.visibility(CSVisibility.Private); type.anonymous(true); ITypeBinding bt = anonymousBaseType(); CSTypeReferenceExpression tref = mappedTypeReference(bt); type.addBaseType(tref); for (ITypeBinding tp : bt.getTypeParameters()) type.addTypeParameter(new CSTypeParameter(identifier(tp.getName()))); if (isNonStaticNestedType(bt)) requireEnclosingReference(); return type; } private void setUpConstructor() { _constructor = new CSConstructor(); _constructor.visibility(CSVisibility.Public); _type.addMember(_constructor); } private int flushCapturedVariables() { int capturedVariableCount = 0; if (isEnclosingReferenceRequired()) { capturedVariableCount++; CSField ef = createEnclosingField(); addFieldParameter(ef); ITypeBinding bt = anonymousBaseType(); if (bt != null && isNonStaticNestedType (bt)) { if (null == _constructor.chainedConstructorInvocation ()) _constructor.chainedConstructorInvocation(new CSConstructorInvocationExpression(new CSBaseExpression())); final IMethodBinding ctor = constructorBinding(); final ITypeBinding superclass = ctor.getDeclaringClass().getSuperclass(); CSExpression enclosing = createEnclosingBaseCtorArgument(ef.name(), superclass); _constructor.chainedConstructorInvocation().addArgument(enclosing); } } for (IVariableBinding variable : _capturedVariables) { capturedVariableCount++; String mapped = mappedVariableName(variable); String name = mapped != null ? mapped : identifier(variable.getName()); addFieldParameter(name, mappedTypeReference(variable.getType())); } return capturedVariableCount; } private void captureExternalLocalVariables() { _node.accept(new ASTVisitor() { IMethodBinding _currentMethodBinding; @Override public boolean visit(MethodDeclaration node) { IMethodBinding saved = _currentMethodBinding; _currentMethodBinding = node.resolveBinding(); node.getBody().accept(this); _currentMethodBinding = saved; return false; } @Override public boolean visit(AnonymousClassDeclaration node) { return node == _node; } @Override public boolean visit(SimpleName node) { IBinding binding = node.resolveBinding(); if (isExternalLocal(binding)) { _capturedVariables.add((IVariableBinding)binding); } return false; } boolean isExternalLocal(IBinding binding) { if (binding instanceof IVariableBinding) { IVariableBinding variable = (IVariableBinding)binding; if (!variable.isField()) { return variable.getDeclaringMethod() != _currentMethodBinding; } } return false; } }); } }