1 package org.jacorb.notification.filter; 2 3 23 24 import java.lang.reflect.Constructor ; 25 import java.lang.reflect.InvocationTargetException ; 26 import java.util.ArrayList ; 27 import java.util.Date ; 28 import java.util.HashMap ; 29 import java.util.Iterator ; 30 import java.util.LinkedList ; 31 import java.util.List ; 32 import java.util.Map ; 33 import java.util.NoSuchElementException ; 34 35 import org.apache.avalon.framework.configuration.Configurable; 36 import org.apache.avalon.framework.configuration.Configuration; 37 import org.apache.avalon.framework.configuration.ConfigurationException; 38 import org.apache.avalon.framework.logger.Logger; 39 import org.jacorb.notification.AbstractMessage; 40 import org.jacorb.notification.EventTypeWrapper; 41 import org.jacorb.notification.MessageFactory; 42 import org.jacorb.notification.conf.Attributes; 43 import org.jacorb.notification.conf.Default; 44 import org.jacorb.notification.interfaces.Disposable; 45 import org.jacorb.notification.interfaces.EvaluationContextFactory; 46 import org.jacorb.notification.interfaces.GCDisposable; 47 import org.jacorb.notification.interfaces.Message; 48 import org.jacorb.notification.servant.ManageableServant; 49 import org.jacorb.notification.util.DisposableManager; 50 import org.jacorb.notification.util.LogUtil; 51 import org.jacorb.notification.util.WildcardMap; 52 import org.jacorb.util.ObjectUtil; 53 import org.omg.CORBA.Any ; 54 import org.omg.CORBA.ORB ; 55 import org.omg.CosNotification.EventType; 56 import org.omg.CosNotification.Property; 57 import org.omg.CosNotification.StructuredEvent; 58 import org.omg.CosNotifyComm.NotifySubscribe; 59 import org.omg.CosNotifyFilter.ConstraintExp; 60 import org.omg.CosNotifyFilter.ConstraintInfo; 61 import org.omg.CosNotifyFilter.ConstraintNotFound; 62 import org.omg.CosNotifyFilter.Filter; 63 import org.omg.CosNotifyFilter.FilterOperations; 64 import org.omg.CosNotifyFilter.FilterPOATie; 65 import org.omg.CosNotifyFilter.InvalidConstraint; 66 import org.omg.CosNotifyFilter.UnsupportedFilterableData; 67 import org.omg.PortableServer.POA ; 68 import org.omg.PortableServer.POAPackage.ObjectNotActive ; 69 import org.omg.PortableServer.POAPackage.ServantNotActive ; 70 import org.omg.PortableServer.POAPackage.WrongPolicy ; 71 72 import EDU.oswego.cs.dl.util.concurrent.ReadWriteLock; 73 import EDU.oswego.cs.dl.util.concurrent.Sync; 74 import EDU.oswego.cs.dl.util.concurrent.SynchronizedBoolean; 75 import EDU.oswego.cs.dl.util.concurrent.SynchronizedInt; 76 import EDU.oswego.cs.dl.util.concurrent.WriterPreferenceReadWriteLock; 77 78 130 131 public abstract class AbstractFilter implements GCDisposable, ManageableServant, Configurable, 132 FilterOperations 133 { 134 final static RuntimeException NOT_SUPPORTED = new UnsupportedOperationException ( 135 "this operation is not supported"); 136 137 public static final int NO_CONSTRAINTS_MATCH = -2; 138 139 public static final int CONSTRAINTS_EMPTY = -1; 140 141 private static final String EMPTY_EVENT_TYPE_CONSTRAINT_KEY = AbstractMessage 142 .calcConstraintKey("*", "*"); 143 144 146 private final FilterPOATie servant_; 147 148 private final DisposableManager disposables_ = new DisposableManager(); 149 150 private final CallbackManager callbackManager_ = new CallbackManager(); 151 152 156 protected final Map constraints_ = new HashMap (); 157 158 protected final WildcardMap wildcardMap_; 159 160 protected final ReadWriteLock constraintsLock_; 161 162 private final SynchronizedInt constraintIdPool_ = new SynchronizedInt(0); 163 164 protected final MessageFactory messageFactory_; 165 166 private final FilterUsageDecorator filterUsageDecorator_; 167 168 private final POA poa_; 169 170 private final ORB orb_; 171 172 private Filter thisRef_; 173 174 private final Logger logger_; 175 176 private final EvaluationContextFactory evaluationContextFactory_; 177 178 private final SynchronizedBoolean isActivated = new SynchronizedBoolean(false); 179 180 private static final ConstraintInfo[] EMPTY_CONSTRAINT_INFO = new ConstraintInfo[0]; 181 182 private final long maxIdleTime_; 183 184 186 protected AbstractFilter(Configuration config, 187 EvaluationContextFactory evaluationContextFactory, MessageFactory messageFactory, 188 ORB orb, POA poa) throws ConfigurationException 189 { 190 super(); 191 192 orb_ = orb; 193 poa_ = poa; 194 logger_ = LogUtil.getLogger(config, getClass().getName()); 195 196 if (logger_.isInfoEnabled()) 197 { 198 logger_.info("Created filter for Grammar: " + constraint_grammar()); 199 } 200 201 messageFactory_ = messageFactory; 202 203 evaluationContextFactory_ = evaluationContextFactory; 204 205 constraintsLock_ = new WriterPreferenceReadWriteLock(); 206 207 wildcardMap_ = newWildcardMap(config); 208 209 disposables_.addDisposable(callbackManager_); 210 211 filterUsageDecorator_ = new FilterUsageDecorator(this); 212 213 servant_ = new FilterPOATie(filterUsageDecorator_.getFilterOperations()); 214 215 maxIdleTime_ = config.getAttributeAsLong(Attributes.DEAD_FILTER_INTERVAL, 216 Default.DEFAULT_DEAD_FILTER_INTERVAL); 217 } 218 219 221 private WildcardMap newWildcardMap(Configuration config) throws ConfigurationException 222 { 223 String wildcardMapImpl = config.getAttribute(Attributes.WILDCARDMAP_CLASS, 224 Default.DEFAULT_WILDCARDMAP_IMPL); 225 226 try 227 { 228 Class wildcardMapClazz = ObjectUtil.classForName(wildcardMapImpl); 229 230 Constructor ctor = wildcardMapClazz.getConstructor(new Class [0]); 231 232 return (WildcardMap) ctor.newInstance(new Object [0]); 233 } catch (ClassNotFoundException e) 234 { 235 } catch (IllegalArgumentException e) 237 { 238 } catch (InstantiationException e) 240 { 241 } catch (IllegalAccessException e) 243 { 244 } catch (InvocationTargetException e) 246 { 247 } catch (SecurityException e) 249 { 250 } catch (NoSuchMethodException e) 252 { 253 } 255 256 throw new ConfigurationException(wildcardMapImpl 257 + " is no valid WildcardMap Implementation"); 258 } 259 260 public final void configure(Configuration conf) 261 { 262 } 264 265 public org.omg.CORBA.Object activate() 266 { 267 if (thisRef_ == null) 268 { 269 thisRef_ = servant_._this(orb_); 270 271 isActivated.set(true); 272 } 273 274 return thisRef_; 275 } 276 277 public void deactivate() 278 { 279 try 280 { 281 poa_.deactivate_object(poa_.servant_to_id(servant_)); 282 } catch (WrongPolicy e) 283 { 284 logger_.fatalError("error deactivating object", e); 285 } catch (ObjectNotActive e) 286 { 287 logger_.fatalError("error deactivating object", e); 288 } catch (ServantNotActive e) 289 { 290 logger_.fatalError("error deactivating object", e); 291 } 292 } 293 294 protected int newConstraintId() 295 { 296 return constraintIdPool_.increment(); 297 } 298 299 322 public ConstraintInfo[] add_constraints(ConstraintExp[] constraintExp) throws InvalidConstraint 323 { 324 FilterConstraint[] _arrayFilterConstraint = newFilterConstraints(constraintExp); 325 326 try 327 { 328 constraintsLock_.writeLock().acquire(); 330 331 try 332 { 333 return add_constraint(constraintExp, _arrayFilterConstraint); 334 } finally 335 { 336 constraintsLock_.writeLock().release(); 338 } 339 } catch (InterruptedException ie) 340 { 341 Thread.currentThread().interrupt(); 343 344 return EMPTY_CONSTRAINT_INFO; 345 } 346 } 347 348 private ConstraintInfo[] add_constraint(ConstraintExp[] constraintExp, 349 FilterConstraint[] filterConstraints) throws InterruptedException 350 { 351 final ConstraintInfo[] _arrayConstraintInfo = new ConstraintInfo[filterConstraints.length]; 352 353 for (int _x = 0; _x < constraintExp.length; _x++) 354 { 355 int _constraintId = newConstraintId(); 356 357 _arrayConstraintInfo[_x] = new ConstraintInfo(constraintExp[_x], _constraintId); 358 359 ConstraintEntry _entry = new ConstraintEntry(filterConstraints[_x], 360 _arrayConstraintInfo[_x]); 361 362 addEventTypeMappingsForConstraint(_entry); 363 364 constraints_.put(new Integer (_constraintId), _entry); 365 366 notifyCallbacks(); 367 } 368 369 return _arrayConstraintInfo; 370 } 371 372 private void addEventTypeMappingsForConstraint(ConstraintEntry entry) 373 { 374 int _eventTypeCount = entry.getEventTypeCount(); 375 376 if (_eventTypeCount == 0) 377 { 378 addConstraintEntryToWildcardMap(EMPTY_EVENT_TYPE_CONSTRAINT_KEY, entry); 379 } 380 else 381 { 382 for (int _y = 0; _y < _eventTypeCount; ++_y) 383 { 384 EventTypeWrapper _eventTypeWrapper = entry.getEventTypeWrapper(_y); 385 386 addConstraintEntryToWildcardMap(_eventTypeWrapper.getConstraintKey(), entry); 387 } 388 } 389 } 390 391 private void addConstraintEntryToWildcardMap(String constraintKey, 392 ConstraintEntry constraintEntry) 393 { 394 List _listOfConstraintEntry = (List ) wildcardMap_.getNoExpansion(constraintKey); 395 396 if (_listOfConstraintEntry == null) 397 { 398 _listOfConstraintEntry = new LinkedList (); 399 400 wildcardMap_.put(constraintKey, _listOfConstraintEntry); 401 } 402 403 _listOfConstraintEntry.add(constraintEntry); 404 } 405 406 private FilterConstraint[] newFilterConstraints(ConstraintExp[] constraintExp) 407 throws InvalidConstraint 408 { 409 FilterConstraint[] _arrayFilterConstraint = new FilterConstraint[constraintExp.length]; 410 411 for (int _x = 0; _x < constraintExp.length; _x++) 415 { 416 _arrayFilterConstraint[_x] = newFilterConstraint(constraintExp[_x]); 417 } 418 419 return _arrayFilterConstraint; 420 } 421 422 protected abstract FilterConstraint newFilterConstraint(ConstraintExp constraintExp) 423 throws InvalidConstraint; 424 425 427 public void modify_constraints(int[] deleteIds, ConstraintInfo[] constraintInfo) 428 throws ConstraintNotFound, InvalidConstraint 429 { 430 try 431 { 432 constraintsLock_.writeLock().acquire(); 434 435 try 436 { 437 Integer [] _deleteKeys = checkConstraintsToBeDeleted(deleteIds); 438 439 FilterConstraint[] _arrayConstraintEvaluator = checkConstraintsToBeModified(constraintInfo); 440 441 deleteConstraints(_deleteKeys); 442 443 modifyConstraints(constraintInfo, _arrayConstraintEvaluator); 444 445 notifyCallbacks(); 446 } finally 447 { 448 constraintsLock_.writeLock().release(); 449 } 450 } catch (InterruptedException ie) 451 { 452 Thread.currentThread().interrupt(); 453 } 454 } 455 456 private void modifyConstraints(ConstraintInfo[] constraintInfo, 457 FilterConstraint[] filterConstraints) 458 { 459 for (int _x = 0; _x < constraintInfo.length; _x++) 460 { 461 Integer _key = new Integer (constraintInfo[_x].constraint_id); 462 463 ConstraintEntry _updatedEntry = new ConstraintEntry(filterConstraints[_x], 464 constraintInfo[_x]); 465 466 constraints_.put(_key, _updatedEntry); 468 469 int _eventTypeCount = _updatedEntry.getEventTypeCount(); 470 471 for (int _y = 0; _y < _eventTypeCount; ++_y) 472 { 473 EventTypeIdentifier _eventTypeIdentifier = _updatedEntry.getEventTypeWrapper(_y); 474 475 List _listOfConstraintEvaluator = (List ) wildcardMap_ 476 .getNoExpansion(_eventTypeIdentifier.getConstraintKey()); 477 478 _listOfConstraintEvaluator.add(_updatedEntry); 481 } 482 } 483 } 484 485 private FilterConstraint[] checkConstraintsToBeModified(ConstraintInfo[] constraintInfo) 486 throws InvalidConstraint, ConstraintNotFound 487 { 488 FilterConstraint[] _arrayConstraintEvaluator = new FilterConstraint[constraintInfo.length]; 489 490 for (int _x = 0; _x < constraintInfo.length; ++_x) 491 { 492 if (constraints_.containsKey(new Integer (constraintInfo[_x].constraint_id))) 493 { 494 _arrayConstraintEvaluator[_x] = newFilterConstraint(constraintInfo[_x].constraint_expression); 495 } 496 else 497 { 498 throw new ConstraintNotFound(constraintInfo[_x].constraint_id); 499 } 500 } 501 return _arrayConstraintEvaluator; 502 } 503 504 private Integer [] checkConstraintsToBeDeleted(int[] idsToBeDeleted) throws ConstraintNotFound 505 { 506 final Integer [] _deleteKeys = new Integer [idsToBeDeleted.length]; 507 508 for (int _x = 0; _x < idsToBeDeleted.length; ++_x) 509 { 510 _deleteKeys[_x] = new Integer (idsToBeDeleted[_x]); 511 512 if (!constraints_.containsKey(_deleteKeys[_x])) 513 { 514 throw new ConstraintNotFound(idsToBeDeleted[_x]); 515 } 516 } 517 return _deleteKeys; 518 } 519 520 private void deleteConstraints(Integer [] keys) 521 { 522 for (int _x = 0; _x < keys.length; ++_x) 523 { 524 ConstraintEntry _deletedEntry = (ConstraintEntry) constraints_.remove(keys[_x]); 525 526 removeEventTypeMappingForConstraint(keys[_x], _deletedEntry); 527 } 528 } 529 530 private void removeEventTypeMappingForConstraint(Integer key, ConstraintEntry deletedEntry) 531 { 532 int _eventTypeCount = deletedEntry.getEventTypeCount(); 533 534 for (int _y = 0; _y < _eventTypeCount; ++_y) 535 { 536 EventTypeIdentifier _eventTypeIdentifier = deletedEntry.getEventTypeWrapper(_y); 537 538 List _listOfConstraintEvaluator = (List ) wildcardMap_ 539 .getNoExpansion(_eventTypeIdentifier.getConstraintKey()); 540 541 Iterator _i = _listOfConstraintEvaluator.iterator(); 542 543 while (_i.hasNext()) 546 { 547 ConstraintEntry _constraint = (ConstraintEntry) _i.next(); 548 549 if (_constraint.getConstraintId() == key.intValue()) 550 { 551 _i.remove(); 552 break; 553 } 554 } 555 } 556 } 557 558 public ConstraintInfo[] get_constraints(int[] ids) throws ConstraintNotFound 559 { 560 final Sync _lock = constraintsLock_.readLock(); 561 562 try 563 { 564 _lock.acquire(); 565 try 566 { 567 final ConstraintInfo[] _constraintInfo = new ConstraintInfo[ids.length]; 568 569 for (int _x = 0; _x < ids.length; ++_x) 570 { 571 Integer _key = new Integer (ids[_x]); 572 573 if (constraints_.containsKey(_key)) 574 { 575 _constraintInfo[_x] = ((ConstraintEntry) constraints_.get(_key)) 576 .getConstraintInfo(); 577 } 578 else 579 { 580 throw new ConstraintNotFound(ids[_x]); 581 } 582 } 583 584 return _constraintInfo; 585 } finally 586 { 587 _lock.release(); 588 } 589 } catch (InterruptedException ie) 590 { 591 Thread.currentThread().interrupt(); 592 593 return EMPTY_CONSTRAINT_INFO; 594 } 595 } 596 597 public ConstraintInfo[] get_all_constraints() 598 { 599 try 600 { 601 constraintsLock_.readLock().acquire(); 602 603 try 604 { 605 ConstraintInfo[] _constraintInfo = new ConstraintInfo[constraints_.size()]; 606 607 Iterator _i = constraints_.values().iterator(); 608 609 for (int i = 0; i < _constraintInfo.length; i++) 610 { 611 _constraintInfo[i] = ((ConstraintEntry) _i.next()).getConstraintInfo(); 612 } 613 614 return _constraintInfo; 615 } finally 616 { 617 constraintsLock_.readLock().release(); 618 } 619 } catch (InterruptedException ie) 620 { 621 Thread.currentThread().interrupt(); 622 623 return EMPTY_CONSTRAINT_INFO; 624 } 625 } 626 627 public void remove_all_constraints() 628 { 629 try 630 { 631 constraintsLock_.writeLock().acquire(); 632 633 try 634 { 635 constraints_.clear(); 636 637 wildcardMap_.clear(); 638 639 notifyCallbacks(); 640 } finally 641 { 642 constraintsLock_.writeLock().release(); 643 } 644 } catch (InterruptedException ie) 645 { 646 Thread.currentThread().interrupt(); 647 } 648 } 649 650 public void destroy() 651 { 652 dispose(); 653 } 654 655 658 private Iterator getConstraintsForEvent(Message event) 659 { 660 String _key = event.getConstraintKey(); 661 662 return getIterator(_key); 663 } 664 665 public Iterator getIterator(Object key) 666 { 667 Object [] _entries = wildcardMap_.getWithExpansion(key); 668 669 return new ConstraintIterator(_entries); 670 } 671 672 676 static private class ConstraintIterator implements Iterator 677 { 678 final Object [] arrayOfLists_; 679 680 Iterator current_; 681 682 int currentListIdx_ = 0; 683 684 ConstraintIterator(Object [] arrayOfLists) 685 { 686 arrayOfLists_ = arrayOfLists; 687 688 if (arrayOfLists_.length == 0) 689 { 690 current_ = null; 691 } 692 else 693 { 694 switchIterator(); 695 } 696 } 697 698 private void switchIterator() 699 { 700 current_ = ((List ) arrayOfLists_[currentListIdx_]).iterator(); 701 } 702 703 public boolean hasNext() 704 { 705 return current_ != null && current_.hasNext(); 706 } 707 708 public Object next() 709 { 710 if (current_ == null) 711 { 712 throw new NoSuchElementException (); 713 } 714 715 Object _ret = current_.next(); 716 717 if (!current_.hasNext() && currentListIdx_ < arrayOfLists_.length - 1) 718 { 719 ++currentListIdx_; 720 721 switchIterator(); 722 } 723 724 return _ret; 725 } 726 727 public void remove() 728 { 729 throw NOT_SUPPORTED; 730 } 731 } 732 733 736 private int match_ReadLock(EvaluationContext evaluationContext, Message event) 737 throws UnsupportedFilterableData 738 { 739 try 740 { 741 constraintsLock_.readLock().acquire(); 742 743 try 744 { 745 return match_NoLock(evaluationContext, event); 746 } finally 747 { 748 constraintsLock_.readLock().release(); 749 } 750 } catch (InterruptedException ie) 751 { 752 Thread.currentThread().interrupt(); 753 754 return NO_CONSTRAINTS_MATCH; 755 } 756 } 757 758 private int match_NoLock(EvaluationContext evaluationContext, Message event) 759 throws UnsupportedFilterableData 760 { 761 if (!constraints_.isEmpty()) 762 { 763 Iterator _entries = getConstraintsForEvent(event); 764 765 while (_entries.hasNext()) 766 { 767 ConstraintEntry _entry = (ConstraintEntry) _entries.next(); 768 try 769 { 770 boolean _result = _entry.getFilterConstraint().evaluate(evaluationContext, 771 event).getBool(); 772 773 if (_result) 774 { 775 return _entry.getConstraintId(); 776 } 777 } catch (PropertyDoesNotExistException e) 778 { 779 logger_.info("tried to access non existing Property", e); 782 } catch (EvaluationException e) 783 { 784 logger_.fatalError("Error evaluating filter", e); 785 786 throw new UnsupportedFilterableData(e.getMessage()); 787 } 788 } 789 790 return NO_CONSTRAINTS_MATCH; 791 } 792 793 logger_.info("Filter has no Expressions"); 794 795 return CONSTRAINTS_EMPTY; 796 } 797 798 public boolean match(Any anyEvent) throws UnsupportedFilterableData 799 { 800 return match_internal(anyEvent) >= 0; 801 } 802 803 807 protected int match_internal(Any anyEvent) throws UnsupportedFilterableData 808 { 809 final EvaluationContext _evaluationContext = evaluationContextFactory_ 810 .newEvaluationContext(); 811 812 try 813 { 814 final Message _event = messageFactory_.newMessage(anyEvent); 815 816 try 817 { 818 return match_ReadLock(_evaluationContext, _event); 819 } finally 820 { 821 _event.dispose(); 822 } 823 } finally 824 { 825 _evaluationContext.dispose(); 826 } 827 } 828 829 public boolean match_structured(StructuredEvent structuredevent) 830 throws UnsupportedFilterableData 831 { 832 return match_structured_internal(structuredevent) >= 0; 833 } 834 835 839 protected int match_structured_internal(StructuredEvent structuredEvent) 840 throws UnsupportedFilterableData 841 { 842 final EvaluationContext _evaluationContext = evaluationContextFactory_ 843 .newEvaluationContext(); 844 845 try 846 { 847 final Message _event = messageFactory_.newMessage(structuredEvent); 848 849 try 850 { 851 return match_ReadLock(_evaluationContext, _event); 852 } finally 853 { 854 _event.dispose(); 855 } 856 } finally 857 { 858 _evaluationContext.dispose(); 859 } 860 } 861 862 866 protected int match_typed_internal(Property[] typedEvent) throws UnsupportedFilterableData 867 { 868 final EvaluationContext _evaluationContext = evaluationContextFactory_ 869 .newEvaluationContext(); 870 871 try 872 { 873 final Message _event = messageFactory_.newMessage(typedEvent); 874 875 try 876 { 877 return match_ReadLock(_evaluationContext, _event); 878 } finally 879 { 880 _event.dispose(); 881 } 882 } finally 883 { 884 _evaluationContext.dispose(); 885 } 886 } 887 888 public boolean match_typed(Property[] properties) throws UnsupportedFilterableData 889 { 890 return match_typed_internal(properties) >= 0; 891 } 892 893 public int attach_callback(NotifySubscribe notifySubscribe) 894 { 895 return callbackManager_.attach_callback(notifySubscribe); 896 } 897 898 public void detach_callback(int id) 899 { 900 callbackManager_.detach_callback(id); 901 } 902 903 public int[] get_callbacks() 904 { 905 return callbackManager_.get_callbacks(); 906 } 907 908 private void notifyCallbacks() throws InterruptedException 909 { 910 final Iterator i = constraints_.keySet().iterator(); 911 final List eventTypes = new ArrayList (); 912 913 while (i.hasNext()) 914 { 915 Object key = i.next(); 916 917 ConstraintEntry value = (ConstraintEntry) constraints_.get(key); 918 919 int ets = value.getEventTypeCount(); 920 921 for (int j = 0; j < ets; ++j) 922 { 923 EventTypeWrapper et = value.getEventTypeWrapper(j); 924 925 eventTypes.add(et.getEventType()); 926 } 927 } 928 929 callbackManager_.replaceWith((EventType[]) eventTypes 930 .toArray(EventTypeWrapper.EMPTY_EVENT_TYPE_ARRAY)); 931 } 932 933 public POA _default_POA() 934 { 935 return poa_; 936 } 937 938 public void dispose() 939 { 940 if (isActivated.get()) 941 { 942 deactivate(); 943 944 isActivated.set(false); 945 } 946 947 disposables_.dispose(); 948 } 949 950 public void addDisposeHook(Disposable disposeHook) 951 { 952 disposables_.addDisposable(disposeHook); 953 } 954 955 public Date getLastUsage() 956 { 957 return filterUsageDecorator_.getLastUsage(); 958 } 959 960 public void attemptDispose() 961 { 962 attemptDispose(this, getLastUsage(), maxIdleTime_); 963 } 964 965 static void attemptDispose(Disposable disposable, Date lastUsage, long maxIdleTime) 966 { 967 if (maxIdleTime <= 0) 968 { 969 return; 970 } 971 972 if (lastUsage.getTime() + maxIdleTime < System.currentTimeMillis()) 973 { 974 disposable.dispose(); 975 } 976 } 977 } | Popular Tags |