2 * Copyright (C) 2011 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 package android.filterfw.core;
20 import android.filterfw.core.FilterContext;
21 import android.filterfw.core.FilterPort;
22 import android.filterfw.core.KeyValueMap;
23 import android.filterfw.io.TextGraphReader;
24 import android.filterfw.io.GraphIOException;
25 import android.filterfw.format.ObjectFormat;
26 import android.util.Log;
28 import java.lang.annotation.Annotation;
29 import java.lang.reflect.Field;
30 import java.lang.Thread;
32 import java.util.Collection;
33 import java.util.HashMap;
34 import java.util.HashSet;
35 import java.util.Map.Entry;
36 import java.util.LinkedList;
42 public abstract class Filter {
44 static final int STATUS_PREINIT = 0;
45 static final int STATUS_UNPREPARED = 1;
46 static final int STATUS_PREPARED = 2;
47 static final int STATUS_PROCESSING = 3;
48 static final int STATUS_SLEEPING = 4;
49 static final int STATUS_FINISHED = 5;
50 static final int STATUS_ERROR = 6;
51 static final int STATUS_RELEASED = 7;
55 private int mInputCount = -1;
56 private int mOutputCount = -1;
58 private HashMap<String, InputPort> mInputPorts;
59 private HashMap<String, OutputPort> mOutputPorts;
61 private HashSet<Frame> mFramesToRelease;
62 private HashMap<String, Frame> mFramesToSet;
64 private int mStatus = 0;
65 private boolean mIsOpen = false;
66 private int mSleepDelay;
68 private long mCurrentTimestamp;
70 private boolean mLogVerbose;
71 private static final String TAG = "Filter";
73 public Filter(String name) {
75 mFramesToRelease = new HashSet<Frame>();
76 mFramesToSet = new HashMap<String, Frame>();
77 mStatus = STATUS_PREINIT;
79 mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
82 /** Tests to see if a given filter is installed on the system. Requires
83 * full filter package name, including filterpack.
85 public static final boolean isAvailable(String filterName) {
86 ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
88 // First see if a class of that name exists
90 filterClass = contextClassLoader.loadClass(filterName);
91 } catch (ClassNotFoundException e) {
94 // Then make sure it's a subclass of Filter.
96 filterClass.asSubclass(Filter.class);
97 } catch (ClassCastException e) {
103 public final void initWithValueMap(KeyValueMap valueMap) {
105 initFinalPorts(valueMap);
107 // Setup remaining ports
108 initRemainingPorts(valueMap);
110 // This indicates that final ports can no longer be set
111 mStatus = STATUS_UNPREPARED;
114 public final void initWithAssignmentString(String assignments) {
116 KeyValueMap valueMap = new TextGraphReader().readKeyValueAssignments(assignments);
117 initWithValueMap(valueMap);
118 } catch (GraphIOException e) {
119 throw new IllegalArgumentException(e.getMessage());
123 public final void initWithAssignmentList(Object... keyValues) {
124 KeyValueMap valueMap = new KeyValueMap();
125 valueMap.setKeyValues(keyValues);
126 initWithValueMap(valueMap);
129 public final void init() throws ProtocolException {
130 KeyValueMap valueMap = new KeyValueMap();
131 initWithValueMap(valueMap);
134 public String getFilterClassName() {
135 return getClass().getSimpleName();
138 public final String getName() {
142 public boolean isOpen() {
146 public synchronized void setInputFrame(String inputName, Frame frame) {
147 FilterPort port = getInputPort(inputName);
148 if (!port.isOpen()) {
151 port.setFrame(frame);
154 public final void setInputValue(String inputName, Object value) {
155 // Wrap the value in a Java frame
156 MutableFrameFormat inputFormat = ObjectFormat.fromObject(value, FrameFormat.TARGET_JAVA);
158 // If the value is null, the format cannot guess the class, so we adjust it to the
159 // class of the input port here
160 FrameFormat portFormat = getInputPort(inputName).getPortFormat();
161 Class portClass = (portFormat == null) ? null : portFormat.getObjectClass();
162 inputFormat.setObjectClass(portClass);
164 JavaFrame frame = new JavaFrame(inputFormat, null);
165 frame.setObjectValue(value);
166 setInputFrame(inputName, frame);
169 protected void prepare(FilterContext context) {
172 protected void parametersUpdated(Set<String> updated) {
175 protected void delayNextProcess(int millisecs) {
176 mSleepDelay = millisecs;
177 mStatus = STATUS_SLEEPING;
180 public abstract void setupPorts();
182 public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) {
186 public final FrameFormat getInputFormat(String portName) {
187 InputPort inputPort = getInputPort(portName);
188 return inputPort.getSourceFormat();
191 public void open(FilterContext context) {
194 public abstract void process(FilterContext context);
196 public final int getSleepDelay() {
200 public void close(FilterContext context) {
203 public void tearDown(FilterContext context) {
206 public final int getNumberOfConnectedInputs() {
208 for (InputPort inputPort : mInputPorts.values()) {
209 if (inputPort.isConnected()) {
216 public final int getNumberOfConnectedOutputs() {
218 for (OutputPort outputPort : mOutputPorts.values()) {
219 if (outputPort.isConnected()) {
226 public final int getNumberOfInputs() {
227 return mOutputPorts == null ? 0 : mInputPorts.size();
230 public final int getNumberOfOutputs() {
231 return mInputPorts == null ? 0 : mOutputPorts.size();
234 public final InputPort getInputPort(String portName) {
235 if (mInputPorts == null) {
236 throw new NullPointerException("Attempting to access input port '" + portName
237 + "' of " + this + " before Filter has been initialized!");
239 InputPort result = mInputPorts.get(portName);
240 if (result == null) {
241 throw new IllegalArgumentException("Unknown input port '" + portName + "' on filter "
247 public final OutputPort getOutputPort(String portName) {
248 if (mInputPorts == null) {
249 throw new NullPointerException("Attempting to access output port '" + portName
250 + "' of " + this + " before Filter has been initialized!");
252 OutputPort result = mOutputPorts.get(portName);
253 if (result == null) {
254 throw new IllegalArgumentException("Unknown output port '" + portName + "' on filter "
260 protected final void pushOutput(String name, Frame frame) {
261 if (frame.getTimestamp() == Frame.TIMESTAMP_NOT_SET) {
262 if (mLogVerbose) Log.v(TAG, "Default-setting output Frame timestamp on port " + name + " to " + mCurrentTimestamp);
263 frame.setTimestamp(mCurrentTimestamp);
265 getOutputPort(name).pushFrame(frame);
268 protected final Frame pullInput(String name) {
269 Frame result = getInputPort(name).pullFrame();
270 if (mCurrentTimestamp == Frame.TIMESTAMP_UNKNOWN) {
271 if (mLogVerbose) Log.v(TAG, "Default-setting current timestamp from input port " + name + " to " + mCurrentTimestamp);
273 mCurrentTimestamp = result.getTimestamp();
275 // As result is retained, we add it to the release pool here
276 mFramesToRelease.add(result);
281 public void fieldPortValueUpdated(String name, FilterContext context) {
285 * Transfers any frame from an input port to its destination. This is useful to force a
286 * transfer from a FieldPort or ProgramPort to its connected target (field or program variable).
288 protected void transferInputPortFrame(String name, FilterContext context) {
289 getInputPort(name).transfer(context);
293 * Adds an input port to the filter. You should call this from within setupPorts, if your
294 * filter has input ports. No type-checking is performed on the input. If you would like to
295 * check against a type mask, use
296 * {@link #addMaskedInputPort(String, FrameFormat) addMaskedInputPort} instead.
298 * @param name the name of the input port
300 protected void addInputPort(String name) {
301 addMaskedInputPort(name, null);
305 * Adds an input port to the filter. You should call this from within setupPorts, if your
306 * filter has input ports. When type-checking is performed, the input format is
307 * checked against the provided format mask. An exception is thrown in case of a conflict.
309 * @param name the name of the input port
310 * @param formatMask a format mask, which filters the allowable input types
312 protected void addMaskedInputPort(String name, FrameFormat formatMask) {
313 InputPort port = new StreamPort(this, name);
314 if (mLogVerbose) Log.v(TAG, "Filter " + this + " adding " + port);
315 mInputPorts.put(name, port);
316 port.setPortFormat(formatMask);
320 * Adds an output port to the filter with a fixed output format. You should call this from
321 * within setupPorts, if your filter has output ports. You cannot use this method, if your
322 * output format depends on the input format (e.g. in a pass-through filter). In this case, use
323 * {@link #addOutputBasedOnInput(String, String) addOutputBasedOnInput} instead.
325 * @param name the name of the output port
326 * @param format the fixed output format of this port
328 protected void addOutputPort(String name, FrameFormat format) {
329 OutputPort port = new OutputPort(this, name);
330 if (mLogVerbose) Log.v(TAG, "Filter " + this + " adding " + port);
331 port.setPortFormat(format);
332 mOutputPorts.put(name, port);
336 * Adds an output port to the filter. You should call this from within setupPorts, if your
337 * filter has output ports. Using this method indicates that the output format for this
338 * particular port, depends on the format of an input port. You MUST also override
339 * {@link #getOutputFormat(String, FrameFormat) getOutputFormat} to specify what format your
340 * filter will output for a given input. If the output format of your filter port does not
341 * depend on the input, use {@link #addOutputPort(String, FrameFormat) addOutputPort} instead.
343 * @param outputName the name of the output port
344 * @param inputName the name of the input port, that this output depends on
346 protected void addOutputBasedOnInput(String outputName, String inputName) {
347 OutputPort port = new OutputPort(this, outputName);
348 if (mLogVerbose) Log.v(TAG, "Filter " + this + " adding " + port);
349 port.setBasePort(getInputPort(inputName));
350 mOutputPorts.put(outputName, port);
353 protected void addFieldPort(String name,
357 // Make sure field is accessible
358 field.setAccessible(true);
360 // Create port for this input
361 InputPort fieldPort = isFinal
362 ? new FinalPort(this, name, field, hasDefault)
363 : new FieldPort(this, name, field, hasDefault);
365 // Create format for this input
366 if (mLogVerbose) Log.v(TAG, "Filter " + this + " adding " + fieldPort);
367 MutableFrameFormat format = ObjectFormat.fromClass(field.getType(),
368 FrameFormat.TARGET_JAVA);
369 fieldPort.setPortFormat(format);
372 mInputPorts.put(name, fieldPort);
375 protected void addProgramPort(String name,
379 boolean hasDefault) {
380 // Make sure field is accessible
381 field.setAccessible(true);
383 // Create port for this input
384 InputPort programPort = new ProgramPort(this, name, varName, field, hasDefault);
386 // Create format for this input
387 if (mLogVerbose) Log.v(TAG, "Filter " + this + " adding " + programPort);
388 MutableFrameFormat format = ObjectFormat.fromClass(varType,
389 FrameFormat.TARGET_JAVA);
390 programPort.setPortFormat(format);
393 mInputPorts.put(name, programPort);
396 protected void closeOutputPort(String name) {
397 getOutputPort(name).close();
401 protected final Field getParameterField(String name) {
403 Class filterClass = getClass();
404 FilterParameter param;
405 for (Field field : filterClass.getDeclaredFields()) {
406 if ((param = field.getAnnotation(FilterParameter.class)) != null) {
407 String paramName = param.name().isEmpty() ? field.getName() : param.name();
408 if (name.equals(paramName)) {
416 protected final ProgramVariable getProgramVariable(String name) {
418 Class filterClass = getClass();
419 for (Field field : filterClass.getDeclaredFields()) {
420 ProgramParameter[] params = getProgramParameters(field);
421 for (ProgramParameter param : params) {
422 String varName = param.name();
423 String exposedName = param.exposedName().isEmpty() ? varName : param.exposedName();
424 if (name.equals(exposedName)) {
425 field.setAccessible(true);
427 return new ProgramVariable((Program)field.get(this), varName);
428 } catch (IllegalAccessException e) {
429 throw new RuntimeException(
430 "Access to field '" + field.getName() + "' was denied!");
431 } catch (ClassCastException e) {
432 throw new RuntimeException(
433 "Non Program field '" + field.getName() + "' annotated with "
434 + " ProgramParameter!");
442 protected final ProgramParameter[] getProgramParameters(Field field) {
443 Annotation annotation;
444 if ((annotation = field.getAnnotation(ProgramParameter.class)) != null) {
445 ProgramParameter[] result = new ProgramParameter[1];
446 result[0] = (ProgramParameter)annotation;
448 } else if ((annotation = field.getAnnotation(ProgramParameters.class)) != null) {
449 return ((ProgramParameters)annotation).value();
451 return new ProgramParameter[0];
455 protected final boolean setParameterField(String name, Object value) {
456 Field field = getParameterField(name);
458 field.setAccessible(true);
460 field.set(this, value);
461 } catch (IllegalAccessException e) {
462 throw new RuntimeException(
463 "Access to field '" + field.getName() + "' was denied!");
470 protected final boolean setProgramVariable(String name, Object value, boolean isUpdate) {
471 ProgramVariable programVar = getProgramVariable(name);
472 if (programVar != null) {
473 // If this is not an update, postpone until process to ensure program is setup
475 if (mUpdatedParams == null) {
476 mUpdatedParams = new KeyValueMap();
478 mUpdatedParams.put(name, value);
480 programVar.setValue(value);
487 protected final void setExposedParameters(KeyValueMap keyValueMap, boolean isUpdate) {
488 // Go through all parameter entries
489 for (Entry<String, Object> entry : keyValueMap.entrySet()) {
490 // Try setting filter field
491 if (!setParameterField(entry.getKey(), entry.getValue())) {
492 // If that fails, try setting a program variable
493 if (!setProgramVariable(entry.getKey(), entry.getValue(), isUpdate)) {
494 // If that fails too, throw an exception
495 throw new RuntimeException("Attempting to set unknown parameter '"
496 + entry.getKey() + "' on filter " + this + "!");
503 public String toString() {
504 return "'" + getName() + "' (" + getFilterClassName() + ")";
507 // Core internal methods ///////////////////////////////////////////////////////////////////////
508 final Collection<InputPort> getInputPorts() {
509 return mInputPorts.values();
512 final Collection<OutputPort> getOutputPorts() {
513 return mOutputPorts.values();
516 final synchronized int getStatus() {
520 final synchronized void unsetStatus(int flag) {
524 final synchronized void performOpen(FilterContext context) {
526 if (mStatus == STATUS_UNPREPARED) {
527 if (mLogVerbose) Log.v(TAG, "Preparing " + this);
529 mStatus = STATUS_PREPARED;
531 if (mStatus == STATUS_PREPARED) {
532 if (mLogVerbose) Log.v(TAG, "Opening " + this);
534 mStatus = STATUS_PROCESSING;
536 if (mStatus != STATUS_PROCESSING) {
537 throw new RuntimeException("Filter " + this + " was brought into invalid state during "
538 + "opening (state: " + mStatus + ")!");
544 final synchronized void performProcess(FilterContext context) {
545 if (mStatus == STATUS_RELEASED) {
546 throw new RuntimeException("Filter " + this + " is already torn down!");
548 transferInputFrames(context);
549 if (mStatus < STATUS_PROCESSING) {
550 performOpen(context);
552 if (mLogVerbose) Log.v(TAG, "Processing " + this);
553 mCurrentTimestamp = Frame.TIMESTAMP_UNKNOWN;
555 releasePulledFrames(context);
556 if (filterMustClose()) {
557 performClose(context);
561 final synchronized void performClose(FilterContext context) {
563 if (mLogVerbose) Log.v(TAG, "Closing " + this);
565 mStatus = STATUS_PREPARED;
571 final synchronized void performTearDown(FilterContext context) {
572 performClose(context);
573 if (mStatus != STATUS_RELEASED) {
575 mStatus = STATUS_RELEASED;
579 synchronized final boolean canProcess() {
580 if (mLogVerbose) Log.v(TAG, "Checking if can process: " + this + " (" + mStatus + ").");
581 if (mStatus <= STATUS_PROCESSING) {
582 return inputConditionsMet() && outputConditionsMet();
588 final void openOutputs() {
589 if (mLogVerbose) Log.v(TAG, "Opening all output ports on " + this + "!");
590 for (OutputPort outputPort : mOutputPorts.values()) {
591 if (!outputPort.isOpen()) {
597 final void clearOutputs() {
598 for (OutputPort outputPort : mOutputPorts.values()) {
603 final void notifyFieldPortValueUpdated(String name, FilterContext context) {
604 if (mStatus == STATUS_PROCESSING || mStatus == STATUS_PREPARED) {
605 fieldPortValueUpdated(name, context);
609 // Filter internal methods /////////////////////////////////////////////////////////////////////
610 private final void initFinalPorts(KeyValueMap values) {
611 mInputPorts = new HashMap<String, InputPort>();
612 mOutputPorts = new HashMap<String, OutputPort>();
613 addAndSetFinalPorts(values);
616 private final void initRemainingPorts(KeyValueMap values) {
618 setupPorts(); // TODO: rename to addFilterPorts() ?
619 setInitialInputValues(values);
622 private final void addAndSetFinalPorts(KeyValueMap values) {
623 Class filterClass = getClass();
624 Annotation annotation;
625 for (Field field : filterClass.getDeclaredFields()) {
626 if ((annotation = field.getAnnotation(GenerateFinalPort.class)) != null) {
627 GenerateFinalPort generator = (GenerateFinalPort)annotation;
628 String name = generator.name().isEmpty() ? field.getName() : generator.name();
629 boolean hasDefault = generator.hasDefault();
630 addFieldPort(name, field, hasDefault, true);
631 if (values.containsKey(name)) {
632 setImmediateInputValue(name, values.get(name));
634 } else if (!generator.hasDefault()) {
635 throw new RuntimeException("No value specified for final input port '"
636 + name + "' of filter " + this + "!");
642 private final void addAnnotatedPorts() {
643 Class filterClass = getClass();
644 Annotation annotation;
645 for (Field field : filterClass.getDeclaredFields()) {
646 if ((annotation = field.getAnnotation(GenerateFieldPort.class)) != null) {
647 GenerateFieldPort generator = (GenerateFieldPort)annotation;
648 addFieldGenerator(generator, field);
649 } else if ((annotation = field.getAnnotation(GenerateProgramPort.class)) != null) {
650 GenerateProgramPort generator = (GenerateProgramPort)annotation;
651 addProgramGenerator(generator, field);
652 } else if ((annotation = field.getAnnotation(GenerateProgramPorts.class)) != null) {
653 GenerateProgramPorts generators = (GenerateProgramPorts)annotation;
654 for (GenerateProgramPort generator : generators.value()) {
655 addProgramGenerator(generator, field);
661 private final void addFieldGenerator(GenerateFieldPort generator, Field field) {
662 String name = generator.name().isEmpty() ? field.getName() : generator.name();
663 boolean hasDefault = generator.hasDefault();
664 addFieldPort(name, field, hasDefault, false);
667 private final void addProgramGenerator(GenerateProgramPort generator, Field field) {
668 String name = generator.name();
669 String varName = generator.variableName().isEmpty() ? name
670 : generator.variableName();
671 Class varType = generator.type();
672 boolean hasDefault = generator.hasDefault();
673 addProgramPort(name, varName, field, varType, hasDefault);
676 private final void setInitialInputValues(KeyValueMap values) {
677 for (Entry<String, Object> entry : values.entrySet()) {
678 setInputValue(entry.getKey(), entry.getValue());
682 private final void setImmediateInputValue(String name, Object value) {
683 if (mLogVerbose) Log.v(TAG, "Setting immediate value " + value + " for port " + name + "!");
684 FilterPort port = getInputPort(name);
686 port.setFrame(JavaFrame.wrapObject(value, null));
689 private final void transferInputFrames(FilterContext context) {
690 for (InputPort inputPort : mInputPorts.values()) {
691 inputPort.transfer(context);
695 private final void releasePulledFrames(FilterContext context) {
696 for (Frame frame : mFramesToRelease) {
697 context.getFrameManager().releaseFrame(frame);
699 mFramesToRelease.clear();
702 private final boolean inputConditionsMet() {
703 for (FilterPort port : mInputPorts.values()) {
704 if (!port.isReady()) {
705 if (mLogVerbose) Log.v(TAG, "Input condition not met: " + port + "!");
712 private final boolean outputConditionsMet() {
713 for (FilterPort port : mOutputPorts.values()) {
714 if (!port.isReady()) {
715 if (mLogVerbose) Log.v(TAG, "Output condition not met: " + port + "!");
722 private final void closePorts() {
723 if (mLogVerbose) Log.v(TAG, "Closing all ports on " + this + "!");
724 for (InputPort inputPort : mInputPorts.values()) {
727 for (OutputPort outputPort : mOutputPorts.values()) {
732 private final boolean filterMustClose() {
733 for (InputPort inputPort : mInputPorts.values()) {
734 if (inputPort.filterMustClose()) {
735 if (mLogVerbose) Log.v(TAG, "Filter " + this + " must close due to port " + inputPort);
739 for (OutputPort outputPort : mOutputPorts.values()) {
740 if (outputPort.filterMustClose()) {
741 if (mLogVerbose) Log.v(TAG, "Filter " + this + " must close due to port " + outputPort);