1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
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
7 *
8 *            http://www.apache.org/licenses/LICENSE-2.0
9 *
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.
15 */
16
17
18package android.filterfw.core;
19
20import android.filterfw.core.FilterContext;
21import android.filterfw.core.FilterPort;
22import android.filterfw.core.KeyValueMap;
23import android.filterfw.io.TextGraphReader;
24import android.filterfw.io.GraphIOException;
25import android.filterfw.format.ObjectFormat;
26import android.util.Log;
27
28import java.io.Serializable;
29import java.lang.annotation.Annotation;
30import java.lang.reflect.Field;
31import java.lang.Thread;
32import java.util.Collection;
33import java.util.HashMap;
34import java.util.HashSet;
35import java.util.Map.Entry;
36import java.util.Set;
37
38/**
39 * @hide
40 */
41public abstract class Filter {
42
43    static final int STATUS_PREINIT               = 0;
44    static final int STATUS_UNPREPARED            = 1;
45    static final int STATUS_PREPARED              = 2;
46    static final int STATUS_PROCESSING            = 3;
47    static final int STATUS_SLEEPING              = 4;
48    static final int STATUS_FINISHED              = 5;
49    static final int STATUS_ERROR                 = 6;
50    static final int STATUS_RELEASED              = 7;
51
52    private String mName;
53
54    private int mInputCount = -1;
55    private int mOutputCount = -1;
56
57    private HashMap<String, InputPort> mInputPorts;
58    private HashMap<String, OutputPort> mOutputPorts;
59
60    private HashSet<Frame> mFramesToRelease;
61    private HashMap<String, Frame> mFramesToSet;
62
63    private int mStatus = 0;
64    private boolean mIsOpen = false;
65    private int mSleepDelay;
66
67    private long mCurrentTimestamp;
68
69    private boolean mLogVerbose;
70    private static final String TAG = "Filter";
71
72    public Filter(String name) {
73        mName = name;
74        mFramesToRelease = new HashSet<Frame>();
75        mFramesToSet = new HashMap<String, Frame>();
76        mStatus = STATUS_PREINIT;
77
78        mLogVerbose = Log.isLoggable(TAG, Log.VERBOSE);
79    }
80
81    /** Tests to see if a given filter is installed on the system. Requires
82     * full filter package name, including filterpack.
83     */
84    public static final boolean isAvailable(String filterName) {
85        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
86        Class filterClass;
87        // First see if a class of that name exists
88        try {
89            filterClass = contextClassLoader.loadClass(filterName);
90        } catch (ClassNotFoundException e) {
91            return false;
92        }
93        // Then make sure it's a subclass of Filter.
94        try {
95            filterClass.asSubclass(Filter.class);
96        } catch (ClassCastException e) {
97            return false;
98        }
99        return true;
100    }
101
102    public final void initWithValueMap(KeyValueMap valueMap) {
103        // Initialization
104        initFinalPorts(valueMap);
105
106        // Setup remaining ports
107        initRemainingPorts(valueMap);
108
109        // This indicates that final ports can no longer be set
110        mStatus = STATUS_UNPREPARED;
111    }
112
113    public final void initWithAssignmentString(String assignments) {
114        try {
115            KeyValueMap valueMap = new TextGraphReader().readKeyValueAssignments(assignments);
116            initWithValueMap(valueMap);
117        } catch (GraphIOException e) {
118            throw new IllegalArgumentException(e.getMessage());
119        }
120    }
121
122    public final void initWithAssignmentList(Object... keyValues) {
123        KeyValueMap valueMap = new KeyValueMap();
124        valueMap.setKeyValues(keyValues);
125        initWithValueMap(valueMap);
126    }
127
128    public final void init() throws ProtocolException {
129        KeyValueMap valueMap = new KeyValueMap();
130        initWithValueMap(valueMap);
131    }
132
133    public String getFilterClassName() {
134        return getClass().getSimpleName();
135    }
136
137    public final String getName() {
138        return mName;
139    }
140
141    public boolean isOpen() {
142        return mIsOpen;
143    }
144
145    public void setInputFrame(String inputName, Frame frame) {
146        FilterPort port = getInputPort(inputName);
147        if (!port.isOpen()) {
148            port.open();
149        }
150        port.setFrame(frame);
151    }
152
153    public final void setInputValue(String inputName, Object value) {
154        setInputFrame(inputName, wrapInputValue(inputName, value));
155    }
156
157    protected void prepare(FilterContext context) {
158    }
159
160    protected void parametersUpdated(Set<String> updated) {
161    }
162
163    protected void delayNextProcess(int millisecs) {
164        mSleepDelay = millisecs;
165        mStatus = STATUS_SLEEPING;
166    }
167
168    public abstract void setupPorts();
169
170    public FrameFormat getOutputFormat(String portName, FrameFormat inputFormat) {
171        return null;
172    }
173
174    public final FrameFormat getInputFormat(String portName) {
175        InputPort inputPort = getInputPort(portName);
176        return inputPort.getSourceFormat();
177    }
178
179    public void open(FilterContext context) {
180    }
181
182    public abstract void process(FilterContext context);
183
184    public final int getSleepDelay() {
185        return 250;
186    }
187
188    public void close(FilterContext context) {
189    }
190
191    public void tearDown(FilterContext context) {
192    }
193
194    public final int getNumberOfConnectedInputs() {
195        int c = 0;
196        for (InputPort inputPort : mInputPorts.values()) {
197            if (inputPort.isConnected()) {
198                ++c;
199            }
200        }
201        return c;
202    }
203
204    public final int getNumberOfConnectedOutputs() {
205        int c = 0;
206        for (OutputPort outputPort : mOutputPorts.values()) {
207            if (outputPort.isConnected()) {
208                ++c;
209            }
210        }
211        return c;
212    }
213
214    public final int getNumberOfInputs() {
215        return mOutputPorts == null ? 0 : mInputPorts.size();
216    }
217
218    public final int getNumberOfOutputs() {
219        return mInputPorts == null ? 0 : mOutputPorts.size();
220    }
221
222    public final InputPort getInputPort(String portName) {
223        if (mInputPorts == null) {
224            throw new NullPointerException("Attempting to access input port '" + portName
225                + "' of " + this + " before Filter has been initialized!");
226        }
227        InputPort result = mInputPorts.get(portName);
228        if (result == null) {
229            throw new IllegalArgumentException("Unknown input port '" + portName + "' on filter "
230                + this + "!");
231        }
232        return result;
233    }
234
235    public final OutputPort getOutputPort(String portName) {
236        if (mInputPorts == null) {
237            throw new NullPointerException("Attempting to access output port '" + portName
238                + "' of " + this + " before Filter has been initialized!");
239        }
240        OutputPort result = mOutputPorts.get(portName);
241        if (result == null) {
242            throw new IllegalArgumentException("Unknown output port '" + portName + "' on filter "
243                + this + "!");
244        }
245        return result;
246    }
247
248    protected final void pushOutput(String name, Frame frame) {
249        if (frame.getTimestamp() == Frame.TIMESTAMP_NOT_SET) {
250            if (mLogVerbose) Log.v(TAG, "Default-setting output Frame timestamp on port " + name + " to " + mCurrentTimestamp);
251            frame.setTimestamp(mCurrentTimestamp);
252        }
253        getOutputPort(name).pushFrame(frame);
254    }
255
256    protected final Frame pullInput(String name) {
257        Frame result = getInputPort(name).pullFrame();
258        if (mCurrentTimestamp == Frame.TIMESTAMP_UNKNOWN) {
259            mCurrentTimestamp = result.getTimestamp();
260            if (mLogVerbose) Log.v(TAG, "Default-setting current timestamp from input port " + name + " to " + mCurrentTimestamp);
261        }
262        // As result is retained, we add it to the release pool here
263        mFramesToRelease.add(result);
264
265        return result;
266    }
267
268    public void fieldPortValueUpdated(String name, FilterContext context) {
269    }
270
271    /**
272     * Transfers any frame from an input port to its destination. This is useful to force a
273     * transfer from a FieldPort or ProgramPort to its connected target (field or program variable).
274     */
275    protected void transferInputPortFrame(String name, FilterContext context) {
276        getInputPort(name).transfer(context);
277    }
278
279    /**
280     * Assigns all program variables to the ports they are connected to. Call this after
281     * constructing a Program instance with attached ProgramPorts.
282     */
283    protected void initProgramInputs(Program program, FilterContext context) {
284        if (program != null) {
285            for (InputPort inputPort : mInputPorts.values()) {
286                if (inputPort.getTarget() == program) {
287                    inputPort.transfer(context);
288                }
289            }
290        }
291    }
292
293    /**
294     * Adds an input port to the filter. You should call this from within setupPorts, if your
295     * filter has input ports. No type-checking is performed on the input. If you would like to
296     * check against a type mask, use
297     * {@link #addMaskedInputPort(String, FrameFormat) addMaskedInputPort} instead.
298     *
299     * @param name the name of the input port
300     */
301    protected void addInputPort(String name) {
302        addMaskedInputPort(name, null);
303    }
304
305    /**
306     * Adds an input port to the filter. You should call this from within setupPorts, if your
307     * filter has input ports. When type-checking is performed, the input format is
308     * checked against the provided format mask. An exception is thrown in case of a conflict.
309     *
310     * @param name the name of the input port
311     * @param formatMask a format mask, which filters the allowable input types
312     */
313    protected void addMaskedInputPort(String name, FrameFormat formatMask) {
314        InputPort port = new StreamPort(this, name);
315        if (mLogVerbose) Log.v(TAG, "Filter " + this + " adding " + port);
316        mInputPorts.put(name, port);
317        port.setPortFormat(formatMask);
318    }
319
320    /**
321     * Adds an output port to the filter with a fixed output format. You should call this from
322     * within setupPorts, if your filter has output ports. You cannot use this method, if your
323     * output format depends on the input format (e.g. in a pass-through filter). In this case, use
324     * {@link #addOutputBasedOnInput(String, String) addOutputBasedOnInput} instead.
325     *
326     * @param name the name of the output port
327     * @param format the fixed output format of this port
328     */
329    protected void addOutputPort(String name, FrameFormat format) {
330        OutputPort port = new OutputPort(this, name);
331        if (mLogVerbose) Log.v(TAG, "Filter " + this + " adding " + port);
332        port.setPortFormat(format);
333        mOutputPorts.put(name, port);
334    }
335
336    /**
337     * Adds an output port to the filter. You should call this from within setupPorts, if your
338     * filter has output ports. Using this method indicates that the output format for this
339     * particular port, depends on the format of an input port. You MUST also override
340     * {@link #getOutputFormat(String, FrameFormat) getOutputFormat} to specify what format your
341     * filter will output for a given input. If the output format of your filter port does not
342     * depend on the input, use {@link #addOutputPort(String, FrameFormat) addOutputPort} instead.
343     *
344     * @param outputName the name of the output port
345     * @param inputName the name of the input port, that this output depends on
346     */
347    protected void addOutputBasedOnInput(String outputName, String inputName) {
348        OutputPort port = new OutputPort(this, outputName);
349        if (mLogVerbose) Log.v(TAG, "Filter " + this + " adding " + port);
350        port.setBasePort(getInputPort(inputName));
351        mOutputPorts.put(outputName, port);
352    }
353
354    protected void addFieldPort(String name,
355                                Field field,
356                                boolean hasDefault,
357                                boolean isFinal) {
358        // Make sure field is accessible
359        field.setAccessible(true);
360
361        // Create port for this input
362        InputPort fieldPort = isFinal
363            ? new FinalPort(this, name, field, hasDefault)
364            : new FieldPort(this, name, field, hasDefault);
365
366        // Create format for this input
367        if (mLogVerbose) Log.v(TAG, "Filter " + this + " adding " + fieldPort);
368        MutableFrameFormat format = ObjectFormat.fromClass(field.getType(),
369                                                           FrameFormat.TARGET_SIMPLE);
370        fieldPort.setPortFormat(format);
371
372        // Add port
373        mInputPorts.put(name, fieldPort);
374    }
375
376    protected void addProgramPort(String name,
377                                  String varName,
378                                  Field field,
379                                  Class varType,
380                                  boolean hasDefault) {
381        // Make sure field is accessible
382        field.setAccessible(true);
383
384        // Create port for this input
385        InputPort programPort = new ProgramPort(this, name, varName, field, hasDefault);
386
387        // Create format for this input
388        if (mLogVerbose) Log.v(TAG, "Filter " + this + " adding " + programPort);
389        MutableFrameFormat format = ObjectFormat.fromClass(varType,
390                                                           FrameFormat.TARGET_SIMPLE);
391        programPort.setPortFormat(format);
392
393        // Add port
394        mInputPorts.put(name, programPort);
395    }
396
397    protected void closeOutputPort(String name) {
398        getOutputPort(name).close();
399    }
400
401    /**
402     * Specifies whether the filter should not be scheduled until a frame is available on that
403     * input port. Note, that setting this to false, does not block a new frame from coming in
404     * (though there is no necessity to pull that frame for processing).
405     * @param portName the name of the input port.
406     * @param waits true, if the filter should wait for a frame on this port.
407     */
408    protected void setWaitsOnInputPort(String portName, boolean waits) {
409        getInputPort(portName).setBlocking(waits);
410    }
411
412    /**
413     * Specifies whether the filter should not be scheduled until the output port is free, i.e.
414     * there is no frame waiting on that output.
415     * @param portName the name of the output port.
416     * @param waits true, if the filter should wait for the port to become free.
417     */
418    protected void setWaitsOnOutputPort(String portName, boolean waits) {
419        getOutputPort(portName).setBlocking(waits);
420    }
421
422    public String toString() {
423        return "'" + getName() + "' (" + getFilterClassName() + ")";
424    }
425
426    // Core internal methods ///////////////////////////////////////////////////////////////////////
427    final Collection<InputPort> getInputPorts() {
428        return mInputPorts.values();
429    }
430
431    final Collection<OutputPort> getOutputPorts() {
432        return mOutputPorts.values();
433    }
434
435    final synchronized int getStatus() {
436        return mStatus;
437    }
438
439    final synchronized void unsetStatus(int flag) {
440        mStatus &= ~flag;
441    }
442
443    final synchronized void performOpen(FilterContext context) {
444        if (!mIsOpen) {
445            if (mStatus == STATUS_UNPREPARED) {
446                if (mLogVerbose) Log.v(TAG, "Preparing " + this);
447                prepare(context);
448                mStatus = STATUS_PREPARED;
449            }
450            if (mStatus == STATUS_PREPARED) {
451                if (mLogVerbose) Log.v(TAG, "Opening " + this);
452                open(context);
453                mStatus = STATUS_PROCESSING;
454            }
455            if (mStatus != STATUS_PROCESSING) {
456                throw new RuntimeException("Filter " + this + " was brought into invalid state during "
457                    + "opening (state: " + mStatus + ")!");
458            }
459            mIsOpen = true;
460        }
461    }
462
463    final synchronized void performProcess(FilterContext context) {
464        if (mStatus == STATUS_RELEASED) {
465            throw new RuntimeException("Filter " + this + " is already torn down!");
466        }
467        transferInputFrames(context);
468        if (mStatus < STATUS_PROCESSING) {
469            performOpen(context);
470        }
471        if (mLogVerbose) Log.v(TAG, "Processing " + this);
472        mCurrentTimestamp = Frame.TIMESTAMP_UNKNOWN;
473        process(context);
474        releasePulledFrames(context);
475        if (filterMustClose()) {
476            performClose(context);
477        }
478    }
479
480    final synchronized void performClose(FilterContext context) {
481        if (mIsOpen) {
482            if (mLogVerbose) Log.v(TAG, "Closing " + this);
483            mIsOpen = false;
484            mStatus = STATUS_PREPARED;
485            close(context);
486            closePorts();
487        }
488    }
489
490    final synchronized void performTearDown(FilterContext context) {
491        performClose(context);
492        if (mStatus != STATUS_RELEASED) {
493            tearDown(context);
494            mStatus = STATUS_RELEASED;
495        }
496    }
497
498    synchronized final boolean canProcess() {
499        if (mLogVerbose) Log.v(TAG, "Checking if can process: " + this + " (" + mStatus + ").");
500        if (mStatus <= STATUS_PROCESSING) {
501            return inputConditionsMet() && outputConditionsMet();
502        } else {
503            return false;
504        }
505    }
506
507    final void openOutputs() {
508        if (mLogVerbose) Log.v(TAG, "Opening all output ports on " + this + "!");
509        for (OutputPort outputPort : mOutputPorts.values()) {
510            if (!outputPort.isOpen()) {
511                outputPort.open();
512            }
513        }
514    }
515
516    final void clearInputs() {
517        for (InputPort inputPort : mInputPorts.values()) {
518            inputPort.clear();
519        }
520    }
521
522    final void clearOutputs() {
523        for (OutputPort outputPort : mOutputPorts.values()) {
524            outputPort.clear();
525        }
526    }
527
528    final void notifyFieldPortValueUpdated(String name, FilterContext context) {
529        if (mStatus == STATUS_PROCESSING || mStatus == STATUS_PREPARED) {
530            fieldPortValueUpdated(name, context);
531        }
532    }
533
534    final synchronized void pushInputFrame(String inputName, Frame frame) {
535        FilterPort port = getInputPort(inputName);
536        if (!port.isOpen()) {
537            port.open();
538        }
539        port.pushFrame(frame);
540    }
541
542    final synchronized void pushInputValue(String inputName, Object value) {
543        pushInputFrame(inputName, wrapInputValue(inputName, value));
544    }
545
546    // Filter internal methods /////////////////////////////////////////////////////////////////////
547    private final void initFinalPorts(KeyValueMap values) {
548        mInputPorts = new HashMap<String, InputPort>();
549        mOutputPorts = new HashMap<String, OutputPort>();
550        addAndSetFinalPorts(values);
551    }
552
553    private final void initRemainingPorts(KeyValueMap values) {
554        addAnnotatedPorts();
555        setupPorts();   // TODO: rename to addFilterPorts() ?
556        setInitialInputValues(values);
557    }
558
559    private final void addAndSetFinalPorts(KeyValueMap values) {
560        Class filterClass = getClass();
561        Annotation annotation;
562        for (Field field : filterClass.getDeclaredFields()) {
563            if ((annotation = field.getAnnotation(GenerateFinalPort.class)) != null) {
564                GenerateFinalPort generator = (GenerateFinalPort)annotation;
565                String name = generator.name().isEmpty() ? field.getName() : generator.name();
566                boolean hasDefault = generator.hasDefault();
567                addFieldPort(name, field, hasDefault, true);
568                if (values.containsKey(name)) {
569                    setImmediateInputValue(name, values.get(name));
570                    values.remove(name);
571                } else if (!generator.hasDefault()) {
572                    throw new RuntimeException("No value specified for final input port '"
573                        + name + "' of filter " + this + "!");
574                }
575            }
576        }
577    }
578
579    private final void addAnnotatedPorts() {
580        Class filterClass = getClass();
581        Annotation annotation;
582        for (Field field : filterClass.getDeclaredFields()) {
583            if ((annotation = field.getAnnotation(GenerateFieldPort.class)) != null) {
584                GenerateFieldPort generator = (GenerateFieldPort)annotation;
585                addFieldGenerator(generator, field);
586            } else if ((annotation = field.getAnnotation(GenerateProgramPort.class)) != null) {
587                GenerateProgramPort generator = (GenerateProgramPort)annotation;
588                addProgramGenerator(generator, field);
589            } else if ((annotation = field.getAnnotation(GenerateProgramPorts.class)) != null) {
590                GenerateProgramPorts generators = (GenerateProgramPorts)annotation;
591                for (GenerateProgramPort generator : generators.value()) {
592                    addProgramGenerator(generator, field);
593                }
594            }
595        }
596    }
597
598    private final void addFieldGenerator(GenerateFieldPort generator, Field field) {
599        String name = generator.name().isEmpty() ? field.getName() : generator.name();
600        boolean hasDefault = generator.hasDefault();
601        addFieldPort(name, field, hasDefault, false);
602    }
603
604    private final void addProgramGenerator(GenerateProgramPort generator, Field field) {
605        String name = generator.name();
606        String varName = generator.variableName().isEmpty() ? name
607                                                            : generator.variableName();
608        Class varType = generator.type();
609        boolean hasDefault = generator.hasDefault();
610        addProgramPort(name, varName, field, varType, hasDefault);
611    }
612
613    private final void setInitialInputValues(KeyValueMap values) {
614        for (Entry<String, Object> entry : values.entrySet()) {
615            setInputValue(entry.getKey(), entry.getValue());
616        }
617    }
618
619    private final void setImmediateInputValue(String name, Object value) {
620        if (mLogVerbose) Log.v(TAG, "Setting immediate value " + value + " for port " + name + "!");
621        FilterPort port = getInputPort(name);
622        port.open();
623        port.setFrame(SimpleFrame.wrapObject(value, null));
624    }
625
626    private final void transferInputFrames(FilterContext context) {
627        for (InputPort inputPort : mInputPorts.values()) {
628            inputPort.transfer(context);
629        }
630    }
631
632    private final Frame wrapInputValue(String inputName, Object value) {
633        MutableFrameFormat inputFormat = ObjectFormat.fromObject(value, FrameFormat.TARGET_SIMPLE);
634        if (value == null) {
635            // If the value is null, the format cannot guess the class, so we adjust it to the
636            // class of the input port here
637            FrameFormat portFormat = getInputPort(inputName).getPortFormat();
638            Class portClass = (portFormat == null) ? null : portFormat.getObjectClass();
639            inputFormat.setObjectClass(portClass);
640        }
641
642        // Serialize if serializable, and type is not an immutable primitive.
643        boolean shouldSerialize = !(value instanceof Number)
644            && !(value instanceof Boolean)
645            && !(value instanceof String)
646            && value instanceof Serializable;
647
648        // Create frame wrapper
649        Frame frame = shouldSerialize
650            ? new SerializedFrame(inputFormat, null)
651            : new SimpleFrame(inputFormat, null);
652        frame.setObjectValue(value);
653        return frame;
654    }
655
656    private final void releasePulledFrames(FilterContext context) {
657        for (Frame frame : mFramesToRelease) {
658            context.getFrameManager().releaseFrame(frame);
659        }
660        mFramesToRelease.clear();
661    }
662
663    private final boolean inputConditionsMet() {
664        for (FilterPort port : mInputPorts.values()) {
665            if (!port.isReady()) {
666                if (mLogVerbose) Log.v(TAG, "Input condition not met: " + port + "!");
667                return false;
668            }
669        }
670        return true;
671    }
672
673    private final boolean outputConditionsMet() {
674        for (FilterPort port : mOutputPorts.values()) {
675            if (!port.isReady()) {
676                if (mLogVerbose) Log.v(TAG, "Output condition not met: " + port + "!");
677                return false;
678            }
679        }
680        return true;
681    }
682
683    private final void closePorts() {
684        if (mLogVerbose) Log.v(TAG, "Closing all ports on " + this + "!");
685        for (InputPort inputPort : mInputPorts.values()) {
686            inputPort.close();
687        }
688        for (OutputPort outputPort : mOutputPorts.values()) {
689            outputPort.close();
690        }
691    }
692
693    private final boolean filterMustClose() {
694        for (InputPort inputPort : mInputPorts.values()) {
695            if (inputPort.filterMustClose()) {
696                if (mLogVerbose) Log.v(TAG, "Filter " + this + " must close due to port " + inputPort);
697                return true;
698            }
699        }
700        for (OutputPort outputPort : mOutputPorts.values()) {
701            if (outputPort.filterMustClose()) {
702                if (mLogVerbose) Log.v(TAG, "Filter " + this + " must close due to port " + outputPort);
703                return true;
704            }
705        }
706        return false;
707    }
708}
709