gt: 		if (cachedDistinctValues == null) {
context: /**
   * Copyright (C) 2001-2017 by RapidMiner and the contributors
   * 
   * Complete list of developers available at our web site:
   * 
   * http://rapidminer.com
   * 
   * This program is free software: you can redistribute it and/or modify it under the terms of the
   * GNU Affero General Public License as published by the Free Software Foundation, either version 3
   * of the License, or (at your option) any later version.
   * 
   * This program 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
   * Affero General Public License for more details.
   * 
   * You should have received a copy of the GNU Affero General Public License along with this program.
   * If not, see http://www.gnu.org/licenses/.
  */
  package com.rapidminer.gui.new_plotter.data;
  
  import java.util.Collections;
  import java.util.HashSet;
  import java.util.LinkedList;
  import java.util.List;
  import java.util.Set;
  import java.util.logging.Level;
  
  import com.rapidminer.datatable.DataTable;
  import com.rapidminer.datatable.DataTableRow;
  import com.rapidminer.gui.new_plotter.ConfigurationChangeResponse;
  import com.rapidminer.gui.new_plotter.PlotConfigurationError;
  import com.rapidminer.gui.new_plotter.PlotConfigurationQuickFix;
  import com.rapidminer.gui.new_plotter.configuration.DataTableColumn;
  import com.rapidminer.gui.new_plotter.configuration.DataTableColumn.ValueType;
  import com.rapidminer.gui.new_plotter.configuration.DefaultDimensionConfig;
  import com.rapidminer.gui.new_plotter.configuration.DimensionConfig;
  import com.rapidminer.gui.new_plotter.configuration.DimensionConfig.PlotDimension;
  import com.rapidminer.gui.new_plotter.configuration.PlotConfiguration;
  import com.rapidminer.gui.new_plotter.listener.events.DimensionConfigChangeEvent;
  import com.rapidminer.gui.new_plotter.listener.events.DimensionConfigChangeEvent.DimensionConfigChangeType;
  import com.rapidminer.gui.new_plotter.utility.CategoricalColorProvider;
  import com.rapidminer.gui.new_plotter.utility.CategoricalSizeProvider;
  import com.rapidminer.gui.new_plotter.utility.ColorProvider;
  import com.rapidminer.gui.new_plotter.utility.ContinuousColorProvider;
  import com.rapidminer.gui.new_plotter.utility.ContinuousSizeProvider;
  import com.rapidminer.gui.new_plotter.utility.NumericalValueRange;
  import com.rapidminer.gui.new_plotter.utility.ShapeProvider;
  import com.rapidminer.gui.new_plotter.utility.SizeProvider;
  import com.rapidminer.gui.new_plotter.utility.ValueRange;
  import com.rapidminer.tools.I18N;
  import com.rapidminer.tools.LogService;
  
  
  /**
   * @author Marius Helf, Nils Woehler
   * 
   */
  public class DimensionConfigData {
  
  	private PlotInstance plotInstance;
  
  	private SizeProvider sizeProvider = null;
  	private ColorProvider colorProvider = null;
  	private ShapeProvider shapeProvider = null;
  
  	private transient double cachedMinValue = Double.NaN;
  	private transient double cachedMaxValue = Double.NaN;
  	private transient double cachedMinGroupValue = Double.NaN;
  	private transient double cachedMaxGroupValue = Double.NaN;
  	private transient List<ValueRange> cachedValueGroups = null;
  	private transient List<Double> cachedValues = null;
  	private transient List<Double> cachedDistinctValues = null;
  	private DataTableColumnIndex columnIdx;
  	private DimensionConfigChangeEvent lastProcessedEvent = null;
  	private int dimensionConfigId;
  
  	public DimensionConfigData(PlotInstance plotInstance, DefaultDimensionConfig dimensionConfig) {
  		this.dimensionConfigId = dimensionConfig.getId();
  		this.plotInstance = plotInstance;
  		DataTable dataTable = plotInstance.getPlotData().getSortedDataTableWithoutImplicitUpdate();
  		this.columnIdx = new DataTableColumnIndex(getDimensionConfig().getDataTableColumn(), dataTable);
  	}
  
  	/**
  	 * The returned values are sorted and filtered according to the selected sort order and value
  	 * range.
  	 */
  	public List<Double> getValues() {
  		if (cachedValues == null) {
  			updateValueCache();
  		}
  		return cachedValues;
  	}
  
  	protected void updateShapeProvider() {
  		if (getDimensionConfig().isNominal()) {
  			List<Double> categoryList = new LinkedList<Double>();
  			if (getDimensionConfig().isGrouping()) {
  				for (ValueRange valueGroup : getGroupingModel()) {
  					categoryList.add(valueGroup.getValue());
  				}
  			} else {
  				categoryList = getDistinctValues();
  			}
  			setShapeProvider(new ShapeProvider(categoryList));
  		} else {
  			setShapeProvider(null);
  		}
  	}
  
  	private void setShapeProvider(ShapeProvider shapeProvider) {
  		if (this.shapeProvider != shapeProvider) {
  			this.shapeProvider = shapeProvider;
  		}
  	}
  
  	/**
  	 * If isNominal() returns true, returns a string for the given value. If isGrouping() returns
  	 * true, value must be an integer and this function returns the toString() method of the
  	 * valueGroup with value==valueGroup.getValue(). If no grouping is found with
  	 * value==valueGroup.getValue() return <code>null</code>. If isGrouping() returns false, calls
  	 * the mapIndex() function of the underlying DataTable with value.
  	 * 
  	 * If isNominal() returns false, this functions returns null.
  	 */
  	public String getStringForValue(double value) {
  		if (getDimensionConfig().isNominal()) {
  			if (getDimensionConfig().isGrouping()) {
  				List<ValueRange> groupingModel = getGroupingModel();
  				for (ValueRange range : groupingModel) {
  					// if range's value is equal to value or both are NaN (i.e. missing value):
  					if (range.getValue() == value || (Double.isNaN(range.getValue()) && Double.isNaN(value))) {
  						return range.toString();
  					}
  				}
  				return null;
  			} else {
  				DataTable dataTable = plotInstance.getPlotData().getValueMappingDataTable();
  				int columnIdx = DataTableColumn.getColumnIndex(dataTable, getDimensionConfig().getDataTableColumn());
  				String valueString;
  				if (Double.isNaN(value)) {
  					valueString = I18N.getGUILabel("plotter.unknown_value_label");
  				} else {
  					valueString = dataTable.mapIndex(columnIdx, (int) value);
  				}
  				return valueString;
  			}
  		} else {
  			return null;
  		}
  	}
  
  	private void invalidateFormatProviders() {
  		colorProvider = null;
  		shapeProvider = null;
  		sizeProvider = null;
  	}
  
  	private void updateGroupingModel() {
  		DefaultDimensionConfig dimensionConfig = getDimensionConfig();
  		double upperBound = Double.POSITIVE_INFINITY;
  		double lowerBound = Double.NEGATIVE_INFINITY;
  		if (dimensionConfig.isUsingUserDefinedUpperBound()) {
  			upperBound = dimensionConfig.getUserDefinedUpperBound();
  		}
  		if (dimensionConfig.isUsingUserDefinedLowerBound()) {
  			lowerBound = dimensionConfig.getUserDefinedLowerBound();
  		}
  		cachedValueGroups = dimensionConfig.getGrouping().getGroupingModel(plotInstance.getPlotData().getDataTable(),
  				upperBound, lowerBound);
  
  		int maxAllowedValueCount = PlotConfiguration.getMaxAllowedValueCount();
  		if (cachedValueGroups.size() > maxAllowedValueCount) {
  			ConfigurationChangeResponse response = new ConfigurationChangeResponse();
  			response.addError(new PlotConfigurationError("too_many_values_in_plot", dimensionConfig.getDimension().getName()));
  			plotInstance.getMasterOfDesaster().registerConfigurationChangeResponse(response);
  			cachedValueGroups = new LinkedList<ValueRange>();
  			cachedMinGroupValue = 0;
  			cachedMaxGroupValue = 1;
  			return;
  		}
  
  		cachedMinGroupValue = Double.POSITIVE_INFINITY;
  		cachedMaxGroupValue = Double.NEGATIVE_INFINITY;
  
  		for (ValueRange group : cachedValueGroups) {
  			double value = group.getValue();
  			if (value < cachedMinGroupValue) {
  				cachedMinGroupValue = value;
  			}
  			if (value > cachedMaxGroupValue) {
  				cachedMaxGroupValue = value;
  			}
  		}
  	}
  
  	/**
  	 * May not contain null values. If there are no value groups (because this dimension is not
  	 * grouping), this function returns null.
  	 * 
  	 * Classes implementing this method are strongly advised to cache to list of value ranges, since
  	 * this method might be called quite often, and the calculation of the grouping might be quite
  	 * expensive.
  	 */
  	public List<ValueRange> getGroupingModel() {
  		if (getDimensionConfig().isGrouping()) {
  			if (cachedValueGroups == null) {
  				updateGroupingModel();
  			}
  			return cachedValueGroups;
  		} else {
  			return null;
  		}
  	}
  
  	public ColorProvider getColorProvider() {
  		if (colorProvider == null) {
  			updateColorProvider();
  		}
  		return colorProvider;
  	}
  
  	public SizeProvider getSizeProvider() {
  		if (sizeProvider == null) {
  			updateSizeProvider();
  		}
  		return sizeProvider;
  	}
  
  	private void setColorProvider(ColorProvider colorProvider) {
  		this.colorProvider = colorProvider;
  	}
  
  	/**
  	 * @return a sorted list of all distinct values in this domain.
  	 */
  	public List<Double> getDistinctValues() {
  