-
Notifications
You must be signed in to change notification settings - Fork 6.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add basic java support for mapping objects to a Profile.
BUG=1422789 Change-Id: I2d93a41b8eadd62397b643da39a5e19ab050660a Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4326924 Reviewed-by: David Trainor <dtrainor@chromium.org> Commit-Queue: Ted Choc <tedchoc@chromium.org> Commit-Queue: David Trainor <dtrainor@chromium.org> Cr-Commit-Position: refs/heads/main@{#1115989}
- Loading branch information
Ted Choc
authored and
Chromium LUCI CQ
committed
Mar 11, 2023
1 parent
1796303
commit 5f33938
Showing
8 changed files
with
252 additions
and
78 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
92 changes: 92 additions & 0 deletions
92
...owser/profiles/android/java/src/org/chromium/chrome/browser/profiles/ProfileKeyedMap.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
// Copyright 2023 The Chromium Authors | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
package org.chromium.chrome.browser.profiles; | ||
|
||
import androidx.annotation.NonNull; | ||
|
||
import org.chromium.base.Callback; | ||
import org.chromium.base.CollectionUtil; | ||
import org.chromium.base.lifetime.Destroyable; | ||
import org.chromium.base.supplier.Supplier; | ||
|
||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
/** | ||
* A Profile-lifetime aware data structure that allows mapping objects to Profile and handling the | ||
* necessary cleanup when a Profile is destroyed. | ||
* | ||
* This data structure owns the objects associated with the Profile and will delete them when | ||
* appropriate. | ||
* | ||
* @param <T> The type of object being mapped to the Profile. | ||
*/ | ||
public class ProfileKeyedMap<T> { | ||
/** Indicates no cleanup action is required when destroying an object in the map. */ | ||
public static Callback NO_REQUIRED_CLEANUP_ACTION = (e) -> {}; | ||
|
||
private final Map<Profile, T> mData = new HashMap<>(); | ||
private final Callback<T> mDestroyAction; | ||
|
||
private ProfileManager.Observer mProfileManagerObserver; | ||
|
||
/** | ||
* Creates a map of Profile -> Object that handles automatically cleaning up when the profiles | ||
* are destroyed. | ||
* @param destroyAction The action to be taken on the object during destruction of this map | ||
* or when a Profile is destroyed. | ||
*/ | ||
public ProfileKeyedMap(@NonNull Callback<T> destroyAction) { | ||
mDestroyAction = destroyAction; | ||
} | ||
|
||
/** | ||
* @return A data structure that maps Profile to Destroyable objects that will be destroyed | ||
* when appropriate. | ||
* @param <T> The object type being mapped against the Profile. | ||
*/ | ||
public static <T extends Destroyable> ProfileKeyedMap<T> createMapOfDestroyables() { | ||
return new ProfileKeyedMap<>((e) -> e.destroy()); | ||
} | ||
|
||
/** | ||
* Gets (and lazily constructs if needed) the mapped object for a given Profile. | ||
* @param profile The Profile the object is associated with. | ||
* @param factory The factory that will construct the object if it does not already exist. | ||
* @return The object associated with the passed in Profile. | ||
*/ | ||
public T getForProfile(Profile profile, Supplier<T> factory) { | ||
T obj = mData.get(profile); | ||
if (obj == null) { | ||
obj = factory.get(); | ||
mData.put(profile, obj); | ||
} | ||
if (mProfileManagerObserver == null) { | ||
mProfileManagerObserver = new ProfileManager.Observer() { | ||
@Override | ||
public void onProfileAdded(Profile profile) {} | ||
|
||
@Override | ||
public void onProfileDestroyed(Profile destroyedProfile) { | ||
T obj = mData.remove(destroyedProfile); | ||
if (obj == null) return; | ||
mDestroyAction.onResult(obj); | ||
} | ||
}; | ||
ProfileManager.addObserver(mProfileManagerObserver); | ||
} | ||
return obj; | ||
} | ||
|
||
/** | ||
* Destroys this object and all objects currently mapped to Profiles. | ||
*/ | ||
public void destroy() { | ||
if (mProfileManagerObserver != null) ProfileManager.removeObserver(mProfileManagerObserver); | ||
mProfileManagerObserver = null; | ||
CollectionUtil.forEach(mData, e -> mDestroyAction.onResult(e.getValue())); | ||
mData.clear(); | ||
} | ||
} |
121 changes: 121 additions & 0 deletions
121
...r/profiles/android/java/src/org/chromium/chrome/browser/profiles/ProfileKeyedMapTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
// Copyright 2023 The Chromium Authors | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
package org.chromium.chrome.browser.profiles; | ||
|
||
import static org.chromium.chrome.browser.profiles.ProfileKeyedMap.NO_REQUIRED_CLEANUP_ACTION; | ||
|
||
import org.hamcrest.MatcherAssert; | ||
import org.hamcrest.Matchers; | ||
import org.junit.Assert; | ||
import org.junit.Before; | ||
import org.junit.Test; | ||
import org.junit.runner.RunWith; | ||
import org.mockito.Mock; | ||
import org.mockito.Mockito; | ||
import org.mockito.MockitoAnnotations; | ||
import org.robolectric.annotation.Config; | ||
|
||
import org.chromium.base.lifetime.Destroyable; | ||
import org.chromium.base.test.BaseRobolectricTestRunner; | ||
|
||
import java.util.HashSet; | ||
import java.util.Set; | ||
|
||
/** | ||
* Tests for ProfileKeyedMap. | ||
*/ | ||
@RunWith(BaseRobolectricTestRunner.class) | ||
@Config(manifest = Config.NONE) | ||
public class ProfileKeyedMapTest { | ||
@Mock | ||
private Profile mProfile1; | ||
@Mock | ||
private Profile mProfile2; | ||
@Mock | ||
private Profile mProfile3; | ||
|
||
@Before | ||
public void setUp() { | ||
MockitoAnnotations.initMocks(this); | ||
} | ||
|
||
@Test | ||
public void testReusesObjects() { | ||
ProfileKeyedMap<Object> map = new ProfileKeyedMap<Object>(NO_REQUIRED_CLEANUP_ACTION); | ||
|
||
Object obj1 = new Object(); | ||
Assert.assertEquals(obj1, map.getForProfile(mProfile1, () -> obj1)); | ||
Assert.assertEquals(obj1, map.getForProfile(mProfile1, Object::new)); | ||
} | ||
|
||
@Test | ||
public void testCleanupOnProfileDestruction() { | ||
Set<Object> destroyedObjects = new HashSet<>(); | ||
ProfileKeyedMap<Object> map = | ||
new ProfileKeyedMap<Object>((obj) -> destroyedObjects.add(obj)); | ||
|
||
Object obj1 = new Object(); | ||
Assert.assertEquals(obj1, map.getForProfile(mProfile1, () -> obj1)); | ||
|
||
ProfileManager.onProfileDestroyed(mProfile1); | ||
MatcherAssert.assertThat(destroyedObjects, Matchers.hasItem(obj1)); | ||
} | ||
|
||
@Test | ||
public void testDestroy() { | ||
Set<Object> destroyedObjects = new HashSet<>(); | ||
ProfileKeyedMap<Object> map = | ||
new ProfileKeyedMap<Object>((obj) -> destroyedObjects.add(obj)); | ||
|
||
Object obj1 = new Object(); | ||
Assert.assertEquals(obj1, map.getForProfile(mProfile1, () -> obj1)); | ||
Object obj2 = new Object(); | ||
Assert.assertEquals(obj2, map.getForProfile(mProfile2, () -> obj2)); | ||
Object obj3 = new Object(); | ||
Assert.assertEquals(obj3, map.getForProfile(mProfile3, () -> obj3)); | ||
|
||
map.destroy(); | ||
MatcherAssert.assertThat(destroyedObjects, Matchers.hasItems(obj1, obj2, obj3)); | ||
} | ||
|
||
@Test | ||
public void testMapsAreIndependent() { | ||
Set<Object> destroyedObjects = new HashSet<>(); | ||
ProfileKeyedMap<Object> map1 = | ||
new ProfileKeyedMap<Object>((obj) -> destroyedObjects.add(obj)); | ||
|
||
ProfileKeyedMap<Object> map2 = | ||
new ProfileKeyedMap<Object>((obj) -> destroyedObjects.add(obj)); | ||
|
||
Object obj1 = new Object(); | ||
Assert.assertEquals(obj1, map1.getForProfile(mProfile1, () -> obj1)); | ||
|
||
Object obj2 = new Object(); | ||
Assert.assertEquals(obj2, map2.getForProfile(mProfile1, () -> obj2)); | ||
|
||
map1.destroy(); | ||
MatcherAssert.assertThat(destroyedObjects, Matchers.hasItem(obj1)); | ||
|
||
Assert.assertEquals(obj2, map2.getForProfile(mProfile1, null)); | ||
} | ||
|
||
@Test | ||
public void testDestroyableMap() { | ||
ProfileKeyedMap<Destroyable> map = ProfileKeyedMap.createMapOfDestroyables(); | ||
|
||
Destroyable destroyable1 = Mockito.mock(Destroyable.class); | ||
Destroyable destroyable2 = Mockito.mock(Destroyable.class); | ||
|
||
map.getForProfile(mProfile1, () -> destroyable1); | ||
map.getForProfile(mProfile2, () -> destroyable2); | ||
|
||
Assert.assertEquals(destroyable1, map.getForProfile(mProfile1, null)); | ||
Assert.assertEquals(destroyable2, map.getForProfile(mProfile2, null)); | ||
|
||
map.destroy(); | ||
Mockito.verify(destroyable1).destroy(); | ||
Mockito.verify(destroyable2).destroy(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.