content: /* The contents of this file are subject to the terms
   * of the Common Development and Distribution License
   * (the License). You may not use this file except in
   * compliance with the License.
   *
   * You can obtain a copy of the License at
   * http://www.sun.com/cddl/cddl.html or
   * install_dir/legal/LICENSE
   * See the License for the specific language governing
   * permission and limitations under the License.
   *
   * When distributing Covered Code, include this CDDL
   * Header Notice in each file and include the License file
   * at install_dir/legal/LICENSE.
   * If applicable, add the following below the CDDL Header,
   * with the fields enclosed by brackets [] replaced by
   * your own identifying information:
   * "Portions Copyrighted [year] [name of copyright owner]"
   *
   * $Id$
   *
   * Copyright 2005-2009 Sun Microsystems Inc. All Rights Reserved
   */
  package com.sun.faban.driver.engine;
  
  import com.sun.faban.driver.ConfigurationException;
  import com.sun.faban.driver.DefinitionException;
  import com.sun.faban.driver.util.Random;
  import org.w3c.dom.Element;
  import org.w3c.dom.NodeList;
  
  import java.util.HashMap;
  import java.util.Iterator;
  import java.lang.annotation.Annotation;
  
  /**
   * The implementation of the flat mix annotation.
   *
   * @author Akara Sucharitakul
   */
  public class FlatMix extends Mix {
  
  	private static final long serialVersionUID = 1L;
  
  	double[] mix;
  
      /**
       * Initializes this mix according to the annotation.
       *
       * @param driverClass The driver class annotating this mix
       * @param a           The annotation
       * @throws DefinitionException If there is an error in the annotation
       */
  	public void init(Class<?> driverClass, Annotation a)
              throws DefinitionException {
          com.sun.faban.driver.FlatMix flatMix =
                  (com.sun.faban.driver.FlatMix) a;
          operations = BenchmarkDefinition.getOperations(driverClass,
                  flatMix.operations());
          mix = flatMix.mix();
          deviation = flatMix.deviation();
          validate();
      }
  
      /**
       * Initializes the default FlatMix without an annotation. 
       * @param driverClass The driver class in question
       * @throws DefinitionException 
       */
      public void init(Class<?> driverClass) throws DefinitionException {
          operations = BenchmarkDefinition.getOperations(driverClass);
          mix = new double[operations.length];
          for (int i = 0; i < mix.length; i++) {
  			mix[i] = 1d;
  		}
          deviation = 2d; // Default allowed deviation is 2%
      }
  
      /**
       * Creates and returns a copy of this object.  The precise meaning
       * of "copy" may depend on the class of the object. The general
       * intent is that, for any object <tt>x</tt>, the expression:
       * <blockquote>
       * <pre>
       * x.clone() != x</pre></blockquote>
       * will be true, and that the expression:
       * <blockquote>
       * <pre>
       * x.clone().getClass() == x.getClass()</pre></blockquote>
       * will be <tt>true</tt>, but these are not absolute requirements.
       * While it is typically the case that:
       * <blockquote>
       * <pre>
       * x.clone().equals(x)</pre></blockquote>
       * will be <tt>true</tt>, this is not an absolute requirement.
       * <p/>
       * By convention, the returned object should be obtained by calling
       * <tt>super.clone</tt>.  If a class and all of its superclasses (except
       * <tt>Object</tt>) obey this convention, it will be the case that
       * <tt>x.clone().getClass() == x.getClass()</tt>.
       * <p/>
       * By convention, the object returned by this method should be independent
       * of this object (which is being cloned).  To achieve this independence,
       * it may be necessary to modify one or more fields of the object returned
       * by <tt>super.clone</tt> before returning it.  Typically, this means
       * copying any mutable objects that comprise the internal "deep structure"
       * of the object being cloned and replacing the references to these
       * objects with references to the copies.  If a class contains only
       * primitive fields or references to immutable objects, then it is usually
       * the case that no fields in the object returned by <tt>super.clone</tt>
       * need to be modified.
       * <p/>
       * The method <tt>clone</tt> for class <tt>Object</tt> performs a
       * specific cloning operation. First, if the class of this object does
       * not implement the interface <tt>Cloneable</tt>, then a
       * <tt>CloneNotSupportedException</tt> is thrown. Note that all arrays
       * are considered to implement the interface <tt>Cloneable</tt>.
       * Otherwise, this method creates a new instance of the class of this
       * object and initializes all its fields with exactly the contents of
       * the corresponding fields of this object, as if by assignment; the
       * contents of the fields are not themselves cloned. Thus, this method
       * performs a "shallow copy" of this object, not a "deep copy" operation.
       * <p/>
       * The class <tt>Object</tt> does not itself implement the interface
       * <tt>Cloneable</tt>, so calling the <tt>clone</tt> method on an object
       * whose class is <tt>Object</tt> will result in throwing an
       * exception at run time.
       *
       * @return a clone of this instance.
       * @see Cloneable
       */
      @Override
  	public Object clone() {
          FlatMix clone = (FlatMix) super.clone();
          clone.mix = new double[mix.length];
          System.arraycopy(mix, 0, clone.mix, 0, mix.length);
          return clone;
      }
  
      /**
       * Configures the flat mix based on the ratios given in the
       * configuration file.
       * @param driverConfigNode The DOM node holding the driver config
       * @throws ConfigurationException   If the configuration is invalid
       *                                  for the mix
       */
  	public void configure(Element driverConfigNode)
              throws ConfigurationException {
          /* The format is as follows:
          <operationMix>
              <name>operationF</name><r>40</r>
          </operationMix>
          <operationMix>
              <name>operationG</name><r>30</r>
          </operationMix>
          <operationMix>
              <name>operationH</name><r>30</r>
          </operationMix>
          */
  
          HashMap<String, Double> ratioMap =
                  new HashMap<String, Double>(operations.length);
          NodeList operationList = driverConfigNode.
                  getElementsByTagNameNS(RunInfo.DRIVERURI, "operationMix");
          int size0 = operationList.getLength();
          for (int i = 0; i < size0; i++) {
              Element opMix = (Element) operationList.item(i);
              NodeList nl = opMix.getElementsByTagNameNS(
                                                      RunInfo.DRIVERURI, "name");
              if (nl.getLength() > 1) {
                  String msg = "Only one operation name allowed in each " +
                          "operation mix.";
                  getLogger().severe(msg);
                  ConfigurationException e = new ConfigurationException(msg);
                  getLogger().throwing(className, "configure", e);
                  throw e;
              }
              String name = nl.item(0).getFirstChild().getNodeValue();
              nl = opMix.getElementsByTagNameNS(RunInfo.DRIVERURI, "r");
              if (nl.getLength() > 1) {
                  String msg = "Only one ratio allowed for @FlatMix";
                  getLogger().severe(msg);
                  ConfigurationException e = new ConfigurationException(msg);
                  getLogger().throwing(className, "configure", e);
                  throw e;
              }
              Double ratio = new Double(nl.item(0).getFirstChild().
                      getNodeValue());
              ratioMap.put(name, ratio);
          }
  
          if (ratioMap.size() <= 0) {
  			return;
  		}
  
          for (int i = 0; i < operations.length; i++) {
              Double value = ratioMap.remove(operations[i].name);
              if (value == null) {
                  String msg = "Configured ratio for operation " +
                          operations[i].name + " not found";
                  getLogger().severe(msg);
                  ConfigurationException e = new ConfigurationException(msg);
                  getLogger().throwing(className, "configure", e);
                  throw e;
              }
              mix[i] = value.doubleValue();
          }
  
          // By now, the map should be empty
          if (ratioMap.size() > 0) {
              String msg = "";
              for (Iterator<String> i = ratioMap.keySet().iterator();
                   i.hasNext();) {
                  if (msg.length() > 0) {
  					msg += ", ";
  				}
                  msg += i.next();
              }
              msg = "Invalid operation name(s) in operationMix " +
                      "configuration: " + msg + '.';
              getLogger().severe(msg);
              ConfigurationException e = new ConfigurationException(msg);
              getLogger().throwing(className, "configure", e);
              throw e;
          }
      }
  
      /**
       * Validates the mix spec in the benchmark definitions to ensure all
       * the rows and columns are valid.
       * @throws DefinitionException Found missing or
       *                                                  invalid row/column
       */
      public void validate() throws DefinitionException {
          if (mix.length != operations.length) {
              String msg = "Mix ratios must be " + operations.length +
                      "(#ops) in size!\nFound " + mix.length + ".";
              getLogger().severe(msg);
              DefinitionException e = new DefinitionException(msg);
              getLogger().throwing(className, "validate", e);
              throw e;
          }
      }
  
      /**
       * Normalizes the mix so each row will have a sum of 1.0d. This is
       * important as we do not require the spec input to sum to this
       * amount but the selector (doMenu) will base the random number
       * generator to 1.
       */
  	public void normalize() {
          // if (logger.isLoggable(Level.FINEST))
              getLogger().finest("normalize - before\n" + toString());
  
          double rowTotal = 0d;
          for (int i = 0; i < mix.length; i++) {
  			rowTotal += mix[i];
  		}
          for (int i = 0; i < mix.length; i++) {
  			mix[i] /= rowTotal;
  		}
  
          // if (logger.isLoggable(Level.FINEST))
              getLogger().finest("normalize - after\n" + toString());
      }
  
      /**
       * Returns the flat mix representation of this mix. This is used for
       * consistent reporting.
       *
       * @return The flat mix representation.
       */
  	public FlatMix flatMix() {
          return this;
      }
  
      /**
       * Provides a string representation of this FlatMix.
       * @return The string representation
       * @see java.lang.Object#toString()
       */
      @Override
  	public String toString() {
          StringBuffer buffer = new StringBuffer();
          buffer.append("FlatMix\n");
          buffer.append("operations: ");
          buffer.append(operations[0].name);
          for (int i = 1; i < operations.length; i++) {
              buffer.append(", ");
              buffer.append(operations[i].name);
          }
          buffer.append("\nmix: ");
          buffer.append(mix[0]);
          for (int i = 1; i < mix.length; i++) {
              buffer.append(", ");
              buffer.append(mix[i]);
          }
          buffer.append("\ndeviation :");
          buffer.append(deviation);
          buffer.append('\n');
  
          return buffer.toString();
      }
  
      /**
       * Obtains the per-thread and per-driver instance selector.
       *
       * @param random The per-thread random value generator
       * @return The selector to be used by the driver
       */
  	public Selector selector(Random random) {
          return new Selector(random, mix);
      }
  
      /**
       * The selector implementation for the FlatMix.
       */
      public static class Selector extends Mix.Selector {
  
          private Random random;
          private double[] selectMix;
  
          Selector(Random random, double[] mix) {
              this.random = random;
              selectMix = new double[mix.length];
              selectMix[0] = mix[0];
              for (int i = 1; i < selectMix.length; i++) {
                  selectMix[i] = mix[i] + selectMix[i - 1];
              }
          }
  
          /**
           * The select method selects the operation to run next.
           *
           * @return The operation index selected to run next
           */
  		public int select() {
              double val = random.drandom(0, 1);
              for (int i = 0; i < selectMix.length; i++) {
  				if (val <= selectMix[i]) {
  					return i;
  				}
  			}
              return -1;
          }
  
          /**
           * Resets the selector's state to start at the first op,
           * if applicable.
           */
  		public void reset() {
              // Noop... there's nothing to reset in a flatmix as it does
              // not contain any state.
          }
      }
  }
  