/* * 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.j2objc; import com.google.j2objc.annotations.AutoreleasePool; import com.google.j2objc.annotations.RetainedWith; import junit.framework.TestCase; import java.util.ArrayList; import java.util.Collection; import java.util.EnumMap; import java.util.HashMap; import java.util.HashSet; import java.util.Hashtable; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.WeakHashMap; import java.util.concurrent.ConcurrentHashMap; /** * Tests the {@link RetainedWith} annotation. * * @author Keith Stanger */ public class RetainedWithTest extends TestCase { private static Set<Integer> finalizedObjects = new HashSet<>(); @Override public void tearDown() throws Exception { finalizedObjects.clear(); super.tearDown(); } static class Base { protected void finalize() { finalizedObjects.add(System.identityHashCode(this)); } } static class A extends Base implements Cloneable { @RetainedWith B b = new B(this); public A clone() { try { return (A) super.clone(); } catch (CloneNotSupportedException e) { throw new AssertionError(e); } } } static class B extends Base implements Cloneable { A a; B(A a) { this.a = a; } public B clone() { try { return (B) super.clone(); } catch (CloneNotSupportedException e) { throw new AssertionError(e); } } } @AutoreleasePool private void newA(List<Integer> objectCodes) { A a = new A(); objectCodes.add(System.identityHashCode(a)); objectCodes.add(System.identityHashCode(a.b)); } public void testObjectPairIsDeallocated() { List<Integer> objectCodes = new ArrayList<Integer>(); newA(objectCodes); for (Integer i : objectCodes) { assertTrue(finalizedObjects.contains(i)); } } static class Symmetric extends Base { @RetainedWith Symmetric other; Symmetric() { other = new Symmetric(this); } Symmetric(Symmetric other) { this.other = other; } } @AutoreleasePool private void newSymmetric(List<Integer> objectCodes) { Symmetric s = new Symmetric(); objectCodes.add(System.identityHashCode(s)); objectCodes.add(System.identityHashCode(s.other)); } public void testSymmetricObjectPairIsDeallocated() { List<Integer> objectCodes = new ArrayList<Integer>(); newSymmetric(objectCodes); for (Integer i : objectCodes) { assertTrue(finalizedObjects.contains(i)); } } @AutoreleasePool private void newAPlusClone(List<Integer> objectCodes) { A a = new A(); A a2 = a.clone(); assertSame(a.b, a2.b); // We allow this reassignment of a2.b because the child's return reference points at "a" not // "a2". It is important to support setting the child reference to null after cloning the // parent. a2.b = null; objectCodes.add(System.identityHashCode(a)); objectCodes.add(System.identityHashCode(a2)); objectCodes.add(System.identityHashCode(a.b)); } public void testCloneParentObject() { List<Integer> objectCodes = new ArrayList<Integer>(); newAPlusClone(objectCodes); for (Integer i : objectCodes) { assertTrue(finalizedObjects.contains(i)); } } @AutoreleasePool private void newAPlusCloneChild(List<Integer> objectCodes) { A a = new A(); B b = a.b.clone(); assertSame(a, b.a); objectCodes.add(System.identityHashCode(a)); objectCodes.add(System.identityHashCode(a.b)); objectCodes.add(System.identityHashCode(b)); } public void testCloneChildObject() { List<Integer> objectCodes = new ArrayList<Integer>(); newAPlusCloneChild(objectCodes); for (Integer i : objectCodes) { assertTrue(finalizedObjects.contains(i)); } } @AutoreleasePool private void newAPlusReassignChild(List<Integer> objectCodes) { A a = new A(); objectCodes.add(System.identityHashCode(a)); objectCodes.add(System.identityHashCode(a.b)); a.b = new B(a); objectCodes.add(System.identityHashCode(a.b)); } public void testReassignChild() { List<Integer> objectCodes = new ArrayList<Integer>(); newAPlusReassignChild(objectCodes); for (Integer i : objectCodes) { assertTrue(finalizedObjects.contains(i)); } } abstract class MapFactory { final Object key; MapFactory(Object key) { this.key = key; } public abstract Map newMap(); public Object getKey() { return key; } } // We use this class as a value to insert in our maps so we can verity that the map has been // deallocated. static class ValueType { protected void finalize() { finalizedObjects.add(System.identityHashCode(this)); } } enum Color { RED, GREEN, BLUE } Set keys; Collection values; Set<Map.Entry> entrySet; @AutoreleasePool private void createMapChildren(MapFactory factory, List<Integer> objectCodes) { // Use separate maps for each of the views to ensure that each view type is strengthening its // reference to the map. Map m1 = factory.newMap(); Map m2 = factory.newMap(); Map m3 = factory.newMap(); ValueType v = new ValueType(); m1.put(factory.getKey(), v); m2.put(factory.getKey(), v); m3.put(factory.getKey(), v); keys = m1.keySet(); values = m2.values(); entrySet = m3.entrySet(); objectCodes.add(System.identityHashCode(v)); } @AutoreleasePool private void checkMapChildren(MapFactory factory, List<Integer> objectCodes) { createMapChildren(factory, objectCodes); // Call some methods to make sure they still exist and can access the parent assertEquals(1, keys.size()); assertEquals(1, values.size()); assertEquals(1, entrySet.size()); assertTrue(keys.contains(factory.getKey())); assertFalse(values.contains(new Object())); assertEquals(factory.getKey(), entrySet.iterator().next().getKey()); keys = null; values = null; entrySet = null; } public void runMapTest(MapFactory factory) { List<Integer> objectCodes = new ArrayList<Integer>(); checkMapChildren(factory, objectCodes); for (Integer i : objectCodes) { assertTrue(finalizedObjects.contains(i)); } } public void testMapChildren() { runMapTest(new MapFactory(new Object()) { public Map newMap() { return new IdentityHashMap(); } }); runMapTest(new MapFactory(new Object()) { public Map newMap() { return new WeakHashMap(); } }); runMapTest(new MapFactory(Color.RED) { public Map newMap() { return new EnumMap(Color.class); } }); runMapTest(new MapFactory(new Object()) { public Map newMap() { return new HashMap(); } }); runMapTest(new MapFactory(5) { public Map newMap() { return new TreeMap(); } }); runMapTest(new MapFactory(new Object()) { public Map newMap() { return new Hashtable(); } }); runMapTest(new MapFactory(new Object()) { public Map newMap() { return new ConcurrentHashMap(); } }); } }