001package arez.persist.runtime;
002
003import arez.SafeProcedure;
004import arez.persist.StoreTypes;
005import grim.annotations.OmitClinit;
006import grim.annotations.OmitSymbol;
007import javax.annotation.Nonnull;
008import javax.annotation.Nullable;
009import static org.realityforge.braincheck.Guards.*;
010
011/**
012 * Provide an interface to register and access stores and access the root scope as well as global configuration settings.
013 */
014@OmitClinit
015public final class ArezPersist
016{
017  private ArezPersist()
018  {
019  }
020
021  /**
022   * Return true if apiInvariants will be checked.
023   *
024   * @return true if apiInvariants will be checked.
025   */
026  @OmitSymbol
027  public static boolean shouldCheckApiInvariants()
028  {
029    return ArezPersistConfig.shouldCheckApiInvariants();
030  }
031
032  /**
033   * Return true if the in-memory application store should be registered by the framework.
034   *
035   * @return true if the in-memory application store should be registered by the framework.
036   */
037  @OmitSymbol
038  public static boolean isApplicationStoreEnabled()
039  {
040    return ArezPersistConfig.isApplicationStoreEnabled();
041  }
042
043  /**
044   * Return the root scope under which all other scopes are nested.
045   *
046   * @return the root scope under which all other scopes are nested.
047   */
048  @Nonnull
049  public static Scope getRootScope()
050  {
051    return Registry.getRootScope();
052  }
053
054  /**
055   * Register a store with specified name and storage service.
056   * It is an error to register multiple stores with the same name.
057   *
058   * <p>As part of the register operation, the store will attempt to restore state from the storage service.
059   * If an error occurs during the restore, then the error will be logged and registration will complete.</p>
060   *
061   * @param name    the name of the store.
062   * @param service the associated StorageService.
063   * @return the action to invoke to deregister service.
064   */
065  @Nonnull
066  public static SafeProcedure registerStore( @Nonnull final String name, @Nonnull final StorageService service )
067  {
068    return Registry.registerStore( name, service );
069  }
070
071  /**
072   * Return the store that is registered with the specified name.
073   * It is an error to invoke this method without registering a store under this name.
074   *
075   * @param name the name of the store.
076   * @return the store.
077   */
078  @Nonnull
079  public static Store getStore( @Nonnull final String name )
080  {
081    return Registry.getStore( name );
082  }
083
084  /**
085   * Find the scope with the specified name.
086   * The name can actually consist of name components separated by a "." character. Each name component is
087   * nested within the scope identified by the prior name component. i.e. The name "dashboard.finance.entry"
088   * will look for the scope named "entry" nested in a scope named "finance" nested in a scope named "dashboard".
089   *
090   * @param qualifiedName the qualified scope name.
091   * @return the scope if it exists.
092   */
093  @Nullable
094  public static Scope findScope( @Nonnull final String qualifiedName )
095  {
096    Scope scope = getRootScope();
097    if ( Scope.ROOT_SCOPE_NAME.equals( qualifiedName ) )
098    {
099      return scope;
100    }
101    int start = 0;
102    int end;
103    while ( -1 != ( end = qualifiedName.indexOf( '.', start ) ) )
104    {
105      scope = scope.findScope( qualifiedName.substring( start, end ) );
106      if ( null == scope )
107      {
108        return null;
109      }
110      else
111      {
112        start = end + 1;
113      }
114    }
115    return scope.findScope( qualifiedName.substring( start ) );
116  }
117
118  /**
119   * Find the scope with the specified name and if it does not exist then create it.
120   * The name can actually consist of name components separated by a "." character. Each name component is
121   * nested within the scope identified by the prior name component. i.e. The name "dashboard.finance.entry"
122   * will look for the scope named "entry" nested in a scope named "finance" nested in a scope named "dashboard".
123   *
124   * @param qualifiedName the qualified scope name.
125   * @return the scope.
126   */
127  @Nonnull
128  public static Scope findOrCreateScope( @Nonnull final String qualifiedName )
129  {
130    Scope scope = getRootScope();
131    if ( Scope.ROOT_SCOPE_NAME.equals( qualifiedName ) )
132    {
133      return scope;
134    }
135    int start = 0;
136    int end;
137    while ( -1 != ( end = qualifiedName.indexOf( '.', start ) ) )
138    {
139      scope = scope.findOrCreateScope( qualifiedName.substring( start, end ) );
140      start = end + 1;
141    }
142    return scope.findOrCreateScope( qualifiedName.substring( start ) );
143  }
144
145  /**
146   * Dispose the specified scope.
147   * A dispose operation first performs a {@link #releaseScope(Scope)} on the scope, then attempts to
148   * dispose all nested scopes and finally disposes the specified scope. A disposed scope should no longer be
149   * used to store state. It is an error to attempt to dispose the root scope.
150   *
151   * @param scope the scope to dispose.
152   */
153  public static void disposeScope( @Nonnull final Scope scope )
154  {
155    if ( ArezPersist.shouldCheckApiInvariants() )
156    {
157      apiInvariant( () -> !Scope.ROOT_SCOPE_NAME.equals( scope.getName() ),
158                    () -> "disposeScope() invoked with the root scope" );
159    }
160    Registry.disposeScope( scope );
161  }
162
163  /**
164   * Release the specified scope.
165   * A release operation removes any state associated with the scope and any nested scope.
166   *
167   * @param scope the scope to release.
168   */
169  public static void releaseScope( @Nonnull final Scope scope )
170  {
171    Registry.releaseScope( scope );
172  }
173
174  /**
175   * Register a converter for a type.
176   * It is an error to register multiple converters with the same name.
177   *
178   * @param type      the application type.
179   * @param converter the converter.
180   * @return the action to invoke to deregister converter.
181   * @param <A> the type of the value.
182   */
183  @Nonnull
184  public static <A> SafeProcedure registerConverter( @Nonnull final Class<A> type,
185                                                     @Nonnull final Converter<A, ?> converter )
186  {
187    return Registry.registerConverter( type, converter );
188  }
189
190  /**
191   * Return the converter registered for the specified application type or the identity converter if none are specified.
192   *
193   * @param type the application type.
194   * @return the converter if any.
195   * @param <A> the type of the value.
196   */
197  @Nonnull
198  public static <A> Converter<A, ?> getConverter( @Nonnull final Class<A> type )
199  {
200    return Registry.getConverter( type );
201  }
202
203  static void registerApplicationStoreIfEnabled()
204  {
205    if ( isApplicationStoreEnabled() )
206    {
207      registerStore( StoreTypes.APPLICATION, new NoopStorageService() );
208    }
209  }
210}