/**
* 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.viewer.metadata.model;
import com.rapidminer.example.ExampleSet;
import com.rapidminer.gui.viewer.metadata.AttributeStatisticsPanel;
import com.rapidminer.gui.viewer.metadata.MetaDataStatisticsViewer;
import com.rapidminer.gui.viewer.metadata.event.MetaDataStatisticsEvent;
import com.rapidminer.gui.viewer.metadata.event.MetaDataStatisticsEvent.EventType;
import com.rapidminer.gui.viewer.metadata.event.MetaDataStatisticsEventListener;
import com.rapidminer.tools.Ontology;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.SwingWorker;
import javax.swing.event.EventListenerList;
/**
* Data model and controller for the {@link MetaDataStatisticsViewer}. The statistics for the
* {@link ExampleSet} are created in a {@link SwingWorker}, so model creation does not block the
* GUI.
* <p>
* Note that the {@link ExampleSet} which needs to be pased to the constructor is only stored via
* {@link WeakReference}, so no example set memory leak can happen here.
*
* @author Marco Boeck
*
*/
public class MetaDataStatisticsModel {
/**
* Enum indicating the supported types of sorting.
*
*/
public static enum SortingType {
NAME, TYPE, MISSING;
}
/**
* Enum indicating if sorting should be done ascending or descending.
*
*/
public static enum SortingDirection {
UNDEFINED, ASCENDING, DESCENDING;
}
/** the size of each page: {@value} */
public static final int PAGE_SIZE = 1000;
/** event listener for this model */
private final EventListenerList eventListener;
/** {@link WeakReference} on the {@link ExampleSet} */
private final WeakReference<ExampleSet> weakExampleSet;
/**
* this map contains every {@link AbstractAttributeStatisticsModel} and a boolean indicating if
* the model is visible or not (filters)
*/
private final Map<AbstractAttributeStatisticsModel, Boolean> mapOfStatModelVisibility;
/**
* stores the number of stat models which are visible. Used for performance reasons (100.000+
* atributes iteration is slow)
*/
private int visibleCount;
/**
* the ordered list containing all {@link AbstractAttributeStatisticsModel}s in the correct
* order
*/
private List<AbstractAttributeStatisticsModel> orderedModelList;
/** holds the sorting settings for all possible sorting types */
private final Map<SortingType, SortingDirection> mapOfSortingSettings;
/** the attribute name filter {@link String} */
private String filterNameString;
/** if true, shows special attributes */
private boolean showSpecialAttributes;
/** if true, shows regular attributes */
private boolean showRegularAttributes;
/** if true, attributes with missing values will be shown */
private boolean showOnlyMissingsAttributes;
/** if this is <code>true</code>, filtering and sorting is allowed; otherwise it is not */
private volatile boolean allowSortingAndFiltering;
/** the index of the current page of {@link AttributeStatisticsPanel}s */
private int currentPageIndex;
/** indicates for all {@link Ontology#VALUE_TYPE}s if they are visible */
private final Map<Integer, Boolean> mapOfValueTypeVisibility;
/**
* Creates a new {@link MetaDataStatisticsModel} instance.
*
* @param exampleSet
* the {@link ExampleSet} for which the meta data statistics should be created. No
* reference to it is stored to prevent memory leaks.
*/
public MetaDataStatisticsModel(final ExampleSet exampleSet) {
this.weakExampleSet = new WeakReference<>(exampleSet);
this.eventListener = new EventListenerList();
visibleCount = -1;
currentPageIndex = 0;
mapOfStatModelVisibility = new HashMap<>();
mapOfSortingSettings = new HashMap<>();
mapOfValueTypeVisibility = new HashMap<>();
for (String valueTypeString : Ontology.VALUE_TYPE_NAMES) {
int valueType = Ontology.ATTRIBUTE_VALUE_TYPE.mapName(valueTypeString);
mapOfValueTypeVisibility.put(valueType, Boolean.TRUE);
}
allowSortingAndFiltering = false;
orderedModelList = new ArrayList<>(); // has to be ArrayList because we access the index
// from 1-n on filtering
showSpecialAttributes = true;
showRegularAttributes = true;
showOnlyMissingsAttributes = false;
filterNameString = "";
}
/**
* Returns the ordered {@link List} of {@link AbstractAttributeStatisticsModel}s.
*
* @return
*/
public List<AbstractAttributeStatisticsModel> getOrderedAttributeStatisticsModels() {
return orderedModelList;
}
/**
* Returns the total number of {@link AbstractAttributeStatisticsModel}s in this model.
*
* @return
*/
public int getTotalSize() {
if (!mapOfStatModelVisibility.keySet().isEmpty()) {
return mapOfStatModelVisibility.keySet().size();
}
return 0;
}
/**
* Returns the number of {@link AbstractAttributeStatisticsModel}s in this model which are
* currently visible.
*
* @return
*/
public int getVisibleSize() {
if (visibleCount == -1) {
return getTotalSize();
}
return visibleCount;
}
/**
* Sets the number of visible attributes.
*
* @param visibleCount
*/
public void setVisibleCount(final int visibleCount) {
this.visibleCount = visibleCount;
}
/**
* Returns the {@link SortingDirection} for the given {@link SortingType}.
*
* @param type
* @return
*/
public SortingDirection getSortingDirection(final SortingType type) {
return mapOfSortingSettings.get(type);
}
/**
* Set the {@link SortingDirection} for the specified {@link SortingType}.
*
* @param type
* @param direction
*/
public void setSortingDirection(final SortingType type, final SortingDirection direction) {
mapOfSortingSettings.put(type, direction);
}
/**
* Returns if the given {@link AbstractAttributeStatisticsModel} is visible. Returns
* <code>null</code> if the given stat model is unknown to the model.
*
* @param model
* @return
*/
public Boolean isAttributeStatisticsModelsVisible(final AbstractAttributeStatisticsModel model) {
return mapOfStatModelVisibility.get(model);
}
/**
* Sets if the given {@link AbstractAttributeStatisticsModel} is visible.
*
* @param model
* @return the original visibility value
*/
public Boolean setAttributeStatisticsModelVisible(final AbstractAttributeStatisticsModel model, final boolean visible) {
return mapOfStatModelVisibility.put(model, visible);
}
/**
* The {@link String} to filter attribute names against.
*
* @return
*/
public String getFilterNameString() {
return filterNameString;
}
/**
* Set the attribute name filter.
*
* @param filterNameString
*/
public void setFilterNameString(final String filterNameString) {
this.filterNameString = filterNameString;
}
/**
* If <code>true</code>, show only attributes with missing values.
*
* @return
*/
public boolean isShowOnlyMissingsAttributes() {
return showOnlyMissingsAttributes;
}
/**
* Sets whether only attributes with missing values should be shown.
*
* @param showOnlyMissingsAttributes
*/
public void setShowOnlyMissingsAttributes(final boolean showOnlyMissingsAttributes) {
this.showOnlyMissingsAttributes = showOnlyMissingsAttributes;
}
/**
* If <code>true</code>, show special attributes.
*
* @return
*/
public boolean isShowSpecialAttributes() {
return showSpecialAttributes;
}
/**
* Sets whether special attributes should be shown.
*
* @param showSpecialAttributes
*/
public void setShowSpecialAttributes(final boolean showSpecialAttributes) {
this.showSpecialAttributes = showSpecialAttributes;
}
/**
* If <code>true</code>, show regular attributes.
*
* @return
*/
public boolean isShowRegularAttributes() {
return showRegularAttributes;
}
/**
* Sets whether regular attributes should be shown.
*
* @param showRegularAttributes
*/
public void setShowRegularAttributes(final boolean showRegularAttributes) {
this.showRegularAttributes = showRegularAttributes;
}
/**
* Tries to return the {@link ExampleSet} for this model. However this is stored via a
* {@link WeakReference}, so it might be gone when this method is called.
*
* @return
*/
public ExampleSet getExampleSetOrNull() {
return weakExampleSet.get();
}
/**
* Sets the visibility of {@link Ontology#VALUE_TYPE}s.
*
* @param valueType
* @param visible
* @return the previous value
*/
public Boolean setAttributeTypeVisibility(final int valueType, final boolean visible) {
return mapOfValueTypeVisibility.put(valueType, visible);
}
/**
* Returns the visibility for the given value type.
*
* @param valueType
* @return
*/
public Boolean getAttributeTypeVisibility(final int valueType) {
return mapOfValueTypeVisibility.get(valueType);
}
/**
* Adds a {@link MetaDataStatisticsEventListener} which will be informed of all changes to this
* model.
*
* @param listener
*/
public void registerEventListener(final MetaDataStatisticsEventListener listener) {
eventListener.add(MetaDataStatisticsEventListener.class, listener);
}
/**
* Removes the {@link MetaDataStatisticsEventListener} from this model.
*
* @param listener
*/
public void removeEventListener(final MetaDataStatisticsEventListener listener) {
eventListener.remove(MetaDataStatisticsEventListener.class, listener);
}
/**
* Returns the number of pages which are currently needed to display all visible stat models.
*
* @return
*/
public int getNumberOfPages() {
// double cast is needed so 1001/1000 returns 2 pages
int pages = (int) Math.ceil((double) getVisibleSize() / MetaDataStatisticsModel.PAGE_SIZE);
// no pages = 1 page
pages = pages == 0 ? 1 : pages;
return pages;
}
/**
* Sets the current page index. Note that index stats at 0.
*
* @param index
*/
public void setCurrentPageIndex(final int index) {
currentPageIndex = index;
}
/**
* Returns the currently active page index. Note that index starts at 0.
*
* @return
*/
public int getCurrentPageIndex() {
return currentPageIndex;
}
/**
* Returns <code>true</code> if the user applied a filter; <code>false</code> otherwise.
*
* @return
*/
public boolean isFiltering() {
return getVisibleSize() < getTotalSize();
}
/**
* Sets sorting and filtering to allowed.
*/
public void setAllowSortingAndFiltering() {
allowSortingAndFiltering = true;
}
/**
* Returns <code>true</code> if filtering and sorting is allowed; <code>false</code> otherwise.
* Both are only allowed after the data has been fully displayed and calculated.
*
* @return the allowSortingAndFiltering
*/
public boolean isSortingAndFilteringAllowed() {
return allowSortingAndFiltering;
}
/**
* Sets the list of ordered {@link AbstractAttributeStatisticsModel}s.
*
* @param orderedList
*/
public void setOrderedModelList(final List<AbstractAttributeStatisticsModel> orderedList) {
this.orderedModelList = new ArrayList<>(orderedList);
}
/**
* Fire when the initialization is complete.
*/
public void fireInitDoneEvent() {
fireEvent(EventType.INIT_DONE);
}
/**
* Fire when pagination has changed.
*/
public void firePaginationChangedEvent() {
fireEvent(EventType.PAGINATION_CHANGED);
}
/**
* Fire when the filters have changed.
*/
public void fireFilterChangedEvent() {
fireEvent(EventType.FILTER_CHANGED);
}
/**
* Fire when the order of attributes has changed.
*/
public void fireOrderChangedEvent() {
fireEvent(EventType.ORDER_CHANGED);
}
/**
* Fires the given {@link EventType}.
*
* @param type
*/
private void fireEvent(final EventType type) {
Object[] listeners = eventListener.getListenerList();
// Process the listeners last to first
for (int i = listeners.length - 2; i >= 0; i -= 2) {
if (listeners[i] == MetaDataStatisticsEventListener.class) {
MetaDataStatisticsEvent e = new MetaDataStatisticsEvent(type);
((MetaDataStatisticsEventListener) listeners[i + 1]).modelChanged(e);
}
}
}
}