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 18 package android.filterfw.core; 19 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; 27 28 import java.io.Serializable; 29 import java.lang.annotation.Annotation; 30 import java.lang.reflect.Field; 31 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; 37 import java.util.Set; 38 39 /** 40 * @hide 41 */ 42 public 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