| /* |
| * Copyright (C) 2011 The Android Open Source 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 android.filterfw.core; |
| |
| import android.compat.annotation.UnsupportedAppUsage; |
| import android.filterfw.format.ObjectFormat; |
| import android.filterfw.io.GraphIOException; |
| import android.filterfw.io.TextGraphReader; |
| import android.util.Log; |
| |
| import java.io.Serializable; |
| import java.lang.annotation.Annotation; |
| import java.lang.reflect.Field; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| |
| /** |
| * @hide |
| */ |
| public abstract class Filter { |
| |
| static final int STATUS_PREINIT = 0; |
| static final int STATUS_UNPREPARED = 1; |
| static final int STATUS_PREPARED = 2; |
| static final int STATUS_PROCESSING = 3; |
| static final int STATUS_SLEEPING = 4; |
| static final int STATUS_FINISHED = 5; |
| static final int STATUS_ERROR = 6; |
| static final int STATUS_RELEASED = 7; |
| |
| private String mName; |
| |
| private int mInputCount = -1; |
| private int mOutputCount = -1; |
| |
| private HashMap<String, InputPort> mInputPorts; |
| private HashMap<String, OutputPort> mOutputPorts; |
| |
| private HashSet<Frame> mFramesToRelease; |
| private HashMap<String, Frame> mFramesToSet; |
| |
| private int mStatus = 0; |
| private boolean mIsOpen = false; |
| private int mSleepDelay; |
| |
| private long mCurrentTimestamp; |
| |
| private boolean mLogVerbose; |
| private static final String TAG = "Filter"; |
| |
| @UnsupportedAppUsage |
| public Filter(String name) { |
| mName = name; |
| mFramesToRelease = new HashSet<Frame>(); |
| mFramesToSet = new HashMap<String, Frame>(); |
| mStatus = STATUS_PREINIT; |
| |
| mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE); |
| } |
| |
| /** Tests to see if a given filter is installed on the system. Requires |
| * full filter package name, including filterpack. |
| */ |
| @UnsupportedAppUsage |
| public static final boolean isAvailable(String filterName) { |
| ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); |
| Class filterClass; |
| // First see if a class of that name exists |
| try { |
| filterClass = contextClassLoader.loadClass(filterName); |
| } catch (ClassNotFoundException e) { |
| return false; |
| } |
| // Then make sure it's a subclass of Filter. |
| try { |
| filterClass.asSubclass(Filter.class); |
| } catch (ClassCastException e) { |
| return false; |
| } |
| return true; |
| } |
| |
| public final void initWithValueMap(KeyValueMap valueMap) { |
| // Initialization |
| initFinalPorts(valueMap); |
| |
| // Setup remaining ports |
| initRemainingPorts(valueMap); |
| |
| // This indicates that final ports can no longer be set |
| mStatus = STATUS_UNPREPARED; |
| } |
| |
| public final void initWithAssignmentString(String assignments) { |
| try { |
| KeyValueMap valueMap = new TextGraphReader().readKeyValueAssignments(assignments); |
| initWithValueMap(valueMap); |
| } catch (GraphIOException e) { |
| throw new IllegalArgumentException(e.getMessage()); |
| } |
| } |
| |
| public final void initWithAssignmentList(Object... keyValues) { |
| KeyValueMap valueMap = new KeyValueMap(); |
| valueMap.setKeyValues(keyValues); |
| initWithValueMap(valueMap); |
| } |
| |
| public final void init() throws ProtocolException { |
| KeyValueMap valueMap = new KeyValueMap(); |
| initWithValueMap(valueMap); |
| } |
| |
| public String getFilterClassName() { |
| return getClass().getSimpleName(); |
| } |
| |
| public final String getName() { |
| return mName; |
| } |
| |
| public boolean isOpen() { |
| return mIsOpen; |
| } |
| |
| public void setInputFrame(String inputName, Frame frame) { |
| FilterPort port = getInputPort(inputName); |
| if (!port.isOpen()) { |
| port.open(); |
| } |
| port.setFrame(frame); |
| } |
| |
| @UnsupportedAppUsage |
| public final void setInputValue(String inputName, Object value) { |
| setInputFrame(inputName, wrapInputValue(inputName, value)); |
| } |
| |
| protected void prepare(FilterContext context) { |
| } |
| |
| protected void parametersUpdated(Set<String> updated) { |
| } |
| |
| protected void delayNextProcess(int millisecs) { |
| mSleepDelay = millisecs; |
| mStatus = STATUS_SLEEPING; |
| } |
| |
| public abstract void setupPorts(); |
| |
| public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) { |
| return null; |
| } |
| |
| public final FrameFormat getInputFormat(String portName) { |
| InputPort inputPort = getInputPort(portName); |
| return inputPort.getSourceFormat(); |
| } |
| |
| public void open(FilterContext context) { |
| } |
| |
| public abstract void process(FilterContext context); |
| |
| public final int getSleepDelay() { |
| return 250; |
| } |
| |
| public void close(FilterContext context) { |
| } |
| |
| public void tearDown(FilterContext context) { |
| } |
| |
| public final int getNumberOfConnectedInputs() { |
| int c = 0; |
| for (InputPort inputPort : mInputPorts.values()) { |
| if (inputPort.isConnected()) { |
| ++c; |
| } |
| } |
| return c; |
| } |
| |
| public final int getNumberOfConnectedOutputs() { |
| int c = 0; |
| for (OutputPort outputPort : mOutputPorts.values()) { |
| if (outputPort.isConnected()) { |
| ++c; |
| } |
| } |
| return c; |
| } |
| |
| public final int getNumberOfInputs() { |
| return mOutputPorts == null ? 0 : mInputPorts.size(); |
| } |
| |
| public final int getNumberOfOutputs() { |
| return mInputPorts == null ? 0 : mOutputPorts.size(); |
| } |
| |
| public final InputPort getInputPort(String portName) { |
| if (mInputPorts == null) { |
| throw new NullPointerException("Attempting to access input port '" + portName |
| + "' of " + this + " before Filter has been initialized!"); |
| } |
| InputPort result = mInputPorts.get(portName); |
| if (result == null) { |
| throw new IllegalArgumentException("Unknown input port '" + portName + "' on filter " |
| + this + "!"); |
| } |
| return result; |
| } |
| |
| public final OutputPort getOutputPort(String portName) { |
| if (mInputPorts == null) { |
| throw new NullPointerException("Attempting to access output port '" + portName |
| + "' of " + this + " before Filter has been initialized!"); |
| } |
| OutputPort result = mOutputPorts.get(portName); |
| if (result == null) { |
| throw new IllegalArgumentException("Unknown output port '" + portName + "' on filter " |
| + this + "!"); |
| } |
| return result; |
| } |
| |
| protected final void pushOutput(String name, Frame frame) { |
| if (frame.getTimestamp() == Frame.TIMESTAMP_NOT_SET) { |
| if (mLogVerbose) Log.v(TAG, "Default-setting output Frame timestamp on port " + name + " to " + mCurrentTimestamp); |
| frame.setTimestamp(mCurrentTimestamp); |
| } |
| getOutputPort(name).pushFrame(frame); |
| } |
| |
| protected final Frame pullInput(String name) { |
| Frame result = getInputPort(name).pullFrame(); |
| if (mCurrentTimestamp == Frame.TIMESTAMP_UNKNOWN) { |
| mCurrentTimestamp = result.getTimestamp(); |
| if (mLogVerbose) Log.v(TAG, "Default-setting current timestamp from input port " + name + " to " + mCurrentTimestamp); |
| } |
| // As result is retained, we add it to the release pool here |
| mFramesToRelease.add(result); |
| |
| return result; |
| } |
| |
| public void fieldPortValueUpdated(String name, FilterContext context) { |
| } |
| |
| /** |
| * Transfers any frame from an input port to its destination. This is useful to force a |
| * transfer from a FieldPort or ProgramPort to its connected target (field or program variable). |
| */ |
| protected void transferInputPortFrame(String name, FilterContext context) { |
| getInputPort(name).transfer(context); |
| } |
| |
| /** |
| * Assigns all program variables to the ports they are connected to. Call this after |
| * constructing a Program instance with attached ProgramPorts. |
| */ |
| protected void initProgramInputs(Program program, FilterContext context) { |
| if (program != null) { |
| for (InputPort inputPort : mInputPorts.values()) { |
| if (inputPort.getTarget() == program) { |
| inputPort.transfer(context); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Adds an input port to the filter. You should call this from within setupPorts, if your |
| * filter has input ports. No type-checking is performed on the input. If you would like to |
| * check against a type mask, use |
| * {@link #addMaskedInputPort(String, FrameFormat) addMaskedInputPort} instead. |
| * |
| * @param name the name of the input port |
| */ |
| protected void addInputPort(String name) { |
| addMaskedInputPort(name, null); |
| } |
| |
| /** |
| * Adds an input port to the filter. You should call this from within setupPorts, if your |
| * filter has input ports. When type-checking is performed, the input format is |
| * checked against the provided format mask. An exception is thrown in case of a conflict. |
| * |
| * @param name the name of the input port |
| * @param formatMask a format mask, which filters the allowable input types |
| */ |
| protected void addMaskedInputPort(String name, FrameFormat formatMask) { |
| InputPort port = new StreamPort(this, name); |
| if (mLogVerbose) Log.v(TAG, "Filter " + this + " adding " + port); |
| mInputPorts.put(name, port); |
| port.setPortFormat(formatMask); |
| } |
| |
| /** |
| * Adds an output port to the filter with a fixed output format. You should call this from |
| * within setupPorts, if your filter has output ports. You cannot use this method, if your |
| * output format depends on the input format (e.g. in a pass-through filter). In this case, use |
| * {@link #addOutputBasedOnInput(String, String) addOutputBasedOnInput} instead. |
| * |
| * @param name the name of the output port |
| * @param format the fixed output format of this port |
| */ |
| protected void addOutputPort(String name, FrameFormat format) { |
| OutputPort port = new OutputPort(this, name); |
| if (mLogVerbose) Log.v(TAG, "Filter " + this + " adding " + port); |
| port.setPortFormat(format); |
| mOutputPorts.put(name, port); |
| } |
| |
| /** |
| * Adds an output port to the filter. You should call this from within setupPorts, if your |
| * filter has output ports. Using this method indicates that the output format for this |
| * particular port, depends on the format of an input port. You MUST also override |
| * {@link #getOutputFormat(String, FrameFormat) getOutputFormat} to specify what format your |
| * filter will output for a given input. If the output format of your filter port does not |
| * depend on the input, use {@link #addOutputPort(String, FrameFormat) addOutputPort} instead. |
| * |
| * @param outputName the name of the output port |
| * @param inputName the name of the input port, that this output depends on |
| */ |
| protected void addOutputBasedOnInput(String outputName, String inputName) { |
| OutputPort port = new OutputPort(this, outputName); |
| if (mLogVerbose) Log.v(TAG, "Filter " + this + " adding " + port); |
| port.setBasePort(getInputPort(inputName)); |
| mOutputPorts.put(outputName, port); |
| } |
| |
| protected void addFieldPort(String name, |
| Field field, |
| boolean hasDefault, |
| boolean isFinal) { |
| // Make sure field is accessible |
| field.setAccessible(true); |
| |
| // Create port for this input |
| InputPort fieldPort = isFinal |
| ? new FinalPort(this, name, field, hasDefault) |
| : new FieldPort(this, name, field, hasDefault); |
| |
| // Create format for this input |
| if (mLogVerbose) Log.v(TAG, "Filter " + this + " adding " + fieldPort); |
| MutableFrameFormat format = ObjectFormat.fromClass(field.getType(), |
| FrameFormat.TARGET_SIMPLE); |
| fieldPort.setPortFormat(format); |
| |
| // Add port |
| mInputPorts.put(name, fieldPort); |
| } |
| |
| protected void addProgramPort(String name, |
| String varName, |
| Field field, |
| Class varType, |
| boolean hasDefault) { |
| // Make sure field is accessible |
| field.setAccessible(true); |
| |
| // Create port for this input |
| InputPort programPort = new ProgramPort(this, name, varName, field, hasDefault); |
| |
| // Create format for this input |
| if (mLogVerbose) Log.v(TAG, "Filter " + this + " adding " + programPort); |
| MutableFrameFormat format = ObjectFormat.fromClass(varType, |
| FrameFormat.TARGET_SIMPLE); |
| programPort.setPortFormat(format); |
| |
| // Add port |
| mInputPorts.put(name, programPort); |
| } |
| |
| protected void closeOutputPort(String name) { |
| getOutputPort(name).close(); |
| } |
| |
| /** |
| * Specifies whether the filter should not be scheduled until a frame is available on that |
| * input port. Note, that setting this to false, does not block a new frame from coming in |
| * (though there is no necessity to pull that frame for processing). |
| * @param portName the name of the input port. |
| * @param waits true, if the filter should wait for a frame on this port. |
| */ |
| protected void setWaitsOnInputPort(String portName, boolean waits) { |
| getInputPort(portName).setBlocking(waits); |
| } |
| |
| /** |
| * Specifies whether the filter should not be scheduled until the output port is free, i.e. |
| * there is no frame waiting on that output. |
| * @param portName the name of the output port. |
| * @param waits true, if the filter should wait for the port to become free. |
| */ |
| protected void setWaitsOnOutputPort(String portName, boolean waits) { |
| getOutputPort(portName).setBlocking(waits); |
| } |
| |
| public String toString() { |
| return "'" + getName() + "' (" + getFilterClassName() + ")"; |
| } |
| |
| // Core internal methods /////////////////////////////////////////////////////////////////////// |
| final Collection<InputPort> getInputPorts() { |
| return mInputPorts.values(); |
| } |
| |
| final Collection<OutputPort> getOutputPorts() { |
| return mOutputPorts.values(); |
| } |
| |
| final synchronized int getStatus() { |
| return mStatus; |
| } |
| |
| final synchronized void unsetStatus(int flag) { |
| mStatus &= ~flag; |
| } |
| |
| final synchronized void performOpen(FilterContext context) { |
| if (!mIsOpen) { |
| if (mStatus == STATUS_UNPREPARED) { |
| if (mLogVerbose) Log.v(TAG, "Preparing " + this); |
| prepare(context); |
| mStatus = STATUS_PREPARED; |
| } |
| if (mStatus == STATUS_PREPARED) { |
| if (mLogVerbose) Log.v(TAG, "Opening " + this); |
| open(context); |
| mStatus = STATUS_PROCESSING; |
| } |
| if (mStatus != STATUS_PROCESSING) { |
| throw new RuntimeException("Filter " + this + " was brought into invalid state during " |
| + "opening (state: " + mStatus + ")!"); |
| } |
| mIsOpen = true; |
| } |
| } |
| |
| final synchronized void performProcess(FilterContext context) { |
| if (mStatus == STATUS_RELEASED) { |
| throw new RuntimeException("Filter " + this + " is already torn down!"); |
| } |
| transferInputFrames(context); |
| if (mStatus < STATUS_PROCESSING) { |
| performOpen(context); |
| } |
| if (mLogVerbose) Log.v(TAG, "Processing " + this); |
| mCurrentTimestamp = Frame.TIMESTAMP_UNKNOWN; |
| process(context); |
| releasePulledFrames(context); |
| if (filterMustClose()) { |
| performClose(context); |
| } |
| } |
| |
| final synchronized void performClose(FilterContext context) { |
| if (mIsOpen) { |
| if (mLogVerbose) Log.v(TAG, "Closing " + this); |
| mIsOpen = false; |
| mStatus = STATUS_PREPARED; |
| close(context); |
| closePorts(); |
| } |
| } |
| |
| final synchronized void performTearDown(FilterContext context) { |
| performClose(context); |
| if (mStatus != STATUS_RELEASED) { |
| tearDown(context); |
| mStatus = STATUS_RELEASED; |
| } |
| } |
| |
| synchronized final boolean canProcess() { |
| if (mLogVerbose) Log.v(TAG, "Checking if can process: " + this + " (" + mStatus + ")."); |
| if (mStatus <= STATUS_PROCESSING) { |
| return inputConditionsMet() && outputConditionsMet(); |
| } else { |
| return false; |
| } |
| } |
| |
| final void openOutputs() { |
| if (mLogVerbose) Log.v(TAG, "Opening all output ports on " + this + "!"); |
| for (OutputPort outputPort : mOutputPorts.values()) { |
| if (!outputPort.isOpen()) { |
| outputPort.open(); |
| } |
| } |
| } |
| |
| final void clearInputs() { |
| for (InputPort inputPort : mInputPorts.values()) { |
| inputPort.clear(); |
| } |
| } |
| |
| final void clearOutputs() { |
| for (OutputPort outputPort : mOutputPorts.values()) { |
| outputPort.clear(); |
| } |
| } |
| |
| final void notifyFieldPortValueUpdated(String name, FilterContext context) { |
| if (mStatus == STATUS_PROCESSING || mStatus == STATUS_PREPARED) { |
| fieldPortValueUpdated(name, context); |
| } |
| } |
| |
| final synchronized void pushInputFrame(String inputName, Frame frame) { |
| FilterPort port = getInputPort(inputName); |
| if (!port.isOpen()) { |
| port.open(); |
| } |
| port.pushFrame(frame); |
| } |
| |
| final synchronized void pushInputValue(String inputName, Object value) { |
| pushInputFrame(inputName, wrapInputValue(inputName, value)); |
| } |
| |
| // Filter internal methods ///////////////////////////////////////////////////////////////////// |
| private final void initFinalPorts(KeyValueMap values) { |
| mInputPorts = new HashMap<String, InputPort>(); |
| mOutputPorts = new HashMap<String, OutputPort>(); |
| addAndSetFinalPorts(values); |
| } |
| |
| private final void initRemainingPorts(KeyValueMap values) { |
| addAnnotatedPorts(); |
| setupPorts(); // TODO: rename to addFilterPorts() ? |
| setInitialInputValues(values); |
| } |
| |
| private final void addAndSetFinalPorts(KeyValueMap values) { |
| Class filterClass = getClass(); |
| Annotation annotation; |
| for (Field field : filterClass.getDeclaredFields()) { |
| if ((annotation = field.getAnnotation(GenerateFinalPort.class)) != null) { |
| GenerateFinalPort generator = (GenerateFinalPort)annotation; |
| String name = generator.name().isEmpty() ? field.getName() : generator.name(); |
| boolean hasDefault = generator.hasDefault(); |
| addFieldPort(name, field, hasDefault, true); |
| if (values.containsKey(name)) { |
| setImmediateInputValue(name, values.get(name)); |
| values.remove(name); |
| } else if (!generator.hasDefault()) { |
| throw new RuntimeException("No value specified for final input port '" |
| + name + "' of filter " + this + "!"); |
| } |
| } |
| } |
| } |
| |
| private final void addAnnotatedPorts() { |
| Class filterClass = getClass(); |
| Annotation annotation; |
| for (Field field : filterClass.getDeclaredFields()) { |
| if ((annotation = field.getAnnotation(GenerateFieldPort.class)) != null) { |
| GenerateFieldPort generator = (GenerateFieldPort)annotation; |
| addFieldGenerator(generator, field); |
| } else if ((annotation = field.getAnnotation(GenerateProgramPort.class)) != null) { |
| GenerateProgramPort generator = (GenerateProgramPort)annotation; |
| addProgramGenerator(generator, field); |
| } else if ((annotation = field.getAnnotation(GenerateProgramPorts.class)) != null) { |
| GenerateProgramPorts generators = (GenerateProgramPorts)annotation; |
| for (GenerateProgramPort generator : generators.value()) { |
| addProgramGenerator(generator, field); |
| } |
| } |
| } |
| } |
| |
| private final void addFieldGenerator(GenerateFieldPort generator, Field field) { |
| String name = generator.name().isEmpty() ? field.getName() : generator.name(); |
| boolean hasDefault = generator.hasDefault(); |
| addFieldPort(name, field, hasDefault, false); |
| } |
| |
| private final void addProgramGenerator(GenerateProgramPort generator, Field field) { |
| String name = generator.name(); |
| String varName = generator.variableName().isEmpty() ? name |
| : generator.variableName(); |
| Class varType = generator.type(); |
| boolean hasDefault = generator.hasDefault(); |
| addProgramPort(name, varName, field, varType, hasDefault); |
| } |
| |
| private final void setInitialInputValues(KeyValueMap values) { |
| for (Entry<String, Object> entry : values.entrySet()) { |
| setInputValue(entry.getKey(), entry.getValue()); |
| } |
| } |
| |
| private final void setImmediateInputValue(String name, Object value) { |
| if (mLogVerbose) Log.v(TAG, "Setting immediate value " + value + " for port " + name + "!"); |
| FilterPort port = getInputPort(name); |
| port.open(); |
| port.setFrame(SimpleFrame.wrapObject(value, null)); |
| } |
| |
| private final void transferInputFrames(FilterContext context) { |
| for (InputPort inputPort : mInputPorts.values()) { |
| inputPort.transfer(context); |
| } |
| } |
| |
| private final Frame wrapInputValue(String inputName, Object value) { |
| MutableFrameFormat inputFormat = ObjectFormat.fromObject(value, FrameFormat.TARGET_SIMPLE); |
| if (value == null) { |
| // If the value is null, the format cannot guess the class, so we adjust it to the |
| // class of the input port here |
| FrameFormat portFormat = getInputPort(inputName).getPortFormat(); |
| Class portClass = (portFormat == null) ? null : portFormat.getObjectClass(); |
| inputFormat.setObjectClass(portClass); |
| } |
| |
| // Serialize if serializable, and type is not an immutable primitive. |
| boolean shouldSerialize = !(value instanceof Number) |
| && !(value instanceof Boolean) |
| && !(value instanceof String) |
| && value instanceof Serializable; |
| |
| // Create frame wrapper |
| Frame frame = shouldSerialize |
| ? new SerializedFrame(inputFormat, null) |
| : new SimpleFrame(inputFormat, null); |
| frame.setObjectValue(value); |
| return frame; |
| } |
| |
| private final void releasePulledFrames(FilterContext context) { |
| for (Frame frame : mFramesToRelease) { |
| context.getFrameManager().releaseFrame(frame); |
| } |
| mFramesToRelease.clear(); |
| } |
| |
| private final boolean inputConditionsMet() { |
| for (FilterPort port : mInputPorts.values()) { |
| if (!port.isReady()) { |
| if (mLogVerbose) Log.v(TAG, "Input condition not met: " + port + "!"); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private final boolean outputConditionsMet() { |
| for (FilterPort port : mOutputPorts.values()) { |
| if (!port.isReady()) { |
| if (mLogVerbose) Log.v(TAG, "Output condition not met: " + port + "!"); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private final void closePorts() { |
| if (mLogVerbose) Log.v(TAG, "Closing all ports on " + this + "!"); |
| for (InputPort inputPort : mInputPorts.values()) { |
| inputPort.close(); |
| } |
| for (OutputPort outputPort : mOutputPorts.values()) { |
| outputPort.close(); |
| } |
| } |
| |
| private final boolean filterMustClose() { |
| for (InputPort inputPort : mInputPorts.values()) { |
| if (inputPort.filterMustClose()) { |
| if (mLogVerbose) Log.v(TAG, "Filter " + this + " must close due to port " + inputPort); |
| return true; |
| } |
| } |
| for (OutputPort outputPort : mOutputPorts.values()) { |
| if (outputPort.filterMustClose()) { |
| if (mLogVerbose) Log.v(TAG, "Filter " + this + " must close due to port " + outputPort); |
| return true; |
| } |
| } |
| return false; |
| } |
| } |