/** * 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; 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; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; /** * <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<>(); private List<String> outputRepositoryLocations = new ArrayList<>(); private List<Pair<String, String>> macros = new LinkedList<>(); private List<Pair<OperatorLibrary, String>> libraryLocations = new LinkedList<>(); 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; } /** * Updates the given macro with the given value. Fires an update after applying the changes. * * @param macroIndex * index of the macro in the {@link #getMacros()} list * @param pairIndex * 0 for first value; 1 for second value * @param newValue */ public void updateMacroValue(int macroIndex, int pairIndex, String newValue) { switch (pairIndex) { case 0: getMacros().get(macroIndex).setFirst(newValue); fireUpdate(this); break; case 1: getMacros().get(macroIndex).setSecond(newValue); fireUpdate(this); break; default: throw new IndexOutOfBoundsException(pairIndex + " > 1"); } } /** 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); } /** Removes a macro from the list */ public void removeMacro(int index) { this.macros.remove(index); 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<>(library, location)); try { library.registerOperators(); } catch (OperatorCreationException e) { e.printStackTrace(); // TODO: This is unnecessary if operators aren't initialized during registration! } } }