/* * Copyright 2015 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 io.realm.exceptions.RealmException; import io.realm.exceptions.RealmFileException; import io.realm.internal.CheckedRow; import io.realm.internal.OsObject; import io.realm.internal.Table; import io.realm.log.RealmLog; import rx.Observable; /** * DynamicRealm is a dynamic variant of {@link io.realm.Realm}. This means that all access to data and/or queries are * done using string based class names instead of class type references. * <p> * This is useful during migrations or when working with string-based data like CSV or XML files. * <p> * The same {@link io.realm.RealmConfiguration} can be used to open a Realm file in both dynamic and typed mode, but * modifying the schema while having both a typed and dynamic version open is highly discouraged and will most likely * crash the typed Realm. During migrations only a DynamicRealm will be open. * <p> * Dynamic Realms do not enforce schemas or schema versions and {@link RealmMigration} code is not used even if it has * been defined in the {@link RealmConfiguration}. * <p> * This means that the schema is not created or validated until a Realm has been opened in typed mode. If a Realm * file is opened in dynamic mode first it will not contain any information about classes and fields, and any queries * for classes defined by the schema will fail. * * @see Realm * @see RealmSchema */ public class DynamicRealm extends BaseRealm { private DynamicRealm(RealmCache cache) { super(cache); } private DynamicRealm(RealmConfiguration configuration) { super(configuration); } /** * Realm static constructor that returns a dynamic variant of the Realm instance defined by provided * {@link io.realm.RealmConfiguration}. Dynamic Realms do not care about schemaVersion and schemas, so opening a * DynamicRealm will never trigger a migration. * * @return the DynamicRealm defined by the configuration. * @throws RealmFileException if an error happened when accessing the underlying Realm file. * @throws IllegalArgumentException if {@code configuration} argument is {@code null}. * @see RealmConfiguration for details on how to configure a Realm. */ public static DynamicRealm getInstance(RealmConfiguration configuration) { if (configuration == null) { throw new IllegalArgumentException("A non-null RealmConfiguration must be provided"); } return RealmCache.createRealmOrGetFromCache(configuration, DynamicRealm.class); } /** * The creation of the first Realm instance per {@link RealmConfiguration} in a process can take some time as all * initialization code need to run at that point (Setting up the Realm, validating schemas and creating initial * data). This method places the initialization work in a background thread and deliver the Realm instance * to the caller thread asynchronously after the initialization is finished. * * @param configuration {@link RealmConfiguration} used to open the Realm. * @param callback invoked to return the results. * @throws IllegalArgumentException if a null {@link RealmConfiguration} or a null {@link Callback} is provided. * @throws IllegalStateException if it is called from a non-Looper or {@link android.app.IntentService} thread. * @return a {@link RealmAsyncTask} representing a cancellable task. * @see Callback for more details. */ public static RealmAsyncTask getInstanceAsync(RealmConfiguration configuration, Callback callback) { if (configuration == null) { throw new IllegalArgumentException("A non-null RealmConfiguration must be provided"); } return RealmCache.createRealmOrGetFromCacheAsync(configuration, callback, DynamicRealm.class); } /** * Instantiates and adds a new object to the Realm. * * @param className the class name of the object to create. * @return the new object. * @throws RealmException if the object could not be created. */ public DynamicRealmObject createObject(String className) { checkIfValid(); Table table = schema.getTable(className); // Check and throw the exception earlier for a better exception message. if (table.hasPrimaryKey()) { throw new RealmException(String.format("'%s' has a primary key, use" + " 'createObject(String, Object)' instead.", className)); } return new DynamicRealmObject(this, CheckedRow.getFromRow(OsObject.create(sharedRealm, table))); } /** * Creates an object with a given primary key. Classes without a primary key defined must use * {@link #createObject(String)}} instead. * * @return the new object. All fields will have default values for their type, except for the * primary key field which will have the provided value. * @throws RealmException if object could not be created due to the primary key being invalid. * @throws IllegalStateException if the model clazz does not have an primary key defined. * @throws IllegalArgumentException if the {@code primaryKeyValue} doesn't have a value that can be converted to the * expected value. */ public DynamicRealmObject createObject(String className, Object primaryKeyValue) { Table table = schema.getTable(className); return new DynamicRealmObject(this, CheckedRow.getFromRow(OsObject.createWithPrimaryKey(sharedRealm, table, primaryKeyValue))); } /** * Returns a RealmQuery, which can be used to query the provided class. * * @param className the class of the object which is to be queried. * @return a RealmQuery, which can be used to query for specific objects of provided type. * @throws IllegalArgumentException if the class doesn't exist. * @see io.realm.RealmQuery */ public RealmQuery<DynamicRealmObject> where(String className) { checkIfValid(); if (!sharedRealm.hasTable(Table.getTableNameForClass(className))) { throw new IllegalArgumentException("Class does not exist in the Realm and cannot be queried: " + className); } return RealmQuery.createDynamicQuery(this, className); } /** * Adds a change listener to the Realm. * <p> * The listeners will be executed when changes are committed by this or another thread. * <p> * Realm instances are cached per thread. For that reason it is important to * remember to remove listeners again either using {@link #removeChangeListener(RealmChangeListener)} * or {@link #removeAllChangeListeners()}. Not doing so can cause memory leaks. * * @param listener the change listener. * @throws IllegalArgumentException if the change listener is {@code null}. * @see io.realm.RealmChangeListener * @see #removeChangeListener(RealmChangeListener) * @see #removeAllChangeListeners() * @see #waitForChange() */ public void addChangeListener(RealmChangeListener<DynamicRealm> listener) { addListener(listener); } /** * Removes the specified change listener. * * @param listener the change listener to be removed. * @throws IllegalArgumentException if the change listener is {@code null}. * @throws IllegalStateException if you try to remove a listener from a non-Looper Thread. * @see io.realm.RealmChangeListener */ public void removeChangeListener(RealmChangeListener<DynamicRealm> listener) { removeListener(listener); } /** * Removes all user-defined change listeners. * * @throws IllegalStateException if you try to remove listeners from a non-Looper Thread. * @see io.realm.RealmChangeListener */ public void removeAllChangeListeners() { removeAllListeners(); } /** * Deletes all objects of the specified class from the Realm. * * @param className the class for which all objects should be removed. */ public void delete(String className) { checkIfValid(); checkIfInTransaction(); schema.getTable(className).clear(); } /** * Executes a given transaction on the DynamicRealm. {@link #beginTransaction()} and * {@link #commitTransaction()} will be called automatically. If any exception is thrown * during the transaction {@link #cancelTransaction()} will be called instead of {@link #commitTransaction()}. * * @param transaction {@link io.realm.DynamicRealm.Transaction} to execute. * @throws IllegalArgumentException if the {@code transaction} is {@code null}. */ public void executeTransaction(Transaction transaction) { if (transaction == null) { throw new IllegalArgumentException("Transaction should not be null"); } beginTransaction(); try { transaction.execute(this); commitTransaction(); } catch (RuntimeException e) { if (isInTransaction()) { cancelTransaction(); } else { RealmLog.warn("Could not cancel transaction, not currently in a transaction."); } throw e; } } /** * Creates a {@link DynamicRealm} instance without checking the existence in the {@link RealmCache}. * * @return a {@link DynamicRealm} instance. */ static DynamicRealm createInstance(RealmCache cache) { return new DynamicRealm(cache); } /** * Create a {@link DynamicRealm} instance without associating it to any RealmCache. * * @return a {@link DynamicRealm} instance. */ static DynamicRealm createInstance(RealmConfiguration configuration) { return new DynamicRealm(configuration); } /** * {@inheritDoc} */ @Override public Observable<DynamicRealm> asObservable() { return configuration.getRxFactory().from(this); } /** * Encapsulates a Realm transaction. * <p> * Using this class will automatically handle {@link #beginTransaction()} and {@link #commitTransaction()} * If any exception is thrown during the transaction {@link #cancelTransaction()} will be called * instead of {@link #commitTransaction()}. */ public interface Transaction { void execute(DynamicRealm realm); } /** * {@inheritDoc} */ public static abstract class Callback extends InstanceCallback<DynamicRealm> { /** * {@inheritDoc} */ @Override public abstract void onSuccess(DynamicRealm realm); /** * {@inheritDoc} */ @Override public void onError(Throwable exception) { super.onError(exception); } } }