/** * 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.studio.concurrency.internal.util; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.LinkedList; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import com.rapidminer.LoggingListener; import com.rapidminer.Process; import com.rapidminer.ProcessListener; import com.rapidminer.datatable.DataTable; import com.rapidminer.operator.IOContainer; import com.rapidminer.operator.IOObject; import com.rapidminer.operator.Operator; import com.rapidminer.operator.OperatorException; import com.rapidminer.tools.SimpleObservable; import com.rapidminer.tools.Tools; /** * This class implements listener interfaces to keep track of the execution of a process in the * background and to allow the gui to access and display the state. * <p> * Note that this part of the API is only temporary and might be removed in future versions again. * </p> * * @author Sebastian Land * @since 7.4 */ public class ProcessBackgroundExecutionState extends SimpleObservable<ProcessBackgroundExecutionState.State> { public enum State { FAILED, CANCELED, PENDING, RUNNING, FINISHED; } private LinkedList<ProcessExecutionStackEntry> operatorStack = new LinkedList<>(); private LinkedList<DataTable> processLogs = new LinkedList<>(); private ProcessListener processListener; private LoggingListener loggingListener; private State state = State.PENDING; private Future<IOContainer> futureResults; private List<IOObject> results; private Path logFilePath; private Process process; public ProcessBackgroundExecutionState(Process process) { this.process = process; // adding listeners processListener = new ProcessListener() { @Override public void processStarts(Process process) { ProcessBackgroundExecutionState.this.setState(State.RUNNING); } @Override public void processStartedOperator(Process process, Operator op) { operatorStack.push(new ProcessExecutionStackEntry(op)); } @Override public void processFinishedOperator(Process process, Operator op) { operatorStack.pop(); } @Override public void processEnded(Process process) { operatorStack.clear(); if (state != State.CANCELED) { ProcessBackgroundExecutionState.this.setState(State.FINISHED); } } }; loggingListener = new LoggingListener() { @Override public void addDataTable(DataTable dataTable) { processLogs.add(dataTable); } @Override public void removeDataTable(DataTable dataTable) { processLogs.remove(dataTable); } }; this.process.getRootOperator().addProcessListener(processListener); this.process.addLoggingListener(loggingListener); } public boolean isStarted() { return state != State.PENDING; } public boolean isRunning() { return state == State.RUNNING; } public boolean isEnded() { // Hack to move to finish state, even if listener was not informed if (operatorStack.isEmpty() && getResults() != null) { this.setState(State.FINISHED); } return state == State.FINISHED; } /** * This returns the reference on the current operator stack. Do not modify! */ public LinkedList<ProcessExecutionStackEntry> getStack() { return operatorStack; } public void setResults(Future<IOContainer> futureResults) { this.futureResults = futureResults; } public void setLogFilePath(Path logFile) { this.logFilePath = logFile; } /** * Returns the process console log contents. * * @return the log as a string or {@code null} if the log cannot be read from its temp file. */ public String getLogContent() { try (InputStream is = Files.newInputStream(logFilePath)) { return Tools.readTextFile(is); } catch (IOException e) { e.printStackTrace(); return null; } } /** * This returns the results or null if they are still to be computed. * * @return * @throws OperatorException */ public List<IOObject> getResults() { if (futureResults != null && futureResults.isDone()) { try { if (results == null) { results = futureResults.get().asList(); } return results; } catch (InterruptedException e) { } catch (ExecutionException e) { } } return null; } /** * This returns the exception that has been thrown during runtime or null if not present or * still executing. * * @return */ public Throwable getException() { try { if (futureResults != null && futureResults.isDone()) { futureResults.get(); } } catch (ExecutionException e) { return e.getCause(); } catch (InterruptedException e) { } return null; } public boolean isFailed() { if (state != State.FAILED && state != State.CANCELED) { if (getException() != null) { this.setState(State.FAILED); } } return state == State.FAILED; } public void setFailed() { this.setState(State.FAILED); } public boolean isStopped() { return state == State.CANCELED; } public void setStopped() { this.setState(State.CANCELED); } public List<DataTable> getProcessLogs() { return processLogs; } public State getState() { isFailed(); isEnded(); return state; } /** * Sets the current state * * @param newState * @return true if the state has changed */ public boolean setState(State newState) { boolean changed = false; if (state != newState) { state = newState; if (state == State.FINISHED || state == State.CANCELED || state == State.FAILED) { cleanup(); } changed = true; this.fireUpdate(newState); } return changed; } /** * Clean up by removing listeners and reference to process. Call when the state is in any final * state. */ private void cleanup() { this.process.getRootOperator().removeProcessListener(processListener); this.process.removeLoggingListener(loggingListener); this.process = null; } }