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