/*
 *  This file is part of the Jikes RVM project (http://jikesrvm.org).
 *
 *  This file is licensed to You under the Common Public License (CPL);
 *  You may not use this file except in compliance with the License. You
 *  may obtain a copy of the License at
 *
 *      http://www.opensource.org/licenses/cpl1.0.php
 *
 *  See the COPYRIGHT.txt file distributed with this work for information
 *  regarding copyright ownership.
 */
package org.jikesrvm.compilers.opt.specialization;

import java.util.Iterator;

import org.jikesrvm.classloader.RVMMethod;
import org.jikesrvm.util.ImmutableEntryHashMapRVM;
import org.jikesrvm.util.HashSetRVM;

/**
 * Database to store multiple specialized versions for a given method.
 *
 * <p> The overall design is very similar to that of the
 * InvalidationDatabase (see InvalidationDatabase.java)
 * In this database, the key is the RVMMethod object of the source method
 * and the value is a method set. The method set is a list of
 * specialized versions of the method pointed by the key. Specialized
 * versions are represented by using the SpecializedMethod class.
 * There is no provision for removing/deleting method versions as classes
 * are never unloaded and the ClassLoader.compiledMethods[] is never cleaned.
 */
public final class SpecializationDatabase {

  private static boolean specializationInProgress;

  private static final HashSetRVM<SpecializedMethod> deferredMethods =
    new HashSetRVM<SpecializedMethod>();

  private static final ImmutableEntryHashMapRVM<RVMMethod, MethodSet<RVMMethod>> specialVersionsHash =
      new ImmutableEntryHashMapRVM<RVMMethod, MethodSet<RVMMethod>>();

  /**
   * Drain the queue of methods waiting for specialized code
   * generation.
   */
  public static synchronized void doDeferredSpecializations() {
    // prevent recursive entry to this method
    if (specializationInProgress) {
      return;
    }
    specializationInProgress = true;
    Iterator<SpecializedMethod> methods = deferredMethods.iterator();
    while (methods.hasNext()) {
      SpecializedMethod m = methods.next();
      if (m.getCompiledMethod() == null) {
        m.compile();
        registerCompiledMethod(m);
      }
      deferredMethods.remove(m);
      // since we modified the set, reset the iterator.
      // TODO: use a better abstraction
      // (ModifiableSetIterator of some kind?)
      methods = deferredMethods.iterator();
    }
    specializationInProgress = false;
  }

  // write the new compiled method in the specialized method pool
  private static void registerCompiledMethod(SpecializedMethod m) {
    SpecializedMethodPool.registerCompiledMethod(m);
  }

  /**
   * Return an iteration of SpecializedMethods that represents
   * specialied compiled versions of the method pointed by RVMMethod
   * @return null if no specialized versions
   */
  static synchronized Iterator<SpecializedMethod> getSpecialVersions(RVMMethod m) {
    MethodSet<RVMMethod> s = specialVersionsHash.get(m);
    if (s == null) {
      return null;
    } else {
      return s.iterator();
    }
  }

  static int getSpecialVersionCount(RVMMethod m) {
    Iterator<SpecializedMethod> versions = getSpecialVersions(m);
    int count = 0;
    if (versions != null) {
      while (versions.hasNext() && (versions.next() != null)) {
        count++;
      }
    }
    return count;
  }

  /**
   * Record a new specialized method in this database.
   * Also remember that this method will need to be compiled later,
   * at the next call to <code> doDeferredSpecializations() </code>
   */
  static synchronized void registerSpecialVersion(SpecializedMethod spMethod) {
    RVMMethod source = spMethod.getMethod();
    MethodSet<RVMMethod> s = findOrCreateMethodSet(specialVersionsHash, source);
    s.add(spMethod);
    deferredMethods.add(spMethod);
  }

  /**
   * Look up the MethodSet corresponding to a given key in the database
   * If none found, create one.
   */
  private static <T> MethodSet<T> findOrCreateMethodSet(ImmutableEntryHashMapRVM<T, MethodSet<T>> hash, T key) {
    MethodSet<T> result = hash.get(key);
    if (result == null) {
      result = new MethodSet<T>(key);
      hash.put(key, result);
    }
    return result;
  }

  /**
   * The following defines a set of methods that share a common "key"
   */
  static class MethodSet<T> {
    final T key;

    /**
     * a set of SpecializedMethod
     */
    final HashSetRVM<SpecializedMethod> methods = new HashSetRVM<SpecializedMethod>();

    MethodSet(T key) {
      this.key = key;
    }

    void add(SpecializedMethod spMethod) {
      methods.add(spMethod);
    }

    public Iterator<SpecializedMethod> iterator() {
      return methods.iterator();
    }
  }
}
