/*
 * $Id: AbstractOpImpl.java 4252 2008-10-15 13:35:32Z uckelman $
 *
 * Copyright (c) 2007-2008 by Joel Uckelman
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License (LGPL) as published by the Free Software Foundation.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, copies are available
 * at http://www.opensource.org.
 */

package VASSAL.tools.opcache;

import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

/**
 * An abstract representation of an operation. <code>AbstractOpImpl</code>
 * is the base class for all such operations. The results of all operations
 * are memoized (using a memory-sensitive cache), so retrieving results is
 * both fast and memory-efficient.
 * 
 * <p><b>Warning:</b> For efficiency reasons, the methods {@link #get()} and
 * {@link #get(OpObserver)} do <em>not</em> return defensively, nor do the
 * {@code Future}s returned by {@link #getFuture(OpObserver)}. That is, the
 * object returned is possibly the one retained internally by the
 * <code>AbstractOpImpl</code>. Therefore, objects obtained from an
 * <code>AbstractOpImpl</code> <em>must not</em> be altered, as this might 
 * interfere with caching. If an object obtained this way needs to be
 * modified, copy the object first and alter the copy.</p>
 *
 * @author Joel Uckelman
 * @since 3.1.0
 */
public abstract class AbstractOpImpl<V> implements Op<V> {
  /** The cache which contains calculated results. */
  protected final OpCache cache;

  /** 
   * @param cache the cache for storing our result
   */  
  public AbstractOpImpl(OpCache cache) {
    this.cache = cache;
  }

  /** {@inheritDoc} */
  public abstract List<Op<?>> getSources();

  /** {@inheritDoc} */
  public abstract V eval() throws Exception;

  /** {@inheritDoc} */
  public V get() {
    return cache.get(newKey());
  }

  /**
   * {@inheritDoc}
   *
   * @throws CancellationException if the operation was cancelled
   * @throws InterruptedException if the operation was interrupted
   * @throws ExecutionException if the operation failed
   */
  public V get(OpObserver<V> obs) throws CancellationException,
                                         InterruptedException,
                                         ExecutionException {
    return cache.get(newKey(), obs);
  }
  
  /**
   * {@inheritDoc}
   *
   * @throws CancellationException if the operation was cancelled
   * @throws InterruptedException if the operation was interrupted
   * @throws ExecutionException if the operation failed
   */  
  public Future<V> getFuture(OpObserver<V> obs) throws ExecutionException {
    return cache.getFuture(newKey(), obs);
  }

  private static final ConcurrentMap<Op<?>,OpCache.Key<?>> kcache =
    new ConcurrentHashMap<Op<?>,OpCache.Key<?>>();

  /** {@inheritDoc} */  
  @SuppressWarnings("unchecked")
  public OpCache.Key<V> newKey() {
    OpCache.Key<V> key = (OpCache.Key<V>) kcache.get(this);
    if (key == null) {
      final OpCache.Key<V> nkey = new OpCache.Key<V>(this, 0);
      key = (OpCache.Key<V>) kcache.putIfAbsent(this, nkey);
      if (key == null) key = nkey;
    }

    return key;
  }

  /** {@inheritDoc} */  
  @SuppressWarnings("unchecked")
  public void update() {
    final OpCache.Key<V> key = (OpCache.Key<V>) kcache.get(this);
    if (key != null) {
      final OpCache.Key<V> nkey = new OpCache.Key<V>(this, key.version+1);
      kcache.replace(this, key, nkey);
    }
  }
}
