Package org.timepedia.chronoscope.client

Source Code of org.timepedia.chronoscope.client.Datasets

package org.timepedia.chronoscope.client;

import com.google.gwt.user.client.Command;

import org.timepedia.chronoscope.client.data.AbstractDataset;
import org.timepedia.chronoscope.client.data.DatasetListener;
import org.timepedia.chronoscope.client.data.tuple.Tuple2D;
import org.timepedia.chronoscope.client.util.ArgChecker;
import org.timepedia.chronoscope.client.util.Interval;
import org.timepedia.chronoscope.client.util.MathUtil;
import org.timepedia.exporter.client.Export;
import org.timepedia.exporter.client.ExportPackage;
import org.timepedia.exporter.client.Exportable;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
* Container for {@link Dataset} objects that provides indexed access to the
* datasets as well as maintaining aggregate information. <p> This container
* registers itself as a {@link DatasetListener} to any added {@link
* org.timepedia.chronoscope.client.data.MutableDatasetND} objects in order to guarantee that aggregate information is
* kept up-to-date whenever constituent elements are modified.
*
* @author chad takahashi
*/
@ExportPackage("chronoscope")
public final class Datasets<T extends Tuple2D>
    implements Iterable<Dataset<T>>, Exportable {

  private double minInterval = Double.POSITIVE_INFINITY;

  private double minDomain = Double.POSITIVE_INFINITY, maxDomain
      = Double.NEGATIVE_INFINITY;

  private List<Dataset<T>> datasets = new ArrayList<Dataset<T>>();

  private List<DatasetListener<T>> listeners
      = new ArrayList<DatasetListener<T>>();

  private PrivateDatasetListener<T> myDatasetListener;

  private int mutating = 0;

  private List<Command> pending = new ArrayList<Command>();

  /**
   * Constructs an empty dataset container.
   */
  public Datasets() {
    this.myDatasetListener = new PrivateDatasetListener<T>(this);
  }

  /**
   * Constructs a {@link Datasets} from the specified array of datasets.
   */
  public Datasets(Dataset<T>[] datasets) {
    ArgChecker.isNotNull(datasets, "datasets");
    this.myDatasetListener = new PrivateDatasetListener<T>(this);
    // FIXME - setIdentifier is probably a mistake that shouldn't exist, if the IDs need to be added
      // they should be done upstream before "undefined" shows up as ID (right now, a real id of "undefined"
      // might be a problem.
    int count = 0; // Hack for now until setIdentifier question resolved
    for (Dataset<T> dataset : datasets) {
      if(null == dataset.getIdentifier() || dataset.getIdentifier().equals("undefined")) {
        dataset.setIdentifier(Integer.toString(count));
      }
      add(dataset);
      count++;
    }
  }

  /**
   * Adds the specified dataset to the existing datasets in this container.
   */
  @Export
  public void add(Dataset<T> dataset) {
    addPrivate(dataset);
    this.myDatasetListener.onDatasetAdded(dataset);
  }

  /**
   * Adds the specified dataset to the existing datasets in this container
   * without firing events.
   */
  public void addPrivate(Dataset<T> dataset) {
    ArgChecker.isNotNull(dataset, "dataset");
    datasets.add(dataset);
    ((AbstractDataset)dataset).setDatasets(this);
    if (dataset instanceof MutableDataset) {
      ((MutableDataset) dataset).addListener(this.myDatasetListener);
    }
    updateAggregateInfo(dataset);
  }

  /**
   * Registers listeners who wish to be notified of changes to this container as
   * well as its elements.
   */
  public void addListener(DatasetListener<T> listener) {
    ArgChecker.isNotNull(listener, "listener");
    this.listeners.add(listener);
  }

  /**
   * Suspends all firing up dataset events until corresponding endMutation() as
   * well as allows more efficient processing of multiple updates.
   */
  @Export
  public void beginMutation() {
    mutating++;
  }

  /**
   * Disables mutation mode and fires all pending change events.
   */
  @Export
  public void endMutation() {
    mutating--;
    if (mutating == 0) {
      firePendingEvents();
    }
  }


  private void firePendingEvents() {
    for (Command c : pending) {
      c.execute();
    }
  }

  /**
   * Returns the 0-based index of the specified dataset, or -1 if it is not in
   * this container.
   */
  public int indexOf(Dataset<T> dataset) {
    ArgChecker.isNotNull(dataset, "dataset");
    String datasetId=dataset.getIdentifier();
    for (int i = 0; i < datasets.size(); i++) {
      if (datasets.get(i) == dataset) {
        return i;
      } else if((!(null == datasetId)) && (!("" == datasetId)) && (datasets.get(i).getIdentifier().equals(datasetId))){
        return i;
      }
    }

    return -1;
  }

  /**
   * Returns the dataset at the specified 0-based index within this container.
   */
  @Export
  public Dataset<T> get(int index) {
    return this.datasets.get(index);
  }

  /**
   * Returns the dataset with the specified identifier.
   */
  @Export
  public Dataset<T> getById(String id) {
    for (Dataset<T> d : datasets) {
      if (id.equals(d.getIdentifier())) {
        return d;
      }
    }
    return null;
  }

  /**
   * Returns an interval that contains the minimum and maximum domain value
   * across all {@link Dataset} elements within this container.
   */
  @Export
  public Interval getDomainExtrema() {
    verifyDatasetNotEmpty();
    return new Interval(this.minDomain, this.maxDomain);
  }

  /**
   * Returns the minimum value of {@link Dataset#getMinDomainInterval()} across
   * all datasets in this container.
   */
  public double getMinInterval() {
    verifyDatasetNotEmpty();
    return this.minInterval;
  }

  /**
   * Returns true if this container has 0 elements.
   */
  public boolean isEmpty() {
    return size() == 0;
  }

  /**
   * Iterator over the dataset elements of this container.
   */
  public Iterator<Dataset<T>> iterator() {
    return this.datasets.iterator();
  }

  /**
   * Removes the element at the specified index in this container. Shifts any
   * subsequent elements to the left (subtracts one from their indices). Returns
   * the element that was removed from the container. <p> Be aware that this
   * container de-registers itself as an {@link DatasetListener} to the dataset
   * being removed.  In other words, mutations applied to a dataset that once
   * belonged to this container will no longer signal this container, which in
   * turn, will no longer forward the mutation event to its listeners.
   */
  @Export
  public Dataset<T> remove(int index) {
    Dataset<T> removedDataset = removePrivate(index);
    myDatasetListener.onDatasetRemoved(removedDataset, index);
    return removedDataset;
  }

  /**
   * Remove a dataset without firing listeners.
   */
  public Dataset<T> removePrivate(int index) {
    verifyDatasetNotEmpty();
    Dataset<T> removedDataset = datasets.remove(index);
    recalcAggregateInfo();
    if (removedDataset instanceof MutableDataset) {
      ((MutableDataset) removedDataset).removeListener(myDatasetListener);
    }
    return removedDataset;
  }

  /**
   * Returns the number of datasets in this container.
   */
  @Export
  public int size() {
    return this.datasets.size();
  }

  /**
   * Returns the contained dataset elements as an array of {@link Dataset}.
   */
  public Dataset<T>[] toArray() {
    return (Dataset<T>[]) this.datasets.toArray(new Dataset[0]);
  }

  private void recalcAggregateInfo() {
    minDomain = Double.POSITIVE_INFINITY;
    maxDomain = Double.NEGATIVE_INFINITY;
    minInterval = Double.POSITIVE_INFINITY;

    for (Dataset<T> ds : datasets) {
      updateAggregateInfo(ds);
    }
  }

  private void updateAggregateInfo(Dataset<T> dataset) {
    minDomain = MathUtil.min(minDomain, dataset.getDomainExtrema().getStart());
    maxDomain = MathUtil.max(maxDomain, dataset.getDomainExtrema().getEnd());
    minInterval = Math.min(minInterval, dataset.getMinDomainInterval());
  }

  private void verifyDatasetNotEmpty() {
    if (this.datasets.isEmpty()) {
      throw new IllegalStateException(
          "method call not valid for empty container");
    }
  }

  public void fireChanged(Dataset dataset, Interval region) {
     myDatasetListener.onDatasetChanged(dataset, region.getStart(), region.getEnd());
  }

  private abstract class ListenerCommand implements Command {

        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }

            if ( ((ListenerCommand)obj).getName() == null || !((ListenerCommand)obj).getName().equals(getName())) {
                return false;
            }
            return true;
        }

        protected abstract String getName();

        @Override
        public int hashCode() {
            int hash = 7;
            hash = 59 * hash + (this.getName() != null ? this.getName().hashCode() : 0);
            return hash;
        }

  }

  private static final String ON_DATASET_ADDED = "onDatasetAdded";
  private static final String ON_DATASET_CHANGED = "onDatasetChanged";
  private static final String ON_DATASET_REMOVED = "onDatasetRemoved";

  private final class PrivateDatasetListener<S extends Tuple2D>
      implements DatasetListener<S> {

    private Datasets<S> datasets;

    public PrivateDatasetListener(Datasets<S> datasets) {
      this.datasets = datasets;
    }

    public void onDatasetAdded(final Dataset<S> dataset) {
      // forward event to external listeners
      for (final DatasetListener<S> l : this.datasets.listeners) {
        Command c = new ListenerCommand() {

          protected String getName() {
             return ON_DATASET_ADDED;
          }

          @Override
          public void execute() {
            l.onDatasetAdded(dataset);
          }


        };
        if (mutating == 0) {
          c.execute();
        } else {
            replaceCommand(pending,c);
        }
      }
    }

   private void replaceCommand(List<Command> pending, Command cmd ){
        if (pending.contains(cmd)){
           pending.remove(cmd);
       }
       pending.add(cmd);
   }

    public void onDatasetChanged(final Dataset<S> dataset,
        final double domainStart, final double domainEnd) {
      // update aggregate stats as changes to this dataset may have
      // affected them.
      this.datasets.updateAggregateInfo(dataset);

      // forward event to external listeners
      for (final DatasetListener<S> l : this.datasets.listeners) {
        Command c = new ListenerCommand() {

          protected String getName(){
             return ON_DATASET_CHANGED;
          }

          @Override
          public void execute() {
            l.onDatasetChanged(dataset, domainStart, domainEnd);
          }
        };
        if (mutating == 0) {
          c.execute();
        } else {
            replaceCommand(pending,c);
        }
      }
    }

    public void onDatasetRemoved(final Dataset<S> dataset,
        final int datasetIndex) {
      // forward event to external listeners
      for (final DatasetListener<S> l : this.datasets.listeners) {
        Command c = new ListenerCommand() {

          protected String getName(){
             return ON_DATASET_REMOVED;
          }
         
          @Override
          public void execute() {
            l.onDatasetRemoved(dataset, datasetIndex);
          }
        };
        if (mutating == 0) {
          c.execute();
        } else {
           replaceCommand(pending,c);
        }
      }
    }

    @Override
    public void clear() {
      datasets = null;
    }
  }

  public String toJson() {
    String ret = "";
    for (int i = 0; i < datasets.size(); i++) {
      if (i>0) ret += ",";
      ret += datasets.get(i).toJson();
    }
    return "[" + ret + "]";
  }
 
  public String toString() {
    String ret = "";
    for (int i = 0; i < datasets.size(); i++) {
      if (i>0) ret += ",";
      ret += datasets.get(i).toString();
    }
    return ret;
  }
 
  private boolean cleared = false;
  public void clear() {
    if (cleared) {
      return;
    }
    cleared = true;
    if (datasets != null) {
      for (Dataset<?> d: datasets) {
        d.clear();
      }
      datasets.clear();
      datasets = null;
    }
    if (listeners != null) {
      for (DatasetListener<?> d: listeners) {
        d.clear();
      }
      listeners.clear();
      listeners = null;
    }
    myDatasetListener = null;
    // TODO: MCM check if it is necessary clear a Command
    if (pending != null) {
      pending.clear();
      pending = null;
    }
  }
}
TOP

Related Classes of org.timepedia.chronoscope.client.Datasets

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.