/* Copyright 2012 Google, Inc.
*
* 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 org.arbeitspferde.groningen.display;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.inject.Inject;
import org.arbeitspferde.groningen.HistoryDatastore;
import org.arbeitspferde.groningen.HistoryDatastore.HistoryDatastoreException;
import org.arbeitspferde.groningen.Pipeline;
import org.arbeitspferde.groningen.PipelineHistoryState;
import org.arbeitspferde.groningen.PipelineId;
import org.arbeitspferde.groningen.PipelineManager;
import org.arbeitspferde.groningen.common.EvaluatedSubject;
import org.arbeitspferde.groningen.config.PipelineScoped;
import org.arbeitspferde.groningen.experimentdb.ExperimentDb;
import org.arbeitspferde.groningen.scorer.HistoricalBestPerformerScorer;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
/**
* Encapsulates the Groningen information to be displayed on the HUD Implements a
* Mediator pattern.
*/
@PipelineScoped
public class DisplayMediator implements Displayable, MonitorGroningen {
private static final Logger log = Logger.getLogger(DisplayMediator.class.getCanonicalName());
private static final Joiner commaJoiner = Joiner.on(",");
/** Time keeping */
private final DateFormat df = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss z");
private final ExperimentDb experimentDb;
private final PipelineId pipelineId;
private final HistoryDatastore historyDatastore;
private final PipelineManager pipelineManager;
/** List of objects to be monitored */
@VisibleForTesting final List<DisplayableObject> monitoredObjects =
Collections.synchronizedList(new ArrayList<DisplayableObject>());
/** Provides back links from monitored objects to their {@link DisplayableObject}
* wrapper so we can remove an object */
private final Hashtable<Object, DisplayableObject> objectToDisplayable =
new Hashtable<>();
/** The separator for printing the monitored objects */
String separator = "";
/** The maximum number of individuals we care about in each list */
@VisibleForTesting int maxIndv = 3;
private long cummulativeExperimentIdSum = 0;
/*
* TODO(team): Look closely at the use of the List<EvaluatedSubject> below to ensure that they
* do not populate ad infinitum.
*/
/** stores the evaluated subjects while still being added */
@VisibleForTesting final List<EvaluatedSubject> tempEvaluatedSubjects =
Collections.synchronizedList(new ArrayList<EvaluatedSubject>());
/**
* stores the current unique and merged evaluated subjects.
*
* we synchronize access to the list (and combine clearing and re-adding elements in
* steps that should be atomic) so the list itself does not need internal locking.
*/
@VisibleForTesting final List<EvaluatedSubject> currentEvaluatedSubjects =
new ArrayList<>();
/** stores a list of warnings for display to the user */
@VisibleForTesting final List<String> warnings =
Collections.synchronizedList(new ArrayList<String>());
/** best performer scorer and store */
final HistoricalBestPerformerScorer bestPerformerScorer;
private DisplayClusters displayableClusters;
// TODO(team): remove reference to clock
@Inject
public DisplayMediator(final ExperimentDb experimentDb,
final HistoryDatastore historyDatastore, final PipelineManager pipelineManager,
final PipelineId pipelineId, final HistoricalBestPerformerScorer bestPerformerScorer) {
this.experimentDb = experimentDb;
this.pipelineId = pipelineId;
this.historyDatastore = historyDatastore;
this.pipelineManager = pipelineManager;
this.bestPerformerScorer = bestPerformerScorer;
}
/**
* Closes out accounting on a generation and starts accounting on a new
* generation.
*/
@Override
public void processGeneration() {
cummulativeExperimentIdSum += getExperimentId();
this.stopMonitoringObject(displayableClusters);
displayableClusters = new DisplayClusters(tempEvaluatedSubjects);
this.monitorObject(displayableClusters, "Clusters used in the last experiment");
Pipeline pipeline = pipelineManager.findPipelineById(pipelineId);
// TODO(team): propagate error up more gracefully than via an assert.
assert(pipeline != null);
List<EvaluatedSubject> cleanedCurGeneration =
bestPerformerScorer.addGeneration(tempEvaluatedSubjects);
synchronized (currentEvaluatedSubjects) {
currentEvaluatedSubjects.clear();
if (cleanedCurGeneration != null) {
currentEvaluatedSubjects.addAll(cleanedCurGeneration);
}
}
/* Reset temporary list */
tempEvaluatedSubjects.clear();
}
/**
* Adds an {@link EvaluatedSubject}.
*
* @param evaluatedSubject the {@link EvaluatedSubject} to add
*/
@Override
public void addIndividual(final EvaluatedSubject evaluatedSubject) {
Preconditions.checkNotNull(evaluatedSubject, "evaluatedSubject may not be null.");
tempEvaluatedSubjects.add(evaluatedSubject);
}
/** Adds a warning to the warning list */
@Override
public void addWarning(String warning) {
Preconditions.checkNotNull(warning);
synchronized (warnings) {
if (!warnings.contains(warning)) {
warnings.add(warning);
}
}
}
/**
* Determines the maximum number of distinct individuals we care about
*
* @param max an integer
*/
@Override
public void maxIndividuals(int max) {
this.maxIndv = max;
}
/**
* Wraps the object to be monitored in a {@link DisplayableObject} and add to list
* of monitored objects. The objects should be thread safe.
*
* @param obj
* @param infoString
*/
@Override
public void monitorObject(Object obj, String infoString) {
DisplayableObject newAddition = new DisplayableObject(obj, infoString);
objectToDisplayable.put(obj, newAddition);
monitoredObjects.add(newAddition);
}
/**
* Given a monitored {@link Object}, the class stops monitoring it. It returns
* {@code true} if the object was being monitored. It returns false otherwise.
*
* @param obj
*/
@Override
public boolean stopMonitoringObject(Object obj) {
synchronized (monitoredObjects) {
// return false if object not initialized
if (obj == null) {
return false;
}
return monitoredObjects.remove(objectToDisplayable.remove(obj));
}
}
/**
* Calls the .toHtml() method of each registered {@link Displayable} object
*
* @return a concatenated html string
*/
@Deprecated
@Override
public String toHtml() {
// TODO(sanragsood): Remove this method entirely once the cleanup is complete.
return "Deprecated";
}
private class DisplayClusters {
private final String toDisplay;
private DisplayClusters(List<EvaluatedSubject> evalSubjects) {
Set<String> clusters = Collections.synchronizedSet(new HashSet<String>());
synchronized (evalSubjects) {
for (EvaluatedSubject individual : evalSubjects) {
// skip if no subject, which is the case in unitTesting
if (individual.getBridge().getAssociatedSubject() == null) {
continue;
}
clusters.add(
individual.getBridge().getAssociatedSubject().getGroup().getClusterName());
}
}
toDisplay = commaJoiner.join(clusters);
}
@Override
public String toString() {
return toDisplay;
}
}
/**
* A provider of the current {@link ExperimentDb#getExperimentId()} to lessen the Law of Demeter
* smells.
*
* @return The current experiment ID.
*/
private long getExperimentId() {
return experimentDb.getExperimentId();
}
/**
* Returns warnings generated for the pipeline.
*/
public String[] getWarnings() {
return warnings.toArray(new String[0]);
}
public long getCumulativeExperimentIdSum() {
return cummulativeExperimentIdSum;
}
public EvaluatedSubject[] getCurrentExperimentSubjects() {
synchronized (currentEvaluatedSubjects) {
return currentEvaluatedSubjects.toArray(new EvaluatedSubject[0]);
}
}
public EvaluatedSubject[] getAllExperimentSubjects() {
List<EvaluatedSubject> allSubjects = Lists.newArrayList();
List<PipelineHistoryState> states = Lists.newArrayList();
try {
states = historyDatastore.getStatesForPipelineId(pipelineId);
} catch (HistoryDatastoreException e) {
log.severe(e.getMessage());
}
for (PipelineHistoryState state : states) {
allSubjects.addAll(Lists.newArrayList(state.evaluatedSubjects()));
}
return allSubjects.toArray(new EvaluatedSubject[] {});
}
public EvaluatedSubject[] getAlltimeExperimentSubjects() {
return bestPerformerScorer.getBestPerformers().toArray(new EvaluatedSubject[0]);
}
public DisplayableObject[] getMonitoredObjects() {
return monitoredObjects.toArray(new DisplayableObject[0]);
}
}