/* * RapidMiner * * Copyright (C) 2001-2008 by Rapid-I and the contributors * * Complete list of developers available at our web site: * * http://rapid-i.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; import java.util.ArrayList; import java.util.ConcurrentModificationException; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import com.rapidminer.Process; import com.rapidminer.operator.condition.CombinedInnerOperatorCondition; import com.rapidminer.operator.condition.InnerOperatorCondition; import com.rapidminer.operator.condition.SimpleChainInnerOperatorCondition; import com.rapidminer.tools.Tools; import de.tud.inf.operator.IOCapability; import de.tud.inf.operator.UnsatisfiedCapabilityException; /** * A chain of operators that is subsequently applied. As an OperatorChain is an * Operator itself it can be arbitrarily nested.<br> * Inheritants can access inner operators by {@link #getOperator(int)}. They * should override {@link #getMaxNumberOfInnerOperators()} and * {@link #getMinNumberOfInnerOperators()} which are used for some checks. They * should also override {@link #getInnerOperatorCondition()} to ensure that all * inner operators get the desired input and return the necessary output for the * next inner operator. Please refer to the RapidMiner tutorial for a description how * to implement your own operator chain. * * @author Simon Fischer, Ingo Mierswa * @version $Id: OperatorChain.java,v 1.8 2008/07/07 07:06:44 ingomierswa Exp $ */ public abstract class OperatorChain extends Operator { /** The inner operators. They are applied in their ordering in the list. */ private List<Operator> operators = new ArrayList<Operator>(); /** The list of listeners for adding events. */ private List<AddListener> addListeners = new LinkedList<AddListener>(); // -------------------------------------------------------------------------------- /** Creates an empty operator chain. */ public OperatorChain(OperatorDescription description) { super(description); } /** Returns the maximum number of innner operators. */ public abstract int getMaxNumberOfInnerOperators(); /** Returns the minimum number of innner operators. */ public abstract int getMinNumberOfInnerOperators(); /** * Must return a condition of the IO behaviour of all desired inner * operators. If there are no "special" conditions and the chain * works similar to a simple operator chain this method should at least * return a {@link SimpleChainInnerOperatorCondition}. More than one * condition should be combined with help of the class * {@link CombinedInnerOperatorCondition}. */ public abstract InnerOperatorCondition getInnerOperatorCondition(); /** Adds the given listener. */ public void addAddListener(AddListener listener) { addListeners.add(listener); } /** Removes the given listener. */ public void removeAddListener(AddListener listener) { addListeners.remove(listener); } /** Notifies all added add listeners that a new child was added. */ private void fireAddEvent(Operator child) { Iterator<AddListener> i = addListeners.iterator(); while (i.hasNext()) { i.next().operatorAdded(child); } } /** * Performs a deep clone of this operator chain. Use this method only if you * are sure what you are doing. */ public Operator cloneOperator(String name) { OperatorChain clone = (OperatorChain) super.cloneOperator(name); clone.operators = new ArrayList<Operator>(); Iterator<Operator> i = operators.iterator(); while (i.hasNext()) { Operator originalChild = i.next(); Operator clonedChild = originalChild.cloneOperator(originalChild.getName()); clonedChild.setParent(clone); clone.addOperator(clonedChild); } return clone; } /** * This method checks if inner operators can handle their input and deliver * the necessary output. Depending on the return value of the method * {@link #shouldReturnInnerOutput()} this method returns * <ul> * <li>the result of <code>getDeliveredOutputClasses()</code> if the * output of the inner operators should not be returned.</li> * <li>the result of <code>getAllOutputClasses(Class[] innerOutput)</code> * if the output of the inner operators (innerOutput) should also be * returned.</li> * </ul> */ public final Class<?>[] checkIO(Class<?>[] input) throws IllegalInputException, WrongNumberOfInnerOperatorsException { InnerOperatorCondition condition = getInnerOperatorCondition(); Class<?>[] innerOutput = condition.checkIO(this, input); if (shouldReturnInnerOutput()) { return getAllOutputClasses(innerOutput); } else { return getDeliveredOutputClasses(); } } public final IOCapability[] checkCapabilites(IOCapability[] input) throws UnsatisfiedCapabilityException { InnerOperatorCondition condition = getInnerOperatorCondition(); return condition.checkCapabilities(this, input); //TODO: returnInnerOutput - Awareness /* if (shouldReturnInnerOutput()) { return getAllOutputClasses(innerOutput); } else { return getDeliveredOutputClasses(); } */ } /** * Indicates if inner output should be delivered by this operator chain. * Default is false. Operators which want to change this default behaviour * should override this method and should return true. In this case the * method checkIO would not longer return the result of * {@link #getDeliveredOutputClasses()} but of * {@link #getAllOutputClasses(Class[])}. */ public boolean shouldReturnInnerOutput() { return false; } /** * Helper method if in addition to the created output the inner output * should also be returned. Can be used in {@link #checkIO(Class[] input)}. */ private Class[] getAllOutputClasses(Class[] innerOutput) { Class[] deliveredOutput = getDeliveredOutputClasses(); Class[] result = new Class[deliveredOutput.length + innerOutput.length]; System.arraycopy(deliveredOutput, 0, result, 0, deliveredOutput.length); System.arraycopy(innerOutput, 0, result, deliveredOutput.length, innerOutput.length); return result; } /** * Adds a new inner operator at the last position. The returned index is the * position of the added operator with respect to all operators (including * the disabled operators). */ public final int addOperator(Operator o) { return addOperator(o, getNumberOfAllOperators()); } /** * Adds the given operator at the given position. Please note that all * operators (including the disabled operators) are used for position * calculations. */ public final int addOperator(Operator operator, int index) { if (operator == null) return -1; operator.setParent(this); operators.add(index, operator); Process process = getProcess(); if (process != null) operator.registerOperator(process); if (getNumberOfOperators() == getMaxNumberOfInnerOperators() + 1) { logWarning("More than " + getMaxNumberOfInnerOperators() + " inner operators!"); } fireAddEvent(operator); return index; } /** Register this operator chain and all of its children in the given process. This might change the * name of the operator. */ protected void registerOperator(Process process) { super.registerOperator(process); Iterator<Operator> i = operators.iterator(); while (i.hasNext()) { Operator child = i.next(); child.registerOperator(process); } } /** Unregisters this chain and all of its children from the given process. */ protected void unregisterOperator(Process process) { super.unregisterOperator(process); Iterator<Operator> i = operators.iterator(); while (i.hasNext()) { Operator operator = i.next(); operator.unregisterOperator(process); } } /** Removes the given operator from this operator chain. Do not use this method to actually remove * an operator from an operator chain. Use operator.remove() instead. This method will be invoked * by the remove() method (which also performs some other actions). */ protected final void removeOperator(Operator operator) { operators.remove(operator); } /** Returns the i-th inner operator. */ public Operator getOperator(int i) { if ((i < 0) || (i >= getNumberOfOperators())) { throw new RuntimeException("Illegal operator index in getOperator() (" + getName() + "): " + i); } int counter = 0; Iterator<Operator> o = operators.iterator(); while (o.hasNext()) { Operator operator = o.next(); if (operator.isEnabled()) { if (counter == i) return operator; counter++; } } return null; } /** Returns an iterator over all Operators. */ public Iterator<Operator> getOperators() { return operators.iterator(); } /** Returns recursively all child operators independently if they are activated or not. */ public List<Operator> getAllInnerOperators() { List<Operator> children = new LinkedList<Operator>(); for (int i = 0; i < operators.size(); i++) { Operator innerOp = operators.get(i); children.add(innerOp); if (innerOp instanceof OperatorChain) children.addAll(((OperatorChain) innerOp).getAllInnerOperators()); } return children; } /** Returns the number of all enabled inner operators. */ public int getNumberOfOperators() { int number = 0; Iterator<Operator> i = operators.iterator(); while (i.hasNext()) { Operator op = i.next(); if (op.isEnabled()) number++; } return number; } /** * Returns the number of all inner operators (including the disabled * operators). Mainly used for GUI purposes. Operators should use * {@link #getNumberOfOperators()}. */ public int getNumberOfAllOperators() { return operators.size(); } /** * Returns the i-th operator. In contrast to the method * {@link #getOperator(int i)} this method also uses disabled operators. * Mainly used for GUI purposes. Other operators should use the method * {@link #getOperator(int i)} which only delivers enabled inner operators. */ public Operator getOperatorFromAll(int i) { return operators.get(i); } /** Returns the index of the given operator in the list of children. If useDisabled is true, * disabled operators are also used for index calculations. */ public int getIndexOfOperator(Operator operator, boolean useDisabled) { if (useDisabled) { return operators.indexOf(operator); } else { int index = 0; Iterator<Operator> i = operators.iterator(); while (i.hasNext()) { Operator current = i.next(); if (current.isEnabled()) { if (current.equals(operator)) return index; index++; } } return -1; } } /** * Returns the inner operator named <tt>name</tt> or null if no such * operator exists. */ public Operator getInnerOperatorForName(String name) { if (name == null) return null; if (name.equals(this.getName())) return this; Iterator<Operator> i = operators.iterator(); while (i.hasNext()) { Operator inner = i.next(); if (name.equals(inner.getName())) return inner; if (inner instanceof OperatorChain) { Operator innerinner = ((OperatorChain) inner).getInnerOperatorForName(name); if (innerinner != null) return innerinner; } } return null; } /** Returns the result of the super method if this operator does not have a parent. * Otherwise this method returns true if it is enabled and the parent is also enabled. */ /* public boolean isEnabled() { if (getParent() == null) { return super.isEnabled(); } else { return (super.isEnabled() && getParent().isEnabled()); } } */ /** Invokes the super method and the method for all children. */ public void processStarts() throws OperatorException { super.processStarts(); for (int i = 0; i < getNumberOfOperators(); i++) getOperator(i).processStarts(); } /** Invokes the super method and the method for all children. */ public void processFinished() throws OperatorException { super.processFinished(); for (int i = 0; i < getNumberOfOperators(); i++) getOperator(i).processFinished(); } // -------------------- implementierte abstrakte Methoden // -------------------- /** * Applies all inner operators. The input to this operator becomes the input * of the first inner operator. The latter's output is passed to the second * inner operator and so on. Note to subclassers: If subclasses (for example * wrappers) want to make use of this method remember to call exactly this * method <tt>(super.apply())</tt> and do not call * <tt>super.apply(IOContainer)</tt> erroneously which will result in an * infinite loop. * * @return the last inner operator's output or the input itself if the chain * is empty. */ public IOObject[] apply() throws OperatorException { IOContainer input = getInput(); Iterator<Operator> i = operators.iterator(); while (i.hasNext()) { try { input = i.next().apply(input); } catch (ConcurrentModificationException e) { if (isDebugMode()) e.printStackTrace(); throw new UserError(this, 923); } } return input.getIOObjects(); } // -------------------------------------------------------------------------------- /** * This method invokes the additional check method for each child. * Subclasses which override this method to perform a check should also * invoke super.performAdditionalChecks()! */ public void performAdditionalChecks() throws UserError { Iterator<Operator> i = operators.iterator(); while (i.hasNext()) { Operator o = i.next(); if (o.isEnabled()) o.performAdditionalChecks(); } } /** * Will throw an exception if a non optional property has no default value * and is not defined by user. */ public int checkProperties() { int errorCount = super.checkProperties(); Iterator<Operator> i = operators.iterator(); while (i.hasNext()) { Operator o = i.next(); if (o.isEnabled()) errorCount += o.checkProperties(); } return errorCount; } /** * Will count an the number of deprecated operators, i.e. the operators * which {@link #getDeprecationInfo()} method does not return null. Returns * the total number of deprecations. */ public int checkDeprecations() { int deprecationCount = super.checkDeprecations(); Iterator<Operator> i = operators.iterator(); while (i.hasNext()) { Operator o = i.next(); deprecationCount += o.checkDeprecations(); } return deprecationCount; } /** * Checks if the number of inner operators lies between MinInnerOps and * MaxInnerOps. Performs the check for all operator chains which are * children of this operator chain. */ public int checkNumberOfInnerOperators() { int errorCount = 0; if ((getNumberOfOperators() < getMinNumberOfInnerOperators()) || (getNumberOfOperators() > getMaxNumberOfInnerOperators())) { int maximum = getMaxNumberOfInnerOperators(); String maximumString = maximum == Integer.MAX_VALUE ? "infinity" : (maximum + ""); String message = "Operator has " + getNumberOfOperators() + " " + ((getNumberOfOperators() == 1) ? "child" : "children") + ", should be " + ((getMinNumberOfInnerOperators() == getMaxNumberOfInnerOperators()) ? getMinNumberOfInnerOperators() + "" : " between " + getMinNumberOfInnerOperators() + " and " + maximumString); addError(message); errorCount++; } Iterator<Operator> i = operators.iterator(); while (i.hasNext()) { Operator o = i.next(); if ((o instanceof OperatorChain) && o.isEnabled()) errorCount += ((OperatorChain) o).checkNumberOfInnerOperators(); } return errorCount; } /** * Returns this OperatorChain's name and class and the ExperimentTrees of * the inner operators. * @deprecated Use {@link #createProcessTree(int,String,String,Operator,String)} instead */ @Deprecated protected String createExperimentTree(int indent, String selfPrefix, String childPrefix, Operator markOperator, String mark) { return createProcessTree(indent, selfPrefix, childPrefix, markOperator, mark); } /** * Returns this OperatorChain's name and class and the process trees of * the inner operators. */ protected String createProcessTree(int indent, String selfPrefix, String childPrefix, Operator markOperator, String mark) { String tree = super.createProcessTree(indent, selfPrefix, childPrefix, markOperator, mark); Iterator<Operator> i = operators.iterator(); while (i.hasNext()) { Operator o = i.next(); tree += Tools.getLineSeparator() + o.createProcessTree(indent, childPrefix + "+- ", childPrefix + (i.hasNext() ? "| " : " "), markOperator, mark); } return tree; } /** Returns the XML representation for all inner operators. */ protected final String getInnerOperatorsXML(String indent) { StringBuffer result = new StringBuffer(); Iterator<Operator> i = operators.iterator(); while ((i.hasNext())) { result.append(i.next().getXML(indent)); } return result.toString(); } /** Clears the error list for this operator (by invoking the super method) and all children. */ public void clearErrorList() { Iterator<Operator> i = operators.iterator(); while ((i.hasNext())) { i.next().clearErrorList(); } super.clearErrorList(); } }