content: /*******************************************************************************
   * Copyright 2014 Trevor Robinson
   * 
   * 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 com.scurrilous.circe.impl;
  
  import java.util.EnumSet;
  import java.util.concurrent.Callable;
  import java.util.concurrent.ExecutionException;
  import com.scurrilous.circe.Hash;
  import com.scurrilous.circe.HashParameters;
  import com.scurrilous.circe.HashProvider;
  import com.scurrilous.circe.HashSupport;
  import com.scurrilous.circe.IncrementalIntHash;
  import com.scurrilous.circe.IncrementalLongHash;
  import com.scurrilous.circe.StatefulHash;
  import com.scurrilous.circe.StatefulIntHash;
  import com.scurrilous.circe.StatefulLongHash;
  import com.scurrilous.circe.StatelessHash;
  import com.scurrilous.circe.StatelessIntHash;
  import com.scurrilous.circe.StatelessLongHash;
  
  /**
   * Base implementation for hash function providers.
   * 
   * @param <P> base supported hash parameters type
   */
  public abstract class AbstractHashProvider<P extends HashParameters> implements HashProvider {
  
      private final Class<P> parametersClass;
  
      /**
       * Constructs a new {@link AbstractHashProvider} with the given base
       * parameters class.
       * 
       * @param parametersClass the base hash parameters class supported
       */
      protected AbstractHashProvider(Class<P> parametersClass) {
          this.parametersClass = parametersClass;
      }
  
      @Override
      public final EnumSet<HashSupport> querySupport(HashParameters params) {
          if (!parametersClass.isAssignableFrom(params.getClass()))
              return EnumSet.noneOf(HashSupport.class);
          return querySupportTyped(parametersClass.cast(params));
      }
  
      /**
       * Implemented by subclasses to provide information about the available
       * implementations corresponding to the given hash algorithm parameters.
       * Called by {@link #querySupport} if the hash parameters match the base
       * type supported by this provider.
       * 
       * @param params the hash algorithm parameters
       * @return a set of flags indicating the level of support
       */
      protected abstract EnumSet<HashSupport> querySupportTyped(P params);
  
      /**
       * Requests a hash function using the given parameters and support flags.
       * This method is only responsible for checking support flags returned by
       * {@link #querySupportTyped}.
       * <p>
       * To support caching of stateless hash functions, call
       * {@link #getCacheable} from this method and implement
       * {@link #createCacheable}.
       * 
       * @param params the hash algorithm parameters
       * @param required the required hash support flags
       * @return a hash function
       * @throws UnsupportedOperationException if this provider cannot support the
       *             given parameters
       */
      protected abstract Hash get(P params, EnumSet<HashSupport> required);
  
      /**
       * Called by implementations that support caching of stateless hash
       * functions when a cached instance is desired. If a cached instance is not
       * available, this method calls {@link #createCacheable} to create one,
       * which is then cached (if caching is available).
       * 
       * @param params the hash algorithm parameters
       * @param required the required hash support flags
       * @return a hash function
       * @throws UnsupportedOperationException if this provider cannot support the
       *             given parameters
       */
      protected final Hash getCacheable(final P params, final EnumSet<HashSupport> required) {
          if (HashCacheLoader.hasCache()) {
              final HashCache cache = HashCacheLoader.getCache();
              try {
                  return cache.get(params, required, new Callable<Hash>() {
                      @Override
                      public Hash call() throws Exception {
                          return createCacheable(params, required);
                      }
                  });
              } catch (ExecutionException e) {
                  final Throwable cause = e.getCause();
                  if (cause instanceof RuntimeException)
                      throw (RuntimeException) cause;
                  throw new UnsupportedOperationException(e);
              }
          }
          return createCacheable(params, required);
      }
  
      /**
       * Called by {@link #getCacheable} to create new cacheable stateless hash
       * functions. The default implementation simply throws
       * {@link UnsupportedOperationException}.
       * 
       * @param params the hash algorithm parameters
       * @param required the required hash support flags
       * @return a stateless hash function
       * @throws UnsupportedOperationException if this provider cannot support the
       *             given parameters
       */
      protected StatelessHash createCacheable(P params, EnumSet<HashSupport> required) {
          throw new UnsupportedOperationException();
      }
  
      private Hash castAndGet(HashParameters params, EnumSet<HashSupport> required) {
          if (!parametersClass.isAssignableFrom(params.getClass()))
              throw new UnsupportedOperationException();
          return get(parametersClass.cast(params), required);
      }
  
      @Override
      public StatefulHash createStateful(HashParameters params) {
          final Hash hash = castAndGet(params, EnumSet.of(HashSupport.STATEFUL));
          if (hash instanceof StatefulHash)
              return (StatefulHash) hash;
          if (hash instanceof StatelessHash)
              return ((StatelessHash) hash).createStateful();
          throw new UnsupportedOperationException();
      }
  
      @Override
      public StatelessIntHash getStatelessInt(HashParameters params) {
          final Hash hash = castAndGet(params, EnumSet.of(HashSupport.INT_SIZED));
          if (hash instanceof StatelessIntHash)
              return (StatelessIntHash) hash;
          if (hash instanceof StatefulIntHash)
              return ((StatefulIntHash) hash).asStateless();
          throw new UnsupportedOperationException();
      }
  
      @Override
      public StatelessLongHash getStatelessLong(HashParameters params) {
          final Hash hash = castAndGet(params, EnumSet.of(HashSupport.LONG_SIZED));
          if (hash instanceof StatelessLongHash)
              return (StatelessLongHash) hash;
          if (hash instanceof StatefulLongHash)
              return ((StatefulLongHash) hash).asStateless();
          if (hash instanceof StatelessIntHash)
              return new IntStatelessLongHash((StatelessIntHash) hash);
          if (hash instanceof StatefulIntHash)
              return new IntStatelessLongHash(((StatefulIntHash) hash).asStateless());
          throw new UnsupportedOperationException();
      }
  
      @Override
      public IncrementalIntHash getIncrementalInt(HashParameters params) {
          final Hash hash = castAndGet(params,
                  EnumSet.of(HashSupport.INT_SIZED, HashSupport.STATELESS_INCREMENTAL));
          if (hash instanceof IncrementalIntHash)
              return (IncrementalIntHash) hash;
          throw new UnsupportedOperationException();
      }
  
      @Override
      public IncrementalLongHash getIncrementalLong(HashParameters params) {
          final Hash hash = castAndGet(params,
                  EnumSet.of(HashSupport.LONG_SIZED, HashSupport.STATELESS_INCREMENTAL));
          if (hash instanceof IncrementalLongHash)
              return (IncrementalLongHash) hash;
          throw new UnsupportedOperationException();
      }
  }
  