/** * 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.operator.ports; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedList; import java.util.List; import com.rapidminer.gui.renderer.RendererService; import com.rapidminer.operator.IOObject; import com.rapidminer.operator.IOObjectCollection; import com.rapidminer.operator.UserError; import com.rapidminer.operator.ports.metadata.CollectionMetaData; import com.rapidminer.operator.ports.metadata.CollectionPrecondition; import com.rapidminer.operator.ports.metadata.CompatibilityLevel; import com.rapidminer.operator.ports.metadata.FlatteningPassThroughRule; import com.rapidminer.operator.ports.metadata.InputMissingMetaDataError; import com.rapidminer.operator.ports.metadata.MDTransformationRule; import com.rapidminer.operator.ports.metadata.ManyToOnePassThroughRule; import com.rapidminer.operator.ports.metadata.MetaData; import com.rapidminer.operator.ports.metadata.MetaDataError; import com.rapidminer.operator.ports.metadata.Precondition; /** * Port Extender that can be used if an operator needs to receive an arbitrary number of input * objects. Delivered Collections might be unfolded automatically, so that the contained objects are * presented as if they would have been forwarded one by one to the operator. This port extender can * be configured to throw errors if the wrong type or to few inputs are connected. * * @author Simon Fischer, Sebastian Land */ public class InputPortExtender extends SinglePortExtender<InputPort> { private MetaData desiredMetaData; private int numberOfMandatory; public InputPortExtender(String name, Ports<InputPort> ports) { this(name, ports, null, 0); } public InputPortExtender(String name, Ports<InputPort> ports, MetaData desiredMetaData, boolean firstIsMandatory) { this(name, ports, desiredMetaData, firstIsMandatory ? 1 : 0); } public InputPortExtender(String name, Ports<InputPort> ports, MetaData desiredMetaData, int numberOfMandatory) { super(name, ports); this.desiredMetaData = desiredMetaData; this.numberOfMandatory = numberOfMandatory; } @Override protected InputPort createPort() { InputPort port = super.createPort(); Precondition precondition = makePrecondition(port, getManagedPorts().size()); if (precondition != null) { port.addPrecondition(new CollectionPrecondition(precondition)); } return port; } /** * Returns a list of non-null data of all input ports. * * @param unfold * If true, collections are added as individual objects rather than as a collection. * The unfolding is done recursively. * @deprecated use {@link #getData(Class, boolean)} */ @Deprecated @SuppressWarnings("unchecked") public <T extends IOObject> List<T> getData(boolean unfold) { List<IOObject> results = new LinkedList<IOObject>(); for (InputPort port : getManagedPorts()) { IOObject data = port.getAnyDataOrNull(); if (data != null) { if (unfold && (data instanceof IOObjectCollection)) { try { unfold((IOObjectCollection<?>) data, results, IOObject.class, port); } catch (UserError e) { throw new RuntimeException(e.getMessage(), e); } } else { results.add(data); } } } return (List<T>) results; } /** * Returns a list of non-null data of all input ports. * * @param unfold * If true, collections are added as individual objects rather than as a collection. * The unfolding is done recursively. * @throws UserError */ @SuppressWarnings("unchecked") public <T extends IOObject> List<T> getData(Class<T> desiredClass, boolean unfold) throws UserError { List<T> results = new ArrayList<T>(); for (InputPort port : getManagedPorts()) { IOObject data = port.getAnyDataOrNull(); if (data != null) { if (unfold && data instanceof IOObjectCollection) { unfold((IOObjectCollection<?>) data, results, desiredClass, port); } else { if (desiredClass.isInstance(data)) { results.add((T) data); } else { throw new UserError(getPorts().getOwner().getOperator(), 156, RendererService.getName(data.getClass()), port.getName(), RendererService.getName(desiredClass)); } } } } return results; } /** * @param desiredClass * method will throw unless all non-collection children are of type desired class * @param port * Used for error message only */ @SuppressWarnings("unchecked") private <T extends IOObject> void unfold(IOObjectCollection<?> collection, List<T> results, Class<T> desiredClass, Port port) throws UserError { for (IOObject obj : collection.getObjects()) { if (obj instanceof IOObjectCollection) { unfold((IOObjectCollection<?>) obj, results, desiredClass, port); } else { if (desiredClass.isInstance(obj)) { results.add((T) obj); } else { throw new UserError(getPorts().getOwner().getOperator(), 156, RendererService.getName(obj.getClass()), port.getName(), RendererService.getName(desiredClass)); } } } } /** * Returns a list of non-null meta data of all input ports. */ public List<MetaData> getMetaData(boolean unfold) { List<MetaData> results = new LinkedList<MetaData>(); for (InputPort port : getManagedPorts()) { MetaData data = port.getMetaData(); if (data != null) { if (unfold && data instanceof CollectionMetaData) { results.add(((CollectionMetaData) data).getElementMetaDataRecursive()); } else { results.add(data); } } } return results; } /** * Subclasses might override this method in order to specify preconditions dependent on the * number of port. For example when a parameter lists the input types, etc... */ protected Precondition makePrecondition(final InputPort port, int portIndex) { return makePrecondition(port); } protected Precondition makePrecondition(final InputPort port) { if (desiredMetaData != null) { return new Precondition() { @Override public void assumeSatisfied() { if (!getManagedPorts().isEmpty()) { getManagedPorts().iterator().next().receiveMD(desiredMetaData); } } @Override public void check(MetaData metaData) { if (!getManagedPorts().isEmpty()) { int portIndex = getManagedPorts().indexOf(port); boolean isMandatory = (portIndex < numberOfMandatory); // checking if some of the ports received collection for (int i = portIndex; i >= 0; i--) { MetaData portMetaData = getManagedPorts().get(i).getMetaData(); if (portMetaData != null) { isMandatory &= !portMetaData.isCompatible(new CollectionMetaData(desiredMetaData), CompatibilityLevel.VERSION_5); } } // if not: throw error if (metaData == null && isMandatory) { port.addError(new InputMissingMetaDataError(port, desiredMetaData.getObjectClass(), null)); } if (metaData != null) { if (!desiredMetaData.isCompatible(metaData, CompatibilityLevel.VERSION_5)) { Collection<MetaDataError> errors = desiredMetaData.getErrorsForInput(port, metaData, CompatibilityLevel.VERSION_5); for (MetaDataError error : errors) { port.addError(error); } } } } } @Override public String getDescription() { return "requires " + ((numberOfMandatory > 0) ? " at least " + numberOfMandatory + " " : "") + desiredMetaData.getDescription(); } @Override public MetaData getExpectedMetaData() { return desiredMetaData; } @Override public boolean isCompatible(MetaData input, CompatibilityLevel level) { return desiredMetaData.isCompatible(input, level); } }; } return null; } public MDTransformationRule makePassThroughRule(OutputPort outputPort) { return new ManyToOnePassThroughRule(getManagedPorts(), outputPort); } public MDTransformationRule makeFlatteningPassThroughRule(OutputPort outputPort) { return new FlatteningPassThroughRule(getManagedPorts(), outputPort); } }