Skip to content

Instantly share code, notes, and snippets.

@cmelchior
Created April 7, 2017 12:01
  • Star 7 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save cmelchior/d8b944a025f52e1547317d4eef65f2cf to your computer and use it in GitHub Desktop.
Helper class for creating auto increment keys for Realm model classes
/*
* 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();
}
}
/*
* 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