Created
April 7, 2017 12:01
-
-
Save cmelchior/d8b944a025f52e1547317d4eef65f2cf to your computer and use it in GitHub Desktop.
Helper class for creating auto increment keys for Realm model classes
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
/* | |
* Copyright 2017 Realm Inc. | |
* | |
* 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 io.realm; | |
import java.util.HashMap; | |
import java.util.Map; | |
import java.util.concurrent.atomic.AtomicLong; | |
import static java.lang.String.format; | |
/** | |
* Factory class for creating auto-incremented integral keys for Realm. | |
* | |
* This class should be initialized before creating <i>any</i> RealmObjects for a given Realm. | |
* <p> | |
* <b>WARNING:</b> | |
* This class is not safe to use if you Realm is accessed from multiple devices or processes. | |
* <p> | |
* Usage: | |
* <pre> | |
* {@code | |
* public class MyApplication extends Application { | |
* @Override | |
* public void onCreate() { | |
* super.onCreate(); | |
* Realm.init(this); | |
* Realm realm = Realm.getDefaultInstance(); | |
* PrimaryKeyFactory.init(realm); | |
* realm.close(); | |
* } | |
* } | |
* | |
* // In objects | |
* public class Person extends RealmObject { | |
* \@PrimaryKey | |
* private int id = PrimaryKeyFactory.nextKey(this.class); | |
* private String name; | |
* } | |
* | |
* // When creating objects directly | |
* Person p = realm.createObject(Person.class, PrimaryKeyFactory.nextKey(Person.class)); | |
* } | |
* </pre> | |
* <p> | |
* Known limitations: | |
* <ul> | |
* <li> | |
* This class does not work if the Realm Model class names have been obfuscated. | |
* </li> | |
* <li> | |
* No error checking when generating keys to keep it as fast as possible. A {@code NullPointerException} | |
* indicate wrong use of the class. | |
* </li> | |
* | |
* <li> | |
* This class does not work with {@code DynamicRealm}s. | |
* </li> | |
* </ul> | |
* | |
* @see <a href="https://github.com/realm/realm-java/issues/469#issuecomment-196798253">Background issue for this class</a> | |
*/ | |
public class PrimaryKeyFactory { | |
private static Map<Class<? extends RealmModel>, AtomicLong> keyMap; | |
/** | |
* Initialize the factory. Must be called before any primary key is generated. | |
* Note | |
* | |
* @param realm Realm to configure primary keys for. | |
*/ | |
public static synchronized void init(Realm realm) { | |
if (keyMap != null) { | |
throw new IllegalStateException("Factory has already been initialized. Call reset() before initializing again."); | |
} | |
RealmSchema schema = realm.getSchema(); | |
HashMap<Class<? extends RealmModel>, AtomicLong> map = new HashMap<>(); | |
final RealmConfiguration configuration = realm.getConfiguration(); | |
for (final Class<? extends RealmModel> c : configuration.getRealmObjectClasses()) { | |
String className = c.getSimpleName(); | |
RealmObjectSchema objectSchema = schema.get(className); | |
if (objectSchema.hasPrimaryKey()) { | |
String fieldName = objectSchema.getPrimaryKey(); | |
RealmFieldType type = objectSchema.getFieldType(fieldName); | |
if (type == RealmFieldType.INTEGER) { | |
Number val = realm.where(c).max(fieldName); | |
AtomicLong keyGenerator = new AtomicLong(val != null ? val.longValue() : -1); | |
map.put(c, keyGenerator); | |
} | |
} | |
} | |
keyMap = map; | |
} | |
/** | |
* Reset this factory and all generated values. | |
* Call {@link #init(Realm)} before using this class again. | |
*/ | |
public static synchronized void reset() { | |
keyMap = null; | |
} | |
/** | |
* Generate the next primary key for a class. Starting from {@code 0}. | |
* | |
* @param clazz class to generate the next key for. | |
*/ | |
public static long nextKey(final Class<? extends RealmObject> clazz) { | |
AtomicLong generator = keyMap.get(clazz); | |
return generator.incrementAndGet(); | |
} | |
} |
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
/* | |
* Copyright 2017 Realm Inc. | |
* | |
* 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 io.realm; | |
import android.content.Context; | |
import android.support.test.InstrumentationRegistry; | |
import android.support.test.runner.AndroidJUnit4; | |
import org.junit.After; | |
import org.junit.Before; | |
import org.junit.Rule; | |
import org.junit.Test; | |
import org.junit.runner.RunWith; | |
import io.realm.entities.AllJavaTypes; | |
import io.realm.entities.AllTypes; | |
import io.realm.entities.PrimaryKeyAsString; | |
import io.realm.rule.TestRealmConfigurationFactory; | |
import static org.junit.Assert.assertEquals; | |
import static org.junit.Assert.fail; | |
// Require some classes only available in https://github.com/realm/realm-java/tree/master/realm/realm-library/src/androidTest | |
@RunWith(AndroidJUnit4.class) | |
public class PrimaryKeyFactoryTests { | |
@Rule | |
public final TestRealmConfigurationFactory configFactory = new TestRealmConfigurationFactory(); | |
private RealmConfiguration realmConfig; | |
private Realm realm; | |
@Before | |
public void setUp() { | |
Context context = InstrumentationRegistry.getInstrumentation().getContext(); | |
Realm.init(context); | |
RealmConfiguration realmConfig = configFactory.createConfigurationBuilder().build(); | |
realm = Realm.getInstance(realmConfig); | |
PrimaryKeyFactory.init(realm); | |
} | |
@After | |
public void tearDown() { | |
PrimaryKeyFactory.reset(); | |
realm.close(); | |
} | |
@Test | |
public void init_twiceThrows() { | |
try { | |
// Initialized first time in `setUp()` | |
PrimaryKeyFactory.init(realm); | |
fail(); | |
} catch (IllegalStateException ignored) { | |
} | |
} | |
@Test | |
public void getKey_staticClass() { | |
assertEquals(0, PrimaryKeyFactory.nextKey(AllJavaTypes.class)); | |
assertEquals(1, PrimaryKeyFactory.nextKey(AllJavaTypes.class)); | |
} | |
@Test | |
public void getKey_wrongPrimaryKeyType() { | |
try { | |
PrimaryKeyFactory.nextKey(PrimaryKeyAsString.class); | |
fail(); | |
} catch (NullPointerException ignored) { | |
} | |
} | |
@Test | |
public void getKey_noPrimaryKey() { | |
try { | |
PrimaryKeyFactory.nextKey(AllTypes.class); | |
fail(); | |
} catch (NullPointerException ignored) { | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment