/* * RapidMiner * * Copyright (C) 2001-2011 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; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; import com.rapidminer.operator.OperatorCreationException; import com.rapidminer.operator.libraries.OperatorLibrary; import com.rapidminer.repository.RepositoryLocation; import com.rapidminer.tools.AbstractObservable; import com.rapidminer.tools.container.Pair; /** <p>The process context holds some data controlling the execution of a {@link Process}. * This includes connections of the input and output ports of the root operator * to repository locations as well as the definition of macros. * </p> * <p> * The fact that this data is defined outside the process itself is particularly * useful if this process is offered as a service, so it can be adapted easily. Furthermore, * this saves the process designer from defining data reading and storing operators at the beginning * and at the end of the process.</p> * <p> * Note: A ProcessContext is not necessarily associate with a {@link Process}. E.g., if a process * is run remotely, it does not necessarily exist on the machine that prepares the context. * </p> * <p> * Since this class acts merely as a data container, it has public getter and setter methods * which return references to the actual data (as opposed to immutable views). In order to trigger * an update, call a setter method rather than adding to the lists, which is invisible to the * process context. * </p><p> * The data is saved as strings rather than, e.g. using {@link RepositoryLocation}s. * </p> * <p> * Since this class is saved as a Lob with the ProcessExecutionParameters entity, serializability must * be ensured. This is guaranteed by the fact that this class only contains {@link List}s of * strings or {@link Pair}s of strings, where {@link Pair} is serializable. * </p> * @author Simon Fischer */ public class ProcessContext extends AbstractObservable<ProcessContext> implements Serializable { private static final long serialVersionUID = 1L; private List<String> inputRepositoryLocations = new ArrayList<String>(); private List<String> outputRepositoryLocations= new ArrayList<String>(); private List<Pair<String,String>> macros = new LinkedList<Pair<String,String>>(); private List<Pair<OperatorLibrary, String>> libraryLocations = new LinkedList<Pair<OperatorLibrary,String>>(); public ProcessContext() { } public List<String> getInputRepositoryLocations() { return Collections.unmodifiableList(inputRepositoryLocations); } public void setInputRepositoryLocations(List<String> inputRepositoryLocations) { if (inputRepositoryLocations.contains(null)) { throw new NullPointerException("Null elements not allowed"); } this.inputRepositoryLocations = inputRepositoryLocations; fireUpdate(this); } public List<String> getOutputRepositoryLocations() { return Collections.unmodifiableList(outputRepositoryLocations); } public void setOutputRepositoryLocations(List<String> outputRepositoryLocations) { if (outputRepositoryLocations.contains(null)) { throw new NullPointerException("Null elements not allowed"); } this.outputRepositoryLocations = outputRepositoryLocations; fireUpdate(this); } public List<Pair<String, String>> getMacros() { return macros; } /** Adds a macro to the list or sets an existing one. */ public void addMacro(Pair<String, String> macro) { for (Pair<String, String> existingMacro : this.macros) { if (existingMacro.getFirst().equals(macro.getFirst())) { // overwrite existing existingMacro.setSecond(macro.getSecond()); return; } } this.macros.add(macro); fireUpdate(this); } public void setMacros(List<Pair<String, String>> macros) { this.macros = macros; fireUpdate(this); } public void setOutputRepositoryLocation(int index, String location) { if (location == null) { throw new NullPointerException("Null location not allowed"); } while (outputRepositoryLocations.size() <= index) { outputRepositoryLocations.add(""); } outputRepositoryLocations.set(index, location); fireUpdate(); } public void setInputRepositoryLocation(int index, String location) { if (location == null) { throw new NullPointerException("Null location not allowed"); } while (inputRepositoryLocations.size() <= index) { inputRepositoryLocations.add(""); } inputRepositoryLocations.set(index, location); fireUpdate(); } public void removeOutputLocation(int rowIndex) { outputRepositoryLocations.remove(rowIndex); } public void removeInputLocation(int rowIndex) { inputRepositoryLocations.remove(rowIndex); } public void addOutputLocation(String location) { if (location == null) { throw new NullPointerException("Location must not be null"); } outputRepositoryLocations.add(location); } public void addInputLocation(String location) { if (location == null) { throw new NullPointerException("Location must not be null"); } inputRepositoryLocations.add(location); } /** Merges the current context with the given one. * Macros will be simply added, input and output locations override * their respective counterparts if not null. This modifies this instance. */ public void superimpose(ProcessContext other) { if (other == null) { return; } for (Pair<String, String> macro : other.macros) { this.macros.add(macro); } for (int i = 0; i < other.inputRepositoryLocations.size(); i++) { String loc = other.inputRepositoryLocations.get(i); if ((loc != null) && !loc.isEmpty()) { this.setInputRepositoryLocation(i, loc); } } for (int i = 0; i < other.outputRepositoryLocations.size(); i++) { String loc = other.outputRepositoryLocations.get(i); if ((loc != null) && !loc.isEmpty()) { this.setOutputRepositoryLocation(i, loc); } } } @Override public String toString() { StringBuilder b = new StringBuilder(); b.append("Macros: ").append(getMacros()); b.append("; Input: ").append(getInputRepositoryLocations()); b.append("; Output: ").append(getOutputRepositoryLocations()); return b.toString(); } /** * This returns all loaded OperatorLibries that should be used within this process. */ public List<OperatorLibrary> getOperatorLibraries() { return Collections.emptyList(); } public void addOperatorLibrary(OperatorLibrary library, String location) { libraryLocations.add(new Pair<OperatorLibrary, String>(library, location)); try { library.registerOperators(); } catch (OperatorCreationException e) { e.printStackTrace(); // TODO: This is unnecessary if operators aren't initialized during registration! } } }