/**
* DataCleaner (community edition)
* Copyright (C) 2014 Neopost - Customer Information Management
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.datacleaner.widgets.visualization;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.util.Set;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import org.apache.commons.collections15.Predicate;
import org.apache.commons.collections15.Transformer;
import org.apache.commons.collections15.functors.TruePredicate;
import org.apache.metamodel.schema.Table;
import org.datacleaner.api.AnalyzerResult;
import org.datacleaner.api.InputColumn;
import org.datacleaner.descriptors.ComponentDescriptor;
import org.datacleaner.job.ComponentRequirement;
import org.datacleaner.job.CompoundComponentRequirement;
import org.datacleaner.job.FilterOutcome;
import org.datacleaner.job.HasFilterOutcomes;
import org.datacleaner.job.builder.ComponentBuilder;
import org.datacleaner.user.UserPreferences;
import org.datacleaner.util.GraphUtils;
import org.datacleaner.util.IconUtils;
import org.datacleaner.util.ImageManager;
import org.datacleaner.util.LabelUtils;
import org.datacleaner.util.ReflectionUtils;
import org.datacleaner.util.WidgetUtils;
import com.google.common.base.Strings;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.util.Context;
import edu.uci.ics.jung.visualization.decorators.EdgeShape;
import edu.uci.ics.jung.visualization.renderers.EdgeLabelRenderer;
/**
* Collection of {@link Transformer} (and {@link Predicate} and so on) instances
* to use in the {@link JobGraph}.
*/
public class JobGraphTransformers {
public static final String USER_PREFERENCES_PROPERTY_SHOW_CANVAS_HINTS = "JobGraph.ShowCanvasHints";
public static final String USER_PREFERENCES_PROPERTY_FONT_SIZE_FACTOR = "JobGraph.FontSizeFactor";
public static final String USER_PREFERENCES_PROPERTY_EDGE_STYLE = "JobGraph.LineStyle";
public static final String EDGE_STYLE_NAME_STRAIGHT = "straight";
public static final String EDGE_STYLE_NAME_CURVED = "curved";
public static final String EDGE_STYLE_NAME_ORTOGHONAL = "orthogonal";
public static final Predicate<Context<Graph<Object, JobGraphLink>, JobGraphLink>> EDGE_ARROW_PREDICATE =
TruePredicate.getInstance();
public static final Transformer<JobGraphLink, String> EDGE_LABEL_TRANSFORMER = JobGraphLink::getLinkLabel;
public static final Transformer<Context<Graph<Object, JobGraphLink>, JobGraphLink>, Shape> EDGE_ARROW_TRANSFORMER =
input -> GraphUtils.ARROW_SHAPE;
public static final Transformer<Context<Graph<Object, JobGraphLink>, JobGraphLink>, Number>
EDGE_LABEL_CLOSENESS_TRANSFORMER = input -> 0.4d;
public static final Transformer<Object, String> VERTEX_LABEL_TRANSFORMER = obj -> {
if (obj instanceof InputColumn) {
return ((InputColumn<?>) obj).getName();
}
if (obj instanceof ComponentBuilder) {
return LabelUtils.getLabel((ComponentBuilder) obj);
}
if (obj instanceof FilterOutcome) {
return ((FilterOutcome) obj).getCategory().name();
}
if (obj instanceof Table) {
return ((Table) obj).getName();
}
if (obj instanceof Class) {
final Class<?> cls = (Class<?>) obj;
if (ReflectionUtils.is(cls, AnalyzerResult.class)) {
return "Analyzer result";
}
return cls.getSimpleName();
}
return obj.toString();
};
public static final Transformer<Object, Shape> VERTEX_SHAPE_TRANSFORMER = input -> {
final int size = IconUtils.ICON_SIZE_LARGE;
final int offset = -size / 2;
return new Rectangle(new Point(offset, offset), new Dimension(size, size));
};
private static final ImageManager imageManager = ImageManager.get();
public static final Transformer<Object, Icon> VERTEX_ICON_TRANSFORMER = obj -> {
if (obj == JobGraph.MORE_COLUMNS_VERTEX || obj instanceof InputColumn) {
return imageManager.getImageIcon(IconUtils.MODEL_COLUMN, IconUtils.ICON_SIZE_MEDIUM);
}
if (obj instanceof ComponentBuilder) {
final ComponentBuilder componentBuilder = (ComponentBuilder) obj;
final ComponentDescriptor<?> descriptor = componentBuilder.getDescriptor();
final boolean configured;
if (componentBuilder.getInput().length == 0) {
configured = true;
} else {
configured = componentBuilder.isConfigured(false);
}
return IconUtils.getDescriptorIcon(descriptor, configured, IconUtils.ICON_SIZE_LARGE);
}
if (obj instanceof FilterOutcome) {
return imageManager.getImageIcon(IconUtils.FILTER_OUTCOME_PATH, IconUtils.ICON_SIZE_MEDIUM);
}
if (obj instanceof Table) {
return imageManager.getImageIcon(IconUtils.MODEL_TABLE, IconUtils.ICON_SIZE_LARGE);
}
if (obj instanceof Class) {
final Class<?> cls = (Class<?>) obj;
if (ReflectionUtils.is(cls, AnalyzerResult.class)) {
return imageManager.getImageIcon(IconUtils.MODEL_RESULT, IconUtils.ICON_SIZE_LARGE);
}
}
return imageManager.getImageIcon(IconUtils.STATUS_ERROR);
};
private final UserPreferences _userPreferences;
private final Set<Object> _highlighedVertexes;
private final Font _normalFont;
private final Font _boldFont;
public JobGraphTransformers(final UserPreferences userPreferences, final Set<Object> highlighedVertexes) {
_userPreferences = userPreferences;
_highlighedVertexes = highlighedVertexes;
final float fontFactor;
final String fontFactorString =
userPreferences.getAdditionalProperties().get(USER_PREFERENCES_PROPERTY_FONT_SIZE_FACTOR);
if (Strings.isNullOrEmpty(fontFactorString)) {
fontFactor = 1.0f;
} else {
fontFactor = 1.0f * Float.parseFloat(fontFactorString);
}
_normalFont = font(WidgetUtils.FONT_SMALL, fontFactor);
_boldFont = _normalFont.deriveFont(Font.BOLD);
}
private Font font(final Font font, final float fontFactor) {
if (fontFactor == 1.0) {
return font;
}
return font.deriveFont(font.getSize() * fontFactor);
}
public Transformer<Context<Graph<Object, JobGraphLink>, JobGraphLink>, Shape> getEdgeShapeTransformer() {
final String edgeStyle = _userPreferences.getAdditionalProperties().get(USER_PREFERENCES_PROPERTY_EDGE_STYLE);
final Transformer<Context<Graph<Object, JobGraphLink>, JobGraphLink>, Shape> baseTransformer =
getBaseEdgeShapeTransformer(edgeStyle);
return input -> {
final Shape result = baseTransformer.transform(input);
final JobGraphLink link = input.element;
if (isCompoundRequirementLink(link)) {
// make a double link (actually a wedge, but close
// enough) to show that there are more than one filter
// outcome coming from this source
return new EdgeShape.Wedge<Object, JobGraphLink>(10).transform(input);
}
return result;
};
}
protected boolean isCompoundRequirementLink(final JobGraphLink link) {
final ComponentRequirement req = link.getRequirement();
final Object from = link.getFrom();
if (req instanceof CompoundComponentRequirement && from instanceof HasFilterOutcomes) {
if (((CompoundComponentRequirement) req).hasMultipleRequirementsFrom((HasFilterOutcomes) from)) {
return true;
}
}
return false;
}
private Transformer<Context<Graph<Object, JobGraphLink>, JobGraphLink>, Shape> getBaseEdgeShapeTransformer(
final String edgeStyle) {
if (edgeStyle == null) {
return new EdgeShape.QuadCurve<>();
}
switch (edgeStyle) {
case EDGE_STYLE_NAME_STRAIGHT:
return new EdgeShape.Line<>();
case EDGE_STYLE_NAME_CURVED:
return new EdgeShape.QuadCurve<>();
case EDGE_STYLE_NAME_ORTOGHONAL:
return new EdgeShape.Orthogonal<>();
default:
return new EdgeShape.QuadCurve<>();
}
}
public final EdgeLabelRenderer getEdgeLabelRenderer() {
return new EdgeLabelRenderer() {
@Override
public boolean isRotateEdgeLabels() {
return true;
}
@Override
public void setRotateEdgeLabels(final boolean state) {
}
@Override
public <T> Component getEdgeLabelRendererComponent(final JComponent vv, final Object value, final Font font,
final boolean isSelected, final T edge) {
final String labelText;
final String iconPath;
if (edge instanceof JobGraphLink) {
final JobGraphLink link = (JobGraphLink) edge;
if (link.getOutputDataStream() != null) {
labelText = link.getLinkLabel();
iconPath = IconUtils.OUTPUT_DATA_STREAM_PATH;
} else if (isCompoundRequirementLink(link)) {
final HasFilterOutcomes from = (HasFilterOutcomes) link.getFrom();
final CompoundComponentRequirement req = (CompoundComponentRequirement) link.getRequirement();
final Set<FilterOutcome> outcomesFrom = req.getOutcomesFrom(from);
labelText = new CompoundComponentRequirement(outcomesFrom).getSimpleName();
iconPath = IconUtils.FILTER_OUTCOME_PATH;
} else {
labelText = link.getLinkLabel();
iconPath = IconUtils.FILTER_OUTCOME_PATH;
}
} else {
labelText = value + "";
iconPath = IconUtils.FILTER_OUTCOME_PATH;
}
final Icon icon = imageManager.getImageIcon(iconPath, IconUtils.ICON_SIZE_SMALL);
final JLabel label = new JLabel(labelText, icon, JLabel.LEFT);
label.setFont(_normalFont);
return label;
}
};
}
public Transformer<Object, Font> getVertexFontTransformer() {
return vertex -> {
if (_highlighedVertexes.contains(vertex)) {
return _boldFont;
}
return _normalFont;
};
}
}