/**
* 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.io.process;
import java.awt.Point;
import java.awt.geom.Rectangle2D;
import java.util.HashMap;
import java.util.Map;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import com.rapidminer.operator.ExecutionUnit;
import com.rapidminer.operator.FlagUserData;
import com.rapidminer.operator.Operator;
import com.rapidminer.operator.OperatorChain;
import com.rapidminer.operator.UserData;
import com.rapidminer.operator.ports.Port;
/**
* {@link ProcessXMLFilter} to handle position data for operators and ports.
*
* @author Michael Knopf, Marco Boeck, Nils Woehler, Jan Czogalla
* @since 7.2.0
*/
public class ProcessLayoutXMLFilter implements ProcessXMLFilter {
private static final String KEY_OPERATOR_RECTANGLE = "com.rapidminer.io.process.operator_rectangle";
private static final String KEY_PORT_SPACING = "com.rapidminer.io.process.port_spacing";
public static final String KEY_OPERATOR_CHAIN_POSITION = "com.rapidminer.io.process.operator_chain_position";
public static final String KEY_OPERATOR_CHAIN_ZOOM = "com.rapidminer.io.process.operator_chain_zoom";
private static final String KEY_SCROLL_POSITION = "com.rapidminer.io.process.scroll_position";
private static final String KEY_SCROLL_INDEX = "com.rapidminer.io.process.scroll_index";
private static final String KEY_RESTORE = "com.rapidminer.io.process.restore";
private static final String XML_TAG_PORT_SPACING = "portSpacing";
private static final String XML_ATTRIBUTE_HEIGHT = "height";
private static final String XML_ATTRIBUTE_SINK = "sink_";
private static final String XML_ATTRIBUTE_SOURCE = "source_";
private static final String XML_ATTRIBUTE_SPACING = "spacing";
private static final String XML_ATTRIBUTE_PORT = "port";
private static final String XML_ATTRIBUTE_WIDTH = "width";
private static final String XML_ATTRIBUTE_X_POSITION = "x";
private static final String XML_ATTRIBUTE_Y_POSITION = "y";
/**
* User data wrapper for {@link Rectangle2D}.
*
* @author Michael Knopf
*/
private static class Rectangle2DWrapper implements UserData<Object> {
/** Wrapped object. */
private final Rectangle2D rect;
public Rectangle2DWrapper(Rectangle2D rect) {
this.rect = rect;
}
public Rectangle2D get() {
return this.rect;
}
@Override
public UserData<Object> copyUserData(Object newParent) {
Rectangle2D newRect = new Rectangle2D.Double();
newRect.setRect(this.rect);
return new Rectangle2DWrapper(newRect);
}
}
/**
* User data wrapper for {@link Map}s from {@link Port} to {@link Integer} as used to store port
* spacings.
*
* @author Michael Knopf
*/
private static class PortSpacingWrapper implements UserData<Object> {
/** Wrapped map. */
private final Map<Port, Integer> spaces;
public PortSpacingWrapper(Map<Port, Integer> spacings) {
this.spaces = spacings;
}
public Map<Port, Integer> get() {
return this.spaces;
}
@Override
public UserData<Object> copyUserData(Object newParent) {
// mapping cannot be reused in copied instance
return null;
}
}
/**
* User data wrapper for {@link Point} objects, e.g. to store view positions of
* {@link OperatorChain}.
*
* @author Jan Czogalla
* @since 7.5
*/
private static class PointWrapper implements UserData<Object> {
/** Wrapped object. */
private final Point point;
public PointWrapper(Point point) {
this.point = point;
}
public Point get() {
return this.point;
}
@Override
public UserData<Object> copyUserData(Object newParent) {
Point newPoint = new Point(point);
return new PointWrapper(newPoint);
}
}
/**
* User data wrapper for {@link Double} objects.
*
* @author Jan Czogalla
* @since 7.5
*/
private static class DoubleWrapper implements UserData<Object> {
private final double value;
public DoubleWrapper(Double value) {
this.value = value;
}
public Double get() {
return this.value;
}
@Override
public UserData<Object> copyUserData(Object newParent) {
return this;
}
@Override
public String toString() {
return String.valueOf(get());
}
}
/**
* Adds the position and size of the operator to the XML element.
*/
@Override
public void operatorExported(final Operator op, final Element opElement) {
// add operator location and size
Rectangle2D bounds = lookupOperatorRectangle(op);
if (bounds != null) {
opElement.setAttribute(XML_ATTRIBUTE_X_POSITION, "" + (int) bounds.getX());
opElement.setAttribute(XML_ATTRIBUTE_Y_POSITION, "" + (int) bounds.getY());
opElement.setAttribute(XML_ATTRIBUTE_WIDTH, "" + (int) bounds.getWidth());
opElement.setAttribute(XML_ATTRIBUTE_HEIGHT, "" + (int) bounds.getHeight());
}
}
/**
* Adds port spacings to the XML element.
*/
@Override
public void executionUnitExported(final ExecutionUnit process, final Element element) {
for (Port port : process.getInnerSources().getAllPorts()) {
Element spacingElement = element.getOwnerDocument().createElement(XML_TAG_PORT_SPACING);
spacingElement.setAttribute(XML_ATTRIBUTE_PORT, XML_ATTRIBUTE_SOURCE + port.getName());
spacingElement.setAttribute(XML_ATTRIBUTE_SPACING, "" + lookupPortSpacing(port));
element.appendChild(spacingElement);
}
for (Port port : process.getInnerSinks().getAllPorts()) {
Element spacingElement = element.getOwnerDocument().createElement(XML_TAG_PORT_SPACING);
spacingElement.setAttribute(XML_ATTRIBUTE_PORT, XML_ATTRIBUTE_SINK + port.getName());
spacingElement.setAttribute(XML_ATTRIBUTE_SPACING, "" + lookupPortSpacing(port));
element.appendChild(spacingElement);
}
}
/**
* Extracts operator position and size from the XML element.
*/
@Override
public void operatorImported(final Operator op, final Element opElement) {
String x = opElement.getAttribute(XML_ATTRIBUTE_X_POSITION);
String y = opElement.getAttribute(XML_ATTRIBUTE_Y_POSITION);
String w = opElement.getAttribute(XML_ATTRIBUTE_WIDTH);
String h = opElement.getAttribute(XML_ATTRIBUTE_HEIGHT);
if (x != null && x.length() > 0) {
try {
Rectangle2D rect = new Rectangle2D.Double(Double.parseDouble(x), Double.parseDouble(y),
Double.parseDouble(w), Double.parseDouble(h));
setOperatorRectangle(op, rect);
} catch (Exception e) {
// ignore silently
}
}
}
/**
* Extracts port spacings from the XML element.
*/
@Override
public void executionUnitImported(final ExecutionUnit process, final Element element) {
NodeList children = element.getChildNodes();
for (Port port : process.getInnerSources().getAllPorts()) {
for (int i = 0; i < children.getLength(); i++) {
if (children.item(i) instanceof Element
&& XML_TAG_PORT_SPACING.equals(((Element) children.item(i)).getTagName())) {
Element psElement = (Element) children.item(i);
if ((XML_ATTRIBUTE_SOURCE + port.getName()).equals(psElement.getAttribute(XML_ATTRIBUTE_PORT))) {
try {
setPortSpacing(port, Integer.parseInt(psElement.getAttribute(XML_ATTRIBUTE_SPACING)));
} catch (NumberFormatException e) {
// do nothing
}
break;
}
}
}
}
for (Port port : process.getInnerSinks().getAllPorts()) {
for (int i = 0; i < children.getLength(); i++) {
if (children.item(i) instanceof Element
&& XML_TAG_PORT_SPACING.equals(((Element) children.item(i)).getTagName())) {
Element psElement = (Element) children.item(i);
if ((XML_ATTRIBUTE_SINK + port.getName()).equals(psElement.getAttribute(XML_ATTRIBUTE_PORT))) {
try {
setPortSpacing(port, Integer.parseInt(psElement.getAttribute(XML_ATTRIBUTE_SPACING)));
} catch (NumberFormatException e) {
// do nothing
}
break;
}
}
}
}
}
/**
* Looks up the spacing of the specified {@link Port}.
*
* @param port
* The port.
* @return Additional spacing.
*/
public static int lookupPortSpacing(Port port) {
Operator operator = port.getPorts().getOwner().getOperator();
PortSpacingWrapper wrapper = (PortSpacingWrapper) operator.getUserData(KEY_PORT_SPACING);
if (wrapper != null) {
Map<Port, Integer> spacings = wrapper.get();
if (spacings.containsKey(port)) {
return spacings.get(port);
} else {
// no spacing stored for this particular port
return 0;
}
} else {
// no spacing data available
return 0;
}
}
/**
* Sets the spacing of the specified {@link Port}.
*
* @param port
* The port.
* @param spacing
* The additional spacing.
*/
public static void setPortSpacing(Port port, Integer spacing) {
Operator operator = port.getPorts().getOwner().getOperator();
PortSpacingWrapper wrapper = (PortSpacingWrapper) operator.getUserData(KEY_PORT_SPACING);
Map<Port, Integer> spacings;
if (wrapper == null) {
spacings = new HashMap<>();
operator.setUserData(KEY_PORT_SPACING, new PortSpacingWrapper(spacings));
} else {
spacings = wrapper.get();
}
spacings.put(port, spacing);
}
/**
* Resets the spacing of the specified {@link Port}.
*
* @param port
* The port.
*/
public static void resetPortSpacing(Port port) {
Operator operator = port.getPorts().getOwner().getOperator();
PortSpacingWrapper wrapper = (PortSpacingWrapper) operator.getUserData(KEY_PORT_SPACING);
if (wrapper != null) {
Map<Port, Integer> spacings = wrapper.get();
spacings.remove(port);
}
}
/**
* Looks up the position rectangle of the specified {@link Operator}.
*
* @param operator
* The operator.
* @return The rectangle or null.
*/
public static Rectangle2D lookupOperatorRectangle(Operator operator) {
Rectangle2DWrapper wrapper = (Rectangle2DWrapper) operator.getUserData(KEY_OPERATOR_RECTANGLE);
if (wrapper == null) {
return null;
} else {
return wrapper.get();
}
}
/**
* Sets the position rectangle of the specified {@link Operator}.
*
* @param operator
* The operator.
* @param rect
* The rectangle.
*/
public static void setOperatorRectangle(Operator operator, Rectangle2D rect) {
operator.setUserData(KEY_OPERATOR_RECTANGLE, new Rectangle2DWrapper(rect));
}
/**
* Resets the position rectangle of the specified {@link Operator}.
*
* @param operator
* The operator.
*/
public static void resetOperatorRectangle(Operator operator) {
operator.setUserData(KEY_OPERATOR_RECTANGLE, null);
}
/**
* Looks up the view position of the specified {@link OperatorChain}.
*
* @param operatorChain
* The operator chain
* @return The position or null
* @since 7.5
*/
public static Point lookupOperatorChainPosition(OperatorChain operatorChain) {
PointWrapper wrapper = (PointWrapper) operatorChain.getUserData(KEY_OPERATOR_CHAIN_POSITION);
if (wrapper == null) {
return null;
} else {
return wrapper.get();
}
}
/**
* Sets the view position of the specified {@link OperatorChain}.
*
* @param operatorChain
* The operator chain
* @param position
* The center position
* @since 7.5
*/
public static void setOperatorChainPosition(OperatorChain operatorChain, Point position) {
operatorChain.setUserData(KEY_OPERATOR_CHAIN_POSITION, position == null ? null : new PointWrapper(position));
}
/**
* Resets the view position of the specified {@link OperatorChain}.
*
* @param operatorChain
* The operator chain
* @since 7.5
*/
public static void resetOperatorChainPosition(OperatorChain operatorChain) {
operatorChain.setUserData(KEY_OPERATOR_CHAIN_POSITION, null);
}
/**
* Looks up the zoom of the specified {@link OperatorChain}.
*
* @param operatorChain
* The operator chain
* @return The zoom or null
* @since 7.5
*/
public static Double lookupOperatorChainZoom(OperatorChain operatorChain) {
DoubleWrapper wrapper = (DoubleWrapper) operatorChain.getUserData(KEY_OPERATOR_CHAIN_ZOOM);
if (wrapper == null) {
return null;
} else {
return wrapper.get();
}
}
/**
* Sets the zoom of the specified {@link OperatorChain}.
*
* @param operatorChain
* The operator chain
* @param zoom
* The zoom
* @since 7.5
*/
public static void setOperatorChainZoom(OperatorChain operatorChain, Double zoom) {
operatorChain.setUserData(KEY_OPERATOR_CHAIN_ZOOM, zoom == null ? null : new DoubleWrapper(zoom));
}
/**
* Resets the zoom of the specified {@link OperatorChain}.
*
* @param operatorChain
* The operator chain
* @since 7.5
*/
public static void resetOperatorChainZoom(OperatorChain operatorChain) {
operatorChain.setUserData(KEY_OPERATOR_CHAIN_ZOOM, null);
}
/**
* Looks up the scroll position of the specified {@link OperatorChain}.
*
* @param operatorChain
* The operator chain
* @return The scroll position or null
* @since 7.5
*/
public static Point lookupScrollPosition(OperatorChain operatorChain) {
PointWrapper wrapper = (PointWrapper) operatorChain.getUserData(KEY_SCROLL_POSITION);
if (wrapper == null) {
return null;
} else {
return wrapper.get();
}
}
/**
* Sets the scroll position of the specified {@link OperatorChain}.
*
* @param operatorChain
* The operator chain
* @param scrollPos
* The scroll position
* @since 7.5
*/
public static void setScrollPosition(OperatorChain operatorChain, Point scrollPos) {
operatorChain.setUserData(KEY_SCROLL_POSITION, new PointWrapper(scrollPos));
}
/**
* Resets the scroll position of the specified {@link OperatorChain}.
*
* @param operatorChain
* The operator chain
* @since 7.5
*/
public static void resetScrollPosition(OperatorChain operatorChain) {
operatorChain.setUserData(KEY_SCROLL_POSITION, null);
}
/**
* Looks up the scroll process index of the specified {@link OperatorChain}.
*
* @param operatorChain
* The operator chain
* @return The index or null
* @since 7.5
*/
public static Double lookupScrollIndex(OperatorChain operatorChain) {
DoubleWrapper wrapper = (DoubleWrapper) operatorChain.getUserData(KEY_SCROLL_INDEX);
if (wrapper == null) {
return null;
} else {
return wrapper.get();
}
}
/**
* Sets the scroll process index of the specified {@link OperatorChain}.
*
* @param operatorChain
* The operator chain
* @param index
* The process index
* @since 7.5
*/
public static void setScrollIndex(OperatorChain operatorChain, Double index) {
operatorChain.setUserData(KEY_SCROLL_INDEX, new DoubleWrapper(index));
}
/**
* Resets the scroll process index of the specified {@link OperatorChain}.
*
* @param operatorChain
* The operator chain.
* @since 7.5
*/
public static void resetScrollIndex(OperatorChain operatorChain) {
operatorChain.setUserData(KEY_SCROLL_INDEX, null);
}
/**
* Looks up the restore flag of the specified {@link Operator}.
*
* @param operator
* the operator
* @return the flag or null
* @since 7.5
*/
public static boolean lookupRestore(Operator operator) {
UserData<?> restore = operator.getUserData(KEY_RESTORE);
return restore != null;
}
/**
* Sets the restore flag of the specified {@link Operator}.
*
* @param operator
* the operator
* @since 7.5
*/
public static void setRestore(Operator operator) {
operator.setUserData(KEY_RESTORE, new FlagUserData());
}
/**
* Resets the restore flag of the specified {@link OperatorChain}.
*
* @param operator
* The operator
* @since 7.5
*/
public static void resetRestore(Operator operator) {
operator.setUserData(KEY_RESTORE, null);
}
}