/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.devtools.j2objc.translate;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.devtools.j2objc.GenerationTest;
import com.google.devtools.j2objc.ast.ClassInstanceCreation;
import com.google.devtools.j2objc.ast.CompilationUnit;
import com.google.devtools.j2objc.ast.Expression;
import com.google.devtools.j2objc.ast.InfixExpression;
import com.google.devtools.j2objc.ast.MethodInvocation;
import com.google.devtools.j2objc.ast.PostfixExpression;
import com.google.devtools.j2objc.ast.QualifiedName;
import com.google.devtools.j2objc.ast.SimpleName;
import com.google.devtools.j2objc.ast.TreeNode;
import com.google.devtools.j2objc.ast.TreeNode.Kind;
import com.google.devtools.j2objc.ast.TreeUtil;
import com.google.devtools.j2objc.ast.TreeVisitor;
import com.google.devtools.j2objc.ast.TypeDeclaration;
import com.google.devtools.j2objc.util.CaptureInfo;
import com.google.devtools.j2objc.util.ElementUtil;
import java.io.IOException;
import java.util.List;
import javax.lang.model.element.VariableElement;
/**
* Unit tests for {@link OuterReferenceResolver}.
*
* @author Keith Stanger
*/
public class OuterReferenceResolverTest extends GenerationTest {
private CaptureInfo captureInfo;
private ListMultimap<Kind, TreeNode> nodesByType = ArrayListMultimap.create();
@Override
protected void setUp() throws IOException {
super.setUp();
}
@Override
protected void tearDown() throws Exception {
captureInfo = null;
nodesByType.clear();
}
public void testOuterVarAccess() {
resolveSource("Test", "class Test { int i; class Inner { void test() { i++; } } }");
TypeDeclaration innerNode = (TypeDeclaration) nodesByType.get(Kind.TYPE_DECLARATION).get(1);
assertTrue(captureInfo.needsOuterReference(innerNode.getTypeElement()));
PostfixExpression increment =
(PostfixExpression) nodesByType.get(Kind.POSTFIX_EXPRESSION).get(0);
Expression iNode = increment.getOperand();
assertTrue(iNode instanceof QualifiedName);
VariableElement outerVar = TreeUtil.getVariableElement(((QualifiedName) iNode).getQualifier());
assertNotNull(outerVar);
assertEquals("Test", outerVar.asType().toString());
}
public void testInheritedOuterMethod() {
resolveSource("Test",
"class Test { class A { void foo() {} } class B extends A { "
+ "class Inner { void test() { foo(); } } } }");
TypeDeclaration aNode = (TypeDeclaration) nodesByType.get(Kind.TYPE_DECLARATION).get(1);
TypeDeclaration bNode = (TypeDeclaration) nodesByType.get(Kind.TYPE_DECLARATION).get(2);
TypeDeclaration innerNode = (TypeDeclaration) nodesByType.get(Kind.TYPE_DECLARATION).get(3);
assertFalse(captureInfo.needsOuterReference(aNode.getTypeElement()));
assertFalse(captureInfo.needsOuterReference(bNode.getTypeElement()));
assertTrue(captureInfo.needsOuterReference(innerNode.getTypeElement()));
// B will need an outer reference to Test so it can initialize its
// superclass A.
Expression bSuperOuter = bNode.getSuperOuter();
assertTrue(bSuperOuter instanceof SimpleName);
assertEquals("outer$", ElementUtil.getName(TreeUtil.getVariableElement(bSuperOuter)));
// foo() call will need to get to B's scope to call the inherited method.
MethodInvocation fooCall = (MethodInvocation) nodesByType.get(Kind.METHOD_INVOCATION).get(0);
Expression expr = fooCall.getExpression();
assertTrue(expr instanceof SimpleName);
VariableElement fooReceiver = TreeUtil.getVariableElement(expr);
assertNotNull(fooReceiver);
assertEquals("Test.B", fooReceiver.asType().toString());
}
public void testCapturedLocalVariable() {
resolveSource("Test",
"class Test { void test(final int i) { Runnable r = new Runnable() { "
+ "public void run() { int i2 = i + 1; } }; } }");
TypeDeclaration runnableNode = (TypeDeclaration) nodesByType.get(Kind.TYPE_DECLARATION).get(1);
assertTrue(ElementUtil.isAnonymous(runnableNode.getTypeElement()));
assertFalse(captureInfo.needsOuterReference(runnableNode.getTypeElement()));
List<VariableElement> innerFields = Lists.newArrayList(
captureInfo.getCaptureFields(runnableNode.getTypeElement()));
assertEquals(1, innerFields.size());
assertEquals("val$i", ElementUtil.getName(innerFields.get(0)));
ClassInstanceCreation creationNode =
(ClassInstanceCreation) nodesByType.get(Kind.CLASS_INSTANCE_CREATION).get(0);
List<Expression> captureArgs = creationNode.getCaptureArgs();
assertEquals(1, captureArgs.size());
Expression captureArg = captureArgs.get(0);
assertTrue(captureArg instanceof SimpleName);
VariableElement captureVar = TreeUtil.getVariableElement(captureArg);
assertNotNull(captureVar);
assertEquals("i", ElementUtil.getName(captureVar));
InfixExpression addition = (InfixExpression) nodesByType.get(Kind.INFIX_EXPRESSION).get(0);
Expression iNode = addition.getOperands().get(0);
assertTrue(iNode instanceof SimpleName);
VariableElement iVar = TreeUtil.getVariableElement(iNode);
assertNotNull(iVar);
assertEquals("val$i", ElementUtil.getName(iVar));
}
public void testCapturedWeakLocalVariable() {
resolveSource("Test",
"import com.google.j2objc.annotations.Weak;"
+ "class Test { void test(@Weak final int i) { Runnable r = new Runnable() { "
+ "public void run() { int i2 = i + 1; } }; } }");
TypeDeclaration runnableNode = (TypeDeclaration) nodesByType.get(Kind.TYPE_DECLARATION).get(1);
assertTrue(ElementUtil.isAnonymous(runnableNode.getTypeElement()));
List<VariableElement> innerFields = Lists.newArrayList(
captureInfo.getCaptureFields(runnableNode.getTypeElement()));
assertEquals(1, innerFields.size());
assertTrue(ElementUtil.isWeakReference(innerFields.get(0)));
}
public void testAnonymousClassInheritsLocalClassInStaticMethod() {
resolveSource("Test",
"class Test { static void test() { class LocalClass {}; new LocalClass() {}; } }");
TypeDeclaration decl = (TypeDeclaration) nodesByType.get(Kind.TYPE_DECLARATION).get(2);
assertTrue(ElementUtil.isAnonymous(decl.getTypeElement()));
assertFalse(captureInfo.needsOuterParam(decl.getTypeElement()));
}
public void testAnonymousClassCreatesLocalClassWithCaptures() {
resolveSource("Test",
"class Test { Runnable test(final Object o) { "
+ "class Local { public void foo() { o.toString(); } } "
+ "return new Runnable() { public void run() { new Local(); } }; } }");
TypeDeclaration runnableNode = (TypeDeclaration) nodesByType.get(Kind.TYPE_DECLARATION).get(2);
assertTrue(ElementUtil.isAnonymous(runnableNode.getTypeElement()));
List<VariableElement> innerFields = Lists.newArrayList(
captureInfo.getCaptureFields(runnableNode.getTypeElement()));
assertEquals(1, innerFields.size());
assertEquals("val$o", ElementUtil.getName(innerFields.get(0)));
ClassInstanceCreation creationNode =
(ClassInstanceCreation) nodesByType.get(Kind.CLASS_INSTANCE_CREATION).get(1);
List<Expression> captureArgs = creationNode.getCaptureArgs();
assertEquals(1, captureArgs.size());
Expression captureArg = captureArgs.get(0);
assertTrue(captureArg instanceof SimpleName);
VariableElement captureVar = TreeUtil.getVariableElement(captureArg);
assertNotNull(captureVar);
assertEquals("val$o", ElementUtil.getName(captureVar));
}
public void testNoOuterFieldWhenSuperConstructorIsQualified() {
resolveSource("Test",
"class Test { class B { class Inner {} } class A { class Inner extends B.Inner { "
+ " Inner(B b) { b.super(); } } } }");
List<TreeNode> typeNodes = nodesByType.get(Kind.TYPE_DECLARATION);
assertEquals(5, typeNodes.size());
for (TreeNode typeNode : typeNodes) {
assertNull(captureInfo.getOuterField(((TypeDeclaration) typeNode).getTypeElement()));
}
}
public void testNestedLocalClassesWithNestedCreations() {
// This test is particularly tricky for OuterReferenceResolver because A captures variable i,
// but that is not known until after A's creation. A's creation occurs within B, which requires
// B to have an outer field in order to access A's capturing field for i. B's creation therefore
// requires the outer field to be passed as an outer argument.
// Because of the cascading effects of the statements in this test and the order in which they
// occur, we would need to do three passes over the code to resolve B's creation successfuly.
resolveSource("Test",
"class Test { void test(int i) { class A { "
+ "void foo() { class B { void bar() { new B(); new A(); } } } "
+ "int other() { return i; } } } }");
ClassInstanceCreation bCreate =
(ClassInstanceCreation) nodesByType.get(Kind.CLASS_INSTANCE_CREATION).get(0);
Expression outerArg = bCreate.getExpression();
assertTrue(outerArg instanceof SimpleName);
VariableElement var = TreeUtil.getVariableElement(outerArg);
assertNotNull(var);
assertEquals("this$0", ElementUtil.getName(var));
}
public void testQualifiedFieldAccess() throws IOException {
addSourceFile("class A { int i; }", "A.java");
String translation = translateSourceFile(
"class B extends A { class C { int foo() { return B.super.i; } } }", "B", "B.m");
assertTranslation(translation, "return this$0_->i_;");
}
private void resolveSource(String name, String source) {
CompilationUnit unit = compileType(name, source);
captureInfo = unit.getEnv().captureInfo();
new OuterReferenceResolver(unit).run();
findTypeDeclarations(unit);
}
private void findTypeDeclarations(CompilationUnit unit) {
unit.accept(new TreeVisitor() {
@Override
public boolean preVisit(TreeNode node) {
nodesByType.put(node.getKind(), node);
return true;
}
});
}
}