/*
* Copyright (C) 2012 The CyanogenMod Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.cyanogenmod.filemanager.commands.shell;
import com.cyanogenmod.filemanager.commands.AsyncResultExecutable;
import com.cyanogenmod.filemanager.commands.AsyncResultListener;
import com.cyanogenmod.filemanager.commands.SIGNAL;
import com.cyanogenmod.filemanager.util.FileHelper;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* An abstract class that allow the consumption of partial data. Commands
* can parse the results while this are still retrieving.
*/
public abstract class AsyncResultProgram
extends Program implements AsyncResultExecutable, AsyncResultProgramListener {
/**
* @hide
*/
static final Byte STDIN = new Byte((byte)0);
/**
* @hide
*/
static final Byte STDERR = new Byte((byte)1);
private final AsyncResultListener mAsyncResultListener;
private AsyncResultProgramThread mWorkerThread;
/**
* @hide
*/
final List<String> mPartialData;
/**
* @hide
*/
final List<Byte> mPartialDataType;
private final Object mSync = new Object();
/**
* @hide
*/
final Object mTerminateSync = new Object();
private boolean mCancelled;
private OnCancelListener mOnCancelListener;
private OnEndListener mOnEndListener;
private StringBuffer mTempBuffer;
/**
* @Constructor of <code>AsyncResultProgram</code>.
*
* @param id The resource identifier of the command
* @param asyncResultListener The partial result listener
* @param args Arguments of the command (will be formatted with the arguments from
* the command definition)
* @throws InvalidCommandDefinitionException If the command has an invalid definition
*/
public AsyncResultProgram(
String id, AsyncResultListener asyncResultListener, String... args)
throws InvalidCommandDefinitionException {
this(id, true, asyncResultListener, args);
}
/**
* @Constructor of <code>AsyncResultProgram</code>.
*
* @param id The resource identifier of the command
* @param prepare Indicates if the argument must be prepared
* @param asyncResultListener The partial result listener
* @param args Arguments of the command (will be formatted with the arguments from
* the command definition)
* @throws InvalidCommandDefinitionException If the command has an invalid definition
*/
public AsyncResultProgram(
String id, boolean prepare, AsyncResultListener asyncResultListener, String... args)
throws InvalidCommandDefinitionException {
super(id, prepare, args);
this.mAsyncResultListener = asyncResultListener;
this.mPartialData = Collections.synchronizedList(new ArrayList<String>());
this.mPartialDataType = Collections.synchronizedList(new ArrayList<Byte>());
this.mTempBuffer = new StringBuffer();
this.mOnCancelListener = null;
this.mOnEndListener = null;
this.mCancelled = false;
}
/**
* Method that communicates that a new partial result parse will start.
* @hide
*/
public final void onRequestStartParsePartialResult() {
this.mWorkerThread = new AsyncResultProgramThread(this.mSync);
this.mWorkerThread.start();
//Notify start to command class
this.onStartParsePartialResult();
//If a listener is defined, then send the start event
if (getAsyncResultListener() != null) {
getAsyncResultListener().onAsyncStart();
}
}
/**
* Method that communicates that partial result is ended and no new result
* will be received.
*
* @param cancelled If the program was cancelled
* @hide
*/
public final void onRequestEndParsePartialResult(boolean cancelled) {
synchronized (this.mSync) {
this.mWorkerThread.mAlive = false;
this.mSync.notify();
}
synchronized (this.mTerminateSync) {
try {
this.mSync.wait();
} catch (Exception e) {
/**NON BLOCK**/
}
try {
if (this.mWorkerThread.isAlive()) {
this.mWorkerThread.interrupt();
}
} catch (Exception e) {
/**NON BLOCK**/
}
}
//Notify end to command class
this.onEndParsePartialResult(cancelled);
//If a listener is defined, then send the start event
if (getAsyncResultListener() != null) {
getAsyncResultListener().onAsyncEnd(cancelled);
}
}
/**
* Method that communicates the exit code of the program
*
* @param exitCode The exit code of the program
* @hide
*/
public final void onRequestExitCode(int exitCode) {
//If a listener is defined, then send the start event
if (getAsyncResultListener() != null) {
getAsyncResultListener().onAsyncExitCode(exitCode);
}
}
/**
* Method that parse the result of a program invocation.
*
* @param partialIn A partial standard input buffer (incremental buffer)
* @hide
*/
public final void onRequestParsePartialResult(String partialIn) {
synchronized (this.mSync) {
String data = partialIn;
String rest = ""; //$NON-NLS-1$
if (parseOnlyCompleteLines()) {
int pos = partialIn.lastIndexOf(FileHelper.NEWLINE);
if (pos == -1) {
//Save partial data
this.mTempBuffer.append(partialIn);
return;
}
//Retrieve the data
data = this.mTempBuffer.append(partialIn.substring(0, pos + 1)).toString();
rest = partialIn.substring(pos + 1);
}
this.mPartialDataType.add(STDIN);
this.mPartialData.add(data);
this.mTempBuffer = new StringBuffer(rest);
this.mSync.notify();
}
}
/**
* Method that parse the error result of a program invocation.
*
* @param partialErr A partial standard err buffer (incremental buffer)
* @hide
*/
public final void parsePartialErrResult(String partialErr) {
synchronized (this.mSync) {
String data = partialErr;
String rest = ""; //$NON-NLS-1$
if (parseOnlyCompleteLines()) {
int pos = partialErr.lastIndexOf(FileHelper.NEWLINE);
if (pos == -1) {
//Save partial data
this.mTempBuffer.append(partialErr);
return;
}
//Retrieve the data
data = this.mTempBuffer.append(partialErr.substring(0, pos + 1)).toString();
rest = partialErr.substring(pos + 1);
}
this.mPartialDataType.add(STDERR);
this.mPartialData.add(data);
this.mTempBuffer = new StringBuffer(rest);
this.mSync.notify();
}
}
/**
* Method that returns if the <code>onParsePartialResult</code> method will
* be called only complete lines are filled.
*
* @return boolean if the <code>onParsePartialResult</code> method will
* be called only complete lines are filled
*/
@SuppressWarnings("static-method")
public boolean parseOnlyCompleteLines() {
return true;
}
/**
* {@inheritDoc}
*/
@Override
public AsyncResultListener getAsyncResultListener() {
return this.mAsyncResultListener;
}
/**
* {@inheritDoc}
*/
@Override
public final boolean isCancelled() {
return this.mCancelled;
}
/**
* {@inheritDoc}
*/
@Override
public final boolean cancel() {
//Is't cancellable by definition?
if (!isCancellable()) {
return false;
}
//Stop the thread
synchronized (this.mSync) {
this.mWorkerThread.mAlive = false;
this.mSync.notify();
}
//Notify cancellation
if (this.mOnCancelListener != null) {
this.mCancelled = this.mOnCancelListener.onCancel();
return this.mCancelled;
}
return false;
}
/**
* {@inheritDoc}
*/
@Override
public final boolean end() {
// Internally this method do the same things that cancel method, but invokes
// onEnd instead of onCancel
//Is't cancellable by definition?
if (!isCancellable()) {
return false;
}
//Stop the thread
synchronized (this.mSync) {
this.mWorkerThread.mAlive = false;
this.mSync.notify();
}
//Notify ending
SIGNAL signal = onRequestEnd();
if (this.mOnEndListener != null) {
if (signal == null) {
this.mCancelled = this.mOnEndListener.onEnd();
} else {
this.mCancelled = this.mOnEndListener.onSendSignal(signal);
}
return this.mCancelled;
}
return false;
}
/**
* {@inheritDoc}
*/
@Override
public final void setOnCancelListener(OnCancelListener onCancelListener) {
this.mOnCancelListener = onCancelListener;
}
/**
* {@inheritDoc}
*/
@Override
public final void setOnEndListener(OnEndListener onEndListener) {
this.mOnEndListener = onEndListener;
}
/**
* {@inheritDoc}
*/
@Override
public boolean isCancellable() {
//By defect an asynchronous command is cancellable
return true;
}
/**
* Method that returns if the command is expected to finalize by it self, or needs
* a call to end method.
*
* @return boolean If the command is expected to finalize by it self.
*/
@SuppressWarnings("static-method")
public boolean isExpectEnd() {
return true;
}
/**
* An internal class for process partial results sequentially in a
* secure way.
*/
private class AsyncResultProgramThread extends Thread {
boolean mAlive = true;
private final Object mSyncObj;
/**
* Constructor of <code>AsyncResultProgramThread</code>.
*
* @param sync The synchronized object
*/
AsyncResultProgramThread(Object sync) {
super();
this.mSyncObj = sync;
}
/**
* {@inheritDoc}
*/
@Override
public void run() {
try {
this.mAlive = true;
while (this.mAlive) {
synchronized (this.mSyncObj) {
this.mSyncObj.wait();
while (AsyncResultProgram.this.mPartialData.size() > 0) {
if (!this.mAlive) {
return;
}
Byte type = AsyncResultProgram.this.mPartialDataType.remove(0);
String data = AsyncResultProgram.this.mPartialData.remove(0);
try {
if (type.compareTo(STDIN) == 0) {
AsyncResultProgram.this.onParsePartialResult(data);
} else {
AsyncResultProgram.this.onParseErrorPartialResult(data);
}
} catch (Throwable ex) {
/**NON BLOCK**/
}
}
}
}
} catch (Exception e) {
/**NON BLOCK**/
} finally {
this.mAlive = false;
synchronized (AsyncResultProgram.this.mTerminateSync) {
AsyncResultProgram.this.mTerminateSync.notify();
}
}
}
}
}