/******************************************************************************* * Copyright (c) 2009, 2014 SAP AG and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * SAP AG - initial API and implementation ******************************************************************************/ package org.eclipse.ocl.examples.eventmanager.framework; import java.lang.ref.Reference; import java.lang.ref.WeakReference; import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.WeakHashMap; import java.util.logging.Logger; import org.eclipse.emf.common.notify.Adapter; import org.eclipse.emf.common.notify.Notification; import org.eclipse.ocl.examples.eventmanager.EventFilter; import org.eclipse.ocl.examples.eventmanager.EventManagerFactory; import org.eclipse.ocl.examples.eventmanager.filters.AbstractEventFilter; import org.eclipse.ocl.examples.eventmanager.filters.AndFilter; import org.eclipse.ocl.examples.eventmanager.filters.LogicalOperationFilter; import org.eclipse.ocl.examples.eventmanager.filters.NotFilter; import org.eclipse.ocl.examples.eventmanager.filters.OrFilter; import org.eclipse.ocl.examples.eventmanager.util.Bag; import org.eclipse.ocl.examples.eventmanager.util.Statistics; /** * The <code>RegistrationManager</code> is responsible for storing the listeners and their registrations and for computing the set * of affected listeners for a certain event. <br> * The <code>RegistrationManager</code> has knowledge about several things: <br> * <ul> * <li>it knows the event types that have to be forwarded to certain <code>EventFilterTables</code></li> * <li>It knows the filter type that belongs to a table.</li> * <li>It knows the registrations that belong to a listener.</li> * </ul> * <br> * The first two points are defined in the {@link #init()} method which is implemented in the subclass * {@link RegistrationManagerTableBased} because originally there also were other * subtypes of <code>RegistrationManager</code> (e.g. the GlobalRegistrationManager). <br> * This knowledge enables the <code>RegistrationManager</code> to fill the <code>EventFilterTables</code> with new registrations * and to compute all listeners that are registered to a certain event. The knowledge of the registrations that belong to a * listener is primary for performance improvement at deregistration time. The * {@link org.eclipse.ocl.examples.eventmanager.framework.TableForEventFilter} provide registrations for an event in * one context. When an event is being fired, the <code>RegistrationManager</code> will know which <code>EventFilterTables</code> * to ask and can collect all registrations from all affected <code>EventFilterTables</code>. The <code>RegistrationManager</code> * has the overview and can merge these candidates into a consolidated set of registrations. Afterwards these Registrations are * converted into listeners that will have to be notified. * * @author Daniel Vocke (D044825), Axel Uhl (D043530) */ public class RegistrationManagerTableBased { private Logger logger = Logger.getLogger(RegistrationManagerTableBased.class.getName()); // uncomment the following lines in case you want to record statistics about the minimum table size; // but pay attention: this method runs in the innermost loop of the event manager, and even a method // call to an empty method may consume some time // /** // * group ID for {@link Statistics} capturing the minimum table sizes during event handling // */ // public static final String GROUP_ID_MINIMUM_TABLE_SIZE = "minimumTableSize*1000000"; // needed to compute the affected registrations for an event private final HashMap<Integer, Set<TableForEventFilter>> tablesByEventType = new HashMap<Integer, Set<TableForEventFilter>>(); /* * needed for registering purposes. The key may consist of either java.lang.Class or a list containing the class and * additional identifying information */ private final HashMap<Object, TableForEventFilter> tableByFilterType = new HashMap<Object, TableForEventFilter>(); /** * During {@link #register(AbstractEventFilter, WeakReference, ListenerTypeEnum) registration}, weak references * to adapters are passed in. To enable {@link #deregister(Adapter) deregistration} by direct references * to the adapter, this field keeps the mapping between the adapters and their weak references. It does * so in a {@link WeakHashMap} such that again no strong references to the adapter are introduced by * the event manager framework. */ private final WeakHashMap<Adapter, Set<Reference<? extends Adapter>>> adaptersToWeakRefs = new WeakHashMap<Adapter, Set<Reference<? extends Adapter>>>(); /** * maps the weak references to the adapters passed to * {@link #register(AbstractEventFilter, WeakReference, ListenerTypeEnum)} to the * registration sets constructed for their event filter; when implicit * de-registration occurs due to the adapter getting garbage collected, this * structure is used to clean up the registrations. */ private final Map<Reference<? extends Adapter>, List<RegistrationSet>> registrationSetByListenerReference = new HashMap<Reference<? extends Adapter>, List<RegistrationSet>>(); /** * Maintains the registrations keyed by their {@link AndFilter}. It is used to find existing registrations * matching an {@link AndFilter} occurring in a filter tree for which a new {@link RegistrationSet} is to * be assembled and added to the event manager's tables. This enables efficient re-use of registrations. */ private Map<AndFilter, Registration> allRegistrations = new HashMap<AndFilter, Registration>(); /** * The elements 0..{@link #numberOfBitSetsWithAtLeastOneRegistration-1} store those bit set values for which * {@link #allRegistrations} has at least one registration with that {@link Registration#getBitSetForTablesRegisteredWith() * bit set}. The array may need to be reorganized when registrations come and go. Registrations for so far unused * bit sets are added at index {@link #numberOfBitSetsWithAtLeastOneRegistration} and the attribute * {@link #numberOfBitSetsWithAtLeastOneRegistration} is incremented by one. When deregistering, the array is * reconstructed from {@link #allRegistrations}. */ private int[] bitSetsWithAtLeastOneRegistration; private int numberOfBitSetsWithAtLeastOneRegistration; /** * Invariants: * <p> * <code>filterTypeToBitMask.get(allTables[i].getIdentifier()) = 2<<i</code> * <p> * <code>filterTypeToBitMask.keySet().equals(allTables.getIdentifier())</code> * * In other words: the identifying classes of all tables stored in this list occur as keys in {@link #filterTypeToBitMask}, * and the order in which the tables occur in this list matches with the bit number of the bit mask returned by * {@link #filterTypeToBitMask} as value for the key being the respective table's {@link TableForEventFilter#getIdentifier() * identifier} */ private TableForEventFilter[] allTables; // The EventTypeFilterTable will also be used with custom events protected TableForEventFilter eventTypeFilterTable = null; // a table with negated registrations always has a contribution to an event // (except the negated registration covers exactly the current event) protected Set<TableForEventFilter> tablesWithNegatedRegistrations = new HashSet<TableForEventFilter>(); /** * Maps the class of a filter to the bit mask it has in finding the correct array position * in an array of registration sets */ private Map<Class<? extends EventFilter>, Integer> filterTypeToBitMask; /** * Maps the filter tables from {@link #allTables} to the integer positions they have in {@link #allTables} * for fast look-up. */ private final Map<TableForEventFilter, Integer> tableToIndexInAllTables = new HashMap<TableForEventFilter, Integer>(); public RegistrationManagerTableBased() { init(); } /** * The implementation of the <code>init()</code> method initialises the members above. * Performs the "configuration" of the RegistrationManager. */ protected void init() { filterTypeToBitMask = new HashMap<Class<? extends EventFilter>, Integer>(); @SuppressWarnings("unchecked") Class<? extends TableForEventFilter>[] tableTypes = (Class<? extends TableForEventFilter>[]) new Class<?>[] { TableForStructuralFeatureFilter.class, TableForClassFilter.class, TableForClassFilterIncludingSubClasses.class, TableForEventTypeFilter.class, TableForContainmentFilter.class, TableForNewValueClassFilter.class, TableForOldValueClassFilter.class, TableForNewValueClassFilterIncludingSubclasses.class, TableForOldValueClassFilterIncludingSubclasses.class}; createAllTables(tableTypes.length); int i=0; for (Class<? extends TableForEventFilter> tableType : tableTypes) { TableForEventFilter table; try { Constructor<? extends TableForEventFilter> constructor = tableType.getConstructor(Integer.TYPE); table = constructor.newInstance(tableTypes.length); } catch (Exception e) { throw new RuntimeException("Didn't find constructor(int) on table type "+tableType.getSimpleName(), e); } setUsualEvents(table); registerTable(table, i++); } } protected void createAllTables(int size) { allTables = new TableForEventFilter[size]; bitSetsWithAtLeastOneRegistration = new int[1<<size]; numberOfBitSetsWithAtLeastOneRegistration = 0; } private void setUsualEvents(TableForEventFilter table) { addTableForEventType(table, Notification.SET); addTableForEventType(table, Notification.UNSET); addTableForEventType(table, Notification.ADD); addTableForEventType(table, Notification.REMOVE); addTableForEventType(table, Notification.ADD_MANY); addTableForEventType(table, Notification.REMOVE_MANY); addTableForEventType(table, Notification.MOVE); } /** * registers a listener with the passed filter expression. The returned object of type <code>RegistrationHandle</code> is * intended to be used as handle for deregistration puposes. * * @param filterTree * the filter expression (may also be a tree) * @param listener * the listener to register */ public synchronized void register(EventFilter filterTree, WeakReference<? extends Adapter> listener, ListenerTypeEnum listenerType) { // adjustFilter() has to be called before the dnf is formed, but there has to be at least one logicaloperationfilter at // the top if (!(filterTree instanceof OrFilter || filterTree instanceof AndFilter)) { EventFilter topOfTree = EventManagerFactory.eINSTANCE.createAndFilterFor(filterTree); filterTree = topOfTree; } OrFilter filterInNormalForm = getDisjunctiveNormalForm((LogicalOperationFilterImpl) filterTree); // visit the whole filter tree and add each atomic filter to its corresponding filterTable. List<Registration> registrations = new LinkedList<Registration>(); for (EventFilter filter : filterInNormalForm.getOperands()) { AndFilter andFilter = (AndFilter) filter; Registration reg = allRegistrations.get(andFilter); if (reg == null) { reg = createRegistration(andFilter); } registrations.add(reg); } RegistrationSet result = new RegistrationSet(listener, listenerType, registrations); addRegistrationForListener(result, listener); } /** * Creates the registration and adds it to {@link #allRegistrations} */ private Registration createRegistration(AndFilter andFilter) { int[] requiredMatchesPerTable = new int[allTables.length]; Map<EventFilter, TableForEventFilter> filterTablesToRegisterWith = getFilterTablesToRegisterWith( andFilter, requiredMatchesPerTable); Registration result = new Registration( getBitSet(filterTablesToRegisterWith.values()), andFilter, requiredMatchesPerTable); allRegistrations.put(andFilter, result); updateBitSetsWithAtLeastOneRegistration(result); for (Entry<EventFilter, TableForEventFilter> filterTableEntry : filterTablesToRegisterWith .entrySet()) { filterTableEntry.getValue().register(filterTableEntry.getKey(), result); } return result; } /** * Ensures that {@link #bitSetsWithAtLeastOneRegistration} contains <code>registration</code>. * {@link Registration#getBitSetForTablesRegisteredWith()} in elements 0..{@link #numberOfBitSetsWithAtLeastOneRegistration} * -1. It may have to append the bit set value to the end. In this case, {@link #numberOfBitSetsWithAtLeastOneRegistration} * will be incremented by one. */ private void updateBitSetsWithAtLeastOneRegistration(Registration registration) { int bitset = registration.getBitSetForTablesRegisteredWith(); for (int i=0; i<numberOfBitSetsWithAtLeastOneRegistration; i++) { if (bitSetsWithAtLeastOneRegistration[i] == bitset) { return; } } bitSetsWithAtLeastOneRegistration[numberOfBitSetsWithAtLeastOneRegistration++] = bitset; } /** * @param requiredMatchesPerTable * Expected to contain only 0 values upon invocation. Updated by * this method with the number of matches that the * {@link Registration} to be created from the results of this * method expects to find in the respective table. Indices in * this array correspond to the indices in {@link #allTables}. * When this method completes normally, this array will contain 0 * for all tables (see also {@link #tableToIndexInAllTables}) * that don't occur in the results {@link Map#values() values} * and at least 1 for all tables that do occur. */ private Map<EventFilter, TableForEventFilter> getFilterTablesToRegisterWith( AndFilter andFilter, int[] requiredMatchesPerTable) { // determine tables to register with; needed already for construction of // Registration Map<EventFilter, TableForEventFilter> filterTablesToRegisterWith = new HashMap<EventFilter, TableForEventFilter>(); // The following table is used only for the possibility of error reporting LogicalOperationFilterImpl level1OfTree = andFilter; for (EventFilter leafOfTree : level1OfTree.getOperands()) { TableForEventFilter filterTable = getFilterTable(leafOfTree); if (filterTable == null) { throw new IllegalArgumentException("no table for type " + leafOfTree.getClass() + " in RegistryManager defined"); } requiredMatchesPerTable[tableToIndexInAllTables.get(filterTable)]++; filterTablesToRegisterWith.put(leafOfTree, filterTable); } return filterTablesToRegisterWith; } private void addRegistrationForListener(RegistrationSet registrationSet, Reference<? extends Adapter> listenerRef) { // registrationsByListener is a WeakHashMap, so direct references to listeners can be stored Adapter adapter = listenerRef.get(); if (adapter == null) { logger.warning("Registered adapter got GCed: "+listenerRef); } else { Set<Reference<? extends Adapter>> listenerSet = adaptersToWeakRefs.get(adapter); if (listenerSet == null) { listenerSet = new HashSet<Reference<? extends Adapter>>(); adaptersToWeakRefs.put(adapter, listenerSet); } listenerSet.add(listenerRef); List<RegistrationSet> registrationSetList = registrationSetByListenerReference.get(listenerRef); if (registrationSetList == null) { registrationSetList = new ArrayList<RegistrationSet>(); registrationSetByListenerReference.put(listenerRef, registrationSetList); } registrationSetList.add(registrationSet); } } /** * From a collection of filter tables, computes the bit set representing this table combination. * * @see RegistrationManagerTableBased#filterTypeToBitMask */ private int getBitSet(Collection<TableForEventFilter> tables) { int result = 0; for (TableForEventFilter table : tables) { result |= filterTypeToBitMask.get(table.getIdentifier()); } return result; } private void deregister(RegistrationSet rs) { for (Registration reg : ((RegistrationSet) rs).getRegistrations()) { if (reg.removeRegistrationSet(rs)) { // the registration lost its last registration set; remove from all tables deregister(reg); } } } /** * Removes all registrations that belong to the passed listener from all <code>EventFilterTables</code>. The listener will not * receive any events any more. This method is synchronized because there must be no changes to the FilterTables while they * are working. * * @param listener * the listener to deregister */ public synchronized void deregister(Adapter listener) { Set<Reference<? extends Adapter>> set = adaptersToWeakRefs.get(listener); if (set != null) { for (Reference<? extends Adapter> listenerRef : set) { deregister(listenerRef); } } } public synchronized void deregister(Reference<? extends Adapter> listenerRef) { Adapter adapter = listenerRef.get(); if (adapter != null) { // not yet collected adaptersToWeakRefs.remove(adapter); } List<RegistrationSet> registrationSets = registrationSetByListenerReference.get(listenerRef); if (registrationSets != null) { for (RegistrationSet registrationSet : registrationSets) { deregister(registrationSet); } registrationSetByListenerReference.remove(listenerRef); } } /** * This method returns a {@link java.util.Collection} of {@link Adapter}s that were registered for the passed event. * * @param event * @return a Collection of listeners that are registered for the passed event */ public Collection<WeakReference<? extends Adapter>> getListenersFor(Notification event) { // this method does the main work, it computes the registrations from all EventFilterTables Collection<Registration> registrations = getRegistrationsFor(event); Collection<WeakReference<? extends Adapter>> result = new LinkedList<WeakReference<? extends Adapter>>(); // this collection is needed in order to remove duplicates. (it is possible, that two OR connected filter criteria matched. // In this case, two different registrations will be returned (which both point to the same RegistrationSet), but // the listener will expect to get notified only once. Set<RegistrationSet> registrationSetsAddedSoFar = new HashSet<RegistrationSet>(); for (Registration reg : registrations) { for (RegistrationSet registrationSet : reg.getRegistrationSets()) { if (!registrationSetsAddedSoFar.contains(registrationSet)) { registrationSetsAddedSoFar.add(registrationSet); result.add(registrationSet.getListener()); } } } return result; } /** * Finds matching {@link Registration}s for <code>event</code>. */ private Set<Registration> getRegistrationsFor(Notification event) { Statistics.getInstance().begin("getRegistrationsFor", event); // insert is O(1) for linked list; no cloning / copying required as it grows Set<Registration> result = new HashSet<Registration>(); // for the following two lists, the index corresponds to the table index in allTables @SuppressWarnings("unchecked") Bag<Registration>[][] yesSetsForTables = (Bag<Registration>[][]) new Bag<?>[allTables.length][]; @SuppressWarnings("unchecked") Bag<Registration>[][] noSetsForTables = (Bag<Registration>[][]) new Bag<?>[allTables.length][]; int i=0; for (TableForEventFilter table : allTables) { Bag<Registration>[] yesSetsForTable = table.getYesSetsFor(event, numberOfBitSetsWithAtLeastOneRegistration, bitSetsWithAtLeastOneRegistration); yesSetsForTables[i] = yesSetsForTable; Bag<Registration>[] noSetsForTable = table.getNoSetsFor(event, numberOfBitSetsWithAtLeastOneRegistration, bitSetsWithAtLeastOneRegistration); noSetsForTables[i] = noSetsForTable; i++; } // loop over all table combinations (as bit set counter) for which registrations exist HashSet<Registration> startSetToReuseToAvoidHashSetCreation = new HashSet<Registration>(); for (int bitSetIndex=0; bitSetIndex<numberOfBitSetsWithAtLeastOneRegistration; bitSetIndex++) { addIntersectionOverTablesInBitset_Of_YesSetUnitedWithAllNoSetMinusNoSet(bitSetsWithAtLeastOneRegistration[bitSetIndex], yesSetsForTables, noSetsForTables, result, startSetToReuseToAvoidHashSetCreation); } Statistics.getInstance().end("getRegistrationsFor", event); return result; } private void addIntersectionOverTablesInBitset_Of_YesSetUnitedWithAllNoSetMinusNoSet( int bitSetForTableCombination, Bag<Registration>[][] yesSetsForTables, Bag<Registration>[][] noSetsForTables, Set<Registration> result, HashSet<Registration> startSetToReuseToAvoidHashSetCreation) { // first, determine maximum size of (yesSet "union" allNo \ no) for each // table in the bit set; // if the maximum size of one of them is 0, we're done; otherwise, start with the table promising the // smallest size int tableWithMinSize = getTableWithMinSizeForIntersection(bitSetForTableCombination, yesSetsForTables); // construct the set to start with: Collection<Registration> resultForTablesInBitSet = getStartCollectionFromMinSizeTable(bitSetForTableCombination, yesSetsForTables, noSetsForTables, tableWithMinSize, startSetToReuseToAvoidHashSetCreation); if (!resultForTablesInBitSet.isEmpty()) { // now retain only those that are also in the (yesSet "union" allNo \ no) for all other tables in the bit set for (int i = allTables.length - 1; i >= 0; i--) { if (i != tableWithMinSize // don't intersect with start set again; wouldn't hurt except for performance && (bitSetForTableCombination & (1 << i)) != 0) { // loop over tables in bit set only Iterator<Registration> resultIter = resultForTablesInBitSet.iterator(); while (resultIter.hasNext()) { Registration next = resultIter.next(); if (!is_InYesOrCompleteNo_And_NotInNo_OfTable(next, i, bitSetForTableCombination, yesSetsForTables, noSetsForTables)) { resultIter.remove(); } } } } } result.addAll(resultForTablesInBitSet); } private Collection<Registration> getStartCollectionFromMinSizeTable( int bitSetForTableCombination, Bag<Registration>[][] yesSetsForTables, Bag<Registration>[][] noSetsForTables, int tableWithMinSize, HashSet<Registration> resultForTablesInBitSet) { resultForTablesInBitSet.clear(); Bag<Registration> yesSetForMinSizeTable = yesSetsForTables[tableWithMinSize][bitSetForTableCombination]; Bag<Registration> allNoForMinSizeTable = allTables[tableWithMinSize].getCompleteNoBag()[bitSetForTableCombination]; Bag<Registration> noSetForMinSizeTable = noSetsForTables[tableWithMinSize][bitSetForTableCombination]; // add those registrations that got exactly as many matches in the table as they require if (yesSetForMinSizeTable != null) { for (Registration r : yesSetForMinSizeTable) { if ((noSetForMinSizeTable == null || !noSetForMinSizeTable.contains(r)) && r.getMatchesRequiredForTable(tableWithMinSize) == yesSetForMinSizeTable.count(r) + (allNoForMinSizeTable == null ? 0 : allNoForMinSizeTable.count(r))) { resultForTablesInBitSet.add(r); } } } if (allNoForMinSizeTable != null) { for (Registration r : allNoForMinSizeTable) { if ((noSetForMinSizeTable == null || !noSetForMinSizeTable.contains(r)) && r.getMatchesRequiredForTable(tableWithMinSize) == (yesSetForMinSizeTable == null ? 0 : yesSetForMinSizeTable.count(r)) + allNoForMinSizeTable.count(r)) { resultForTablesInBitSet.add(r); } } } return resultForTablesInBitSet; } private int getTableWithMinSizeForIntersection(int bitSetForTableCombination, Bag<Registration>[][] yesSetsForTables) { int[] maxSizes = new int[allTables.length]; // only those elements from the bit set will be populated int minSize = Integer.MAX_VALUE; // minimum value that got explicitly set in sizes[] int tableWithMinSize = -1; // position of minimum value in sizes[]; that's the table to start with for intersection int tableBit = 1<<(allTables.length-1); for (int i=allTables.length-1; i>=0; i--) { if ((bitSetForTableCombination & tableBit) != 0) { // table identified by tableBit occurs in the combination represented by bitSetForTableCombination Bag<Registration> yesSet = yesSetsForTables[i][bitSetForTableCombination]; Bag<Registration> allNo = allTables[i].getCompleteNoBag()[bitSetForTableCombination]; maxSizes[i] = (yesSet == null ? 0 : yesSet.size()) + (allNo == null ? 0 : allNo.size()); if (maxSizes[i] < minSize) { // will be executed at least once because we start with minSize=Integer.MAX_VALUE and // there has to be at least one non-zero bit in the bit set minSize = maxSizes[i]; tableWithMinSize = i; } } tableBit >>= 1; } // uncomment the following line in case you want to record statistics about the minimum table size; // but pay attention: this method runs in the innermost loop of the event manager, and even a method // call to an empty method may consume some time // Statistics.getInstance().record(GROUP_ID_MINIMUM_TABLE_SIZE, bitSetForTableCombination, 1000000*minSize); return tableWithMinSize; } private boolean is_InYesOrCompleteNo_And_NotInNo_OfTable( Registration registration, int table, int bitSetForTableCombination, Bag<Registration>[][] yesSetsForTable, Bag<Registration>[][] noSetsForTables) { Bag<Registration> yesSet = yesSetsForTable[table][bitSetForTableCombination]; Bag<Registration> allNo = allTables[table].getCompleteNoBag()[bitSetForTableCombination]; Bag<Registration> noSet = noSetsForTables[table][bitSetForTableCombination]; // add those registrations that got exactly as many matches in the table as they require return ((yesSet != null) && ((noSet == null || !noSet.contains(registration)) && registration.getMatchesRequiredForTable(table) == yesSet.count(registration) + (allNo == null ? 0 : allNo.count(registration)))) || ((allNo != null) && ((noSet == null || !noSet.contains(registration)) && registration.getMatchesRequiredForTable(table) == (yesSet == null ? 0 : yesSet.count(registration)) + allNo.count(registration))); } /** * @param listener * @return whether the listener is registered */ public boolean isListenerRegistered(Adapter listener) { return adaptersToWeakRefs.containsKey(listener); } /** * Removes the passed registration from all affected FilterTables. * * @param registration * the registration to remove */ private void deregister(Registration registration) { for (TableForEventFilter table : allTables) { table.deregister(registration); if (table.isEmpty()) { tablesWithNegatedRegistrations.remove(table); } } allRegistrations.remove(registration.getAndFilter()); rebuildBitSetsWithAtLeastOneRegistration(); } private void rebuildBitSetsWithAtLeastOneRegistration() { Set<Integer> bitSetsInUse = new HashSet<Integer>(); for (Registration r : allRegistrations.values()) { bitSetsInUse.add(r.getBitSetForTablesRegisteredWith()); } numberOfBitSetsWithAtLeastOneRegistration = bitSetsInUse.size(); int i=0; for (int bitSetInUse : bitSetsInUse) { bitSetsWithAtLeastOneRegistration[i++] = bitSetInUse; } } /** * this is a private helper method. unfortunately there is no 1 to 1 mapping of event filter instances and EventFilterTable * instances because some of the filters have modifiers like <code> * includeSubclasses</code>. In this case, there is a seperate EventFilterTable instance for each value of the modifier. this * method encapsulates this knowledge and always returns the right <code>FilterTable</code> * * @param filter * the filter instance to find the correnponding FilterTable for * @return the FilterTable which is reponsible for the passed filter instance */ private TableForEventFilter getFilterTable(EventFilter filter) { return tableByFilterType.get(filter.getClass()); } /** * This method fills the <code>tablesByEventType</code> member with the passed parameters. It is only used in the * <code>init()</code> method of subclasses. * * @param table * @param eventType */ protected void addTableForEventType(TableForEventFilter table, Integer eventType) { Set<TableForEventFilter> tables = tablesByEventType.get(eventType); if (tables == null) { tables = new HashSet<TableForEventFilter>(); tablesByEventType.put(eventType, tables); } tables.add(table); } /** * Transforms the (sub-)tree into disjunctive normal form. This special form is needed internally for the following * processing. The tree is not cloned before processing, so the clients that plan to reuse their FilterTrees will have to * clone them using {@link #clone()} before registering to the EventFramework or invoking this method. * * @return a filter tree in disjunctive normal form which has exactly the same semantics like the original tree */ public static OrFilter getDisjunctiveNormalForm(LogicalOperationFilterImpl filter) { // perhaps the tree is in DNF yet? if (isInDisjunctiveNormalForm(filter)) return (OrFilter) filter; LogicalOperationFilterImpl result = filter; // eliminate negations and then expand the tree until the disjunctive // normalform is reached result = (LogicalOperationFilterImpl) eliminateNegations(result); result = (LogicalOperationFilterImpl) getExpandedSubTree(null, result); // expandSubTree might also produce a conjunctive normal form => // transform again if (!isInDisjunctiveNormalForm(result)) result = (LogicalOperationFilterImpl) getExpandedSubTree(null, result); else return (OrFilter) result; if (getDepth(result) < 2) { // bring simple filters to disjunctive normalform if (result instanceof OrFilter) { /* * This is currently an OrFilter which connects the leaves of the tree => between the OrFilter and each leaf, an * AndFilter is inserted. */ LogicalOperationFilterImpl orfilter = new OrFilter(); for (EventFilter operand : result.getOperands()) { LogicalOperationFilterImpl tmp = new AndFilter(operand); orfilter.addOperand(tmp); } result = orfilter; } else if (result instanceof AndFilter) { /* * This is an AndFilter which connects the leafs => put an OrFilter on the top of the tree */ LogicalOperationFilterImpl tmp = new OrFilter(result); result = tmp; } else if (result instanceof NotFilter) { throw new IllegalStateException("Elimination of NotFilters failed"); } } if (!isInDisjunctiveNormalForm(result)) { if(result instanceof OrFilter){ /* * there might be primitives left as direct children --> encapsulate them into AndFilters * a | b | (c & d) -- > (a &) | (b &) | (c & d) */ Set<EventFilter> oldO = result.getOperands(); LogicalOperationFilterImpl orfilter = new OrFilter(); for(EventFilter f: oldO){ if(!(f instanceof LogicalOperationFilter)){ orfilter.addOperand(EventManagerFactory.eINSTANCE.createAndFilterFor(f)); }else{ orfilter.addOperand(f); } } result = orfilter; if (!isInDisjunctiveNormalForm(result)) { throw new IllegalStateException("Could not create disjunctiv normal form"); } }else{ throw new IllegalStateException("Could not create disjunctiv normal form"); } } return (OrFilter) result; } /** * Tests whether the tree is in disjunctive normal form yet. * * @return true if the tree is in disjunctive normal form. */ private static boolean isInDisjunctiveNormalForm(EventFilter filter) { /* * "this" has to be an OrFilter which connects AndFilters which connect Non-LogicaloperationFilters */ if (!(filter instanceof OrFilter)) return false; for (EventFilter operand : ((LogicalOperationFilterImpl) filter).getOperands()) { if (!(operand instanceof AndFilter)) return false; for (EventFilter operandsOperand : ((LogicalOperationFilterImpl) operand).getOperands()) { if (operandsOperand instanceof OrFilter || operandsOperand instanceof AndFilter) return false; } } return true; } /** * This method traverses the tree recursively and pushes negation to the leafs of the tree using "De Morgan". When the first * <code>NotFilter</code> is reached, the traversing is being continued using {@link #getNegatedSubTree()}. * * @return a filter tree without <code>NotFilter</code>s, which has exactly the same semantics as the original tree. */ private static EventFilter eliminateNegations(EventFilter filter) { // Check if a NotFilter was shall be negated if (filter instanceof NotFilter) { // DeMorgan: negate the Terminals (Filter) and replace the connecting OR // by AND and vice versa (is done in getNegatedSubTree()) EventFilter operand = ((LogicalOperationFilterImpl) filter).getOperands().iterator().next(); // substitue this by the negated subtree return getNegatedSubTree(operand); } // Check if a LogicalOperationFilter shall be negated if (filter instanceof LogicalOperationFilterImpl) { // clone list List<EventFilter> oldOperands = new ArrayList<EventFilter>(((LogicalOperationFilterImpl) filter).getOperands()); // clear original list ((LogicalOperationFilterImpl) filter).clearOperands(); // refill original list for (EventFilter operand : oldOperands) addOperand(((LogicalOperationFilterImpl) filter), eliminateNegations(operand)); // no substitution of the filter needed return filter; } // Normal filter are unchanged return filter; } /** * this method multiplies the boolean expression which the tree represents out in order to get the expanded version. This * processing is needed in order to achieve a disjunctive normal form. This method is responsible for the recursive * dispatching an the simplifying of the expanded tree. The real expansion is done in the {@link #multiplyOut(List, int)} * method. * * @return the expanded subtree which did not change any semantics. */ static EventFilter getExpandedSubTree(LogicalOperationFilterImpl parent, EventFilter filter) { if (!(filter instanceof LogicalOperationFilterImpl)) { return filter; } LogicalOperationFilterImpl lof = (LogicalOperationFilterImpl) filter; // recursively expand subtree // clone operands Set<EventFilter> oldOperands = new HashSet<EventFilter>(lof.getOperands().size()); oldOperands.addAll(lof.getOperands()); // clear "new" operands lof.clearOperands(); /* * shrink degenerated trees, e.g. an AndFilter that contains only one OrFilter which contains filters is shrinked to the * AndFilter which then contains the OrFilter's operands */ if (oldOperands.size() == 1 && oldOperands.iterator().next() instanceof LogicalOperationFilterImpl) { if(oldOperands.iterator().next() instanceof AndFilter){ lof = new AndFilter(); }else{ lof = new OrFilter(); } oldOperands = ((LogicalOperationFilterImpl) oldOperands.iterator().next()).getOperands(); } for (EventFilter operand : oldOperands) { EventFilter expandedSubtree = getExpandedSubTree(lof, operand); if (lof.getClass().equals(expandedSubtree.getClass())) { /* * simplify: due to commutativity of logical ORs and ANDs, the operands of the subtree can be added to this's * operands if the composite LogicalOperationFilter is of the same type */ lof.addOperands(((LogicalOperationFilterImpl) expandedSubtree).getOperands()); } else { addOperand(lof, expandedSubtree); } } boolean isDNFyet = (parent == null && lof.getClass().equals(OrFilter.class)); boolean willBeShrinkedIfNotMultipliedOut = (parent != null && parent.getClass().equals(lof.getClass())); if (isDNFyet || willBeShrinkedIfNotMultipliedOut) return lof; else return multiplyOut(lof, new LinkedList<LogicalOperationFilterImpl>(), 0); } static long getLeafCount(EventFilter f){ if(f instanceof LogicalOperationFilterImpl){ long count =0l; for(EventFilter o: ((LogicalOperationFilterImpl) f).getOperands()){ count +=getLeafCount(o); } return count; } return 1l; } /** * transforms a subtree from e.g. (a | b) & (c | d) to (a & c) | (a & d) | (b & c) | (b & d) where a,b,c,d represent instances * of <code>MoinEventFilter</code>s. * * @param intermediateResult * an empty list - this param is only needed internally * @param index * start with 0, this param is only needed internally * @return the expanded tree. */ private static LogicalOperationFilterImpl multiplyOut(LogicalOperationFilterImpl filter, List<LogicalOperationFilterImpl> intermediateResult, int index) { // System.out.println("Depth: "+ getDepth(filter) + " Leaves: "+getLeafCount(filter)); List<EventFilter> operands = new ArrayList<EventFilter>(filter.getOperands()); if (operands.size() < 2) return filter; /* * asuming a simple expression (a | b) & (c | d) & (e | f) */ if (index == 0) { // first check, if multiplying out the subtree is possible boolean moPossible = false; for (EventFilter operand : operands) { if (operand instanceof LogicalOperationFilterImpl) { moPossible = true; break; // one contained LogicalOperationFilter is enough } } if (!moPossible) return filter; Set<EventFilter> tmp1 = null; if (operands.get(0) instanceof LogicalOperationFilterImpl) tmp1 = ((LogicalOperationFilterImpl) operands.get(0)).getOperands(); else { tmp1 = new HashSet<EventFilter>(1); tmp1.add(operands.get(0)); } Set<EventFilter> tmp2 = null; if (operands.get(1) instanceof LogicalOperationFilterImpl) tmp2 = ((LogicalOperationFilterImpl) operands.get(1)).getOperands(); else { tmp2 = new HashSet<EventFilter>(1); tmp2.add(operands.get(1)); } /* * take the first 2 expressions ( (a | b) & (c | d) ) and transform them to (a & c) | (a & d) | (b & c) | (b & d) */ LogicalOperationFilterImpl lof = null; for (EventFilter filter1 : tmp1) { for (EventFilter filter2 : tmp2) { if (filter instanceof AndFilter) { lof = new AndFilter(filter1, filter2); } else { lof = new OrFilter(filter1, filter2); } intermediateResult.add(lof); } } return multiplyOut(filter, intermediateResult, 2); } // exit condition if (index >= operands.size()) { /* * the intermediate result is just a list of small trees they have to be connected using a LogicalOperationFilter. */ LogicalOperationFilterImpl result; try { result = (LogicalOperationFilterImpl) getSubstitutionForLogicalOperation(filter); } catch (Exception e) { result = null; } for (LogicalOperationFilterImpl lof : intermediateResult) addOperand(result, lof); return result; } Set<EventFilter> currentOperands = null; if (operands.get(index) instanceof LogicalOperationFilterImpl) { currentOperands = ((LogicalOperationFilterImpl) operands.get(index)).getOperands(); } else { currentOperands = new HashSet<EventFilter>(1); currentOperands.add(operands.get(index)); } /* * for each operand in (e | f) clone the intermediate result ((a & c) | (a & d) | (b & c) | (b & d)) and append e and f * respectively to each clone. => (a & c & e) | (a & d & e) | (b & c & e) | (b & d & e) | (a & c & f) | (a & d & f) | (b & * c & f) | (b & d & f) */ List<LogicalOperationFilterImpl> irClone = new ArrayList<LogicalOperationFilterImpl>(intermediateResult.size()); irClone.addAll(intermediateResult); intermediateResult.clear(); for (EventFilter currentOperand : currentOperands) { for (LogicalOperationFilterImpl currentIntermediateResult : irClone) { LogicalOperationFilterImpl clone = (LogicalOperationFilterImpl) currentIntermediateResult.clone(); addOperand(clone, currentOperand); intermediateResult.add(clone); } } return multiplyOut(filter, intermediateResult, index + 1); } private static LogicalOperationFilterImpl getSubstitutionForLogicalOperation(LogicalOperationFilterImpl filter) { if (filter instanceof AndFilter) { return new OrFilter(); } if (filter instanceof OrFilter) { return new AndFilter(); } throw new IllegalStateException("Unknown logical substitution for " + filter.getClass()); } /** * Computes the depth of the subtree. NotFilters are ignored while computing the depth. * * @return the maximal depth of the tree. */ public static int getDepth(LogicalOperationFilterImpl filter) { return getDepth(filter, 1); } private static int getDepth(LogicalOperationFilterImpl filter, int level) { int result = 0; for (EventFilter currentFilter : filter.getOperands()) { if (currentFilter instanceof LogicalOperationFilterImpl) { int nextLevel = level + 1; // Do not count NotFilter if (currentFilter instanceof NotFilter) nextLevel = level; result = Math.max(result, getDepth((LogicalOperationFilterImpl) currentFilter, nextLevel)); } } return result + level; } /** * This operation applies the rules of De Morgan in order to return a filter tree which represents the negation of the * subtree. */ private static EventFilter getNegatedSubTree(EventFilter filter) { if (filter instanceof NotFilter) { EventFilter result = new ArrayList<EventFilter>(((LogicalOperationFilterImpl) filter).getOperands()).get(0); // trick in oder to continue the recursion without negating the operand return getNegatedSubTree(getNegatedSubTree(result)); } if (filter instanceof LogicalOperationFilterImpl) { // replace the OrFilter(this) by an AndFilter and negate its operands LogicalOperationFilterImpl result = getSubstitutionForLogicalOperation((LogicalOperationFilterImpl) filter); for (EventFilter operand : ((LogicalOperationFilterImpl) filter).getOperands()) { EventFilter negatedOperand = getNegatedSubTree(operand); // simplify due to commutativity of AND/OR conjunction if (negatedOperand.getClass().equals(result.getClass())) { result.addOperands(((LogicalOperationFilterImpl) negatedOperand).getOperands()); } else { addOperand(result, negatedOperand); } } return result; } // By default simply change the "negated" flag if the EventFilter ((AbstractEventFilter) filter).setNegated(!filter.isNegated()); return filter; } /** * adds the operand to the list if it is not contained yet. Equality of operands is computet by the equality of their * contained members or operands, not by their reference! This means, that adding two empty AndFilters will result in only one * AndFilter added for example. Clients that want to evade that functionality can use <code>getOperands().add(..)</code> * instead. * * @param operand * the operand to add * @return true if the operand was added */ private static boolean addOperand(LogicalOperationFilterImpl filter, EventFilter operand) { // prevent doubles filter.addOperand(operand); return true; } public String toString() { StringBuilder result = new StringBuilder(); result.append("RegistrationManager: (\n"); boolean first = true; int i=0; for (TableForEventFilter table : allTables) { if (!first) { result.append(",\n"); } else { first = false; } result.append("("); result.append(i++); result.append(") "); result.append(table); } result.append(')'); return result.toString(); } /** * Creates an array that has length <code>2^{@link #filterTypeToBitMask}.size()</code> so that any combination of * bit mask used as value in {@link #filterTypeToBitMask} can be used as index into such an array. */ @SuppressWarnings("unchecked") protected Set<Registration>[] createRegistrationSetArray() { return (Set<Registration>[]) new Set<?>[1<<filterTypeToBitMask.size()]; } protected void registerTable(TableForEventFilter table, int posInAllTables) { allTables[posInAllTables] = table; tableByFilterType.put(table.getIdentifier(), table); filterTypeToBitMask.put(table.getIdentifier(), 1<<filterTypeToBitMask.size()); tableToIndexInAllTables.put(table, posInAllTables); } /** * When passing a bit set as returned by {@link #getBitSet(Collection)}, returns the collection * of filter tables identified by this bit set. */ protected Collection<TableForEventFilter> getTablesForBitSet(int bitSet) { Collection<TableForEventFilter> result = new ArrayList<TableForEventFilter>(allTables.length); int i=1; for (TableForEventFilter table : allTables) { if ((bitSet & i) != 0) { result.add(table); } i <<= 1; } return result; } /** * Mostly for debugging and analysis. Expects the {@link AndFilter}s to be stored in the {@link Registration}s. * Scans through all {@link Registration} objects known by this event manager and determines how many overlapping * distinct registrations with equal {@link AndFilter} there are. This is a prerequisite to judging how much * of a performance improvement we may gain if we try to collate registrations with equal filters. */ public int redundantFilters() { Map<AndFilter, Registration> distinctAndFilters = new HashMap<AndFilter, Registration>(); int result = 0; for (Registration ar : allRegistrations.values()) { if (checkForRedundantFilterAndUpdateMapCorrespondingly((Registration) ar, distinctAndFilters)) { result++; } } return result; } private boolean checkForRedundantFilterAndUpdateMapCorrespondingly(Registration r, Map<AndFilter, Registration> distinctAndFilters) { AndFilter andFilter = r.getAndFilter(); boolean result; Registration knownRegistrationWithEqualAndFilter = distinctAndFilters.get(andFilter); if (knownRegistrationWithEqualAndFilter == null) { distinctAndFilters.put(andFilter, r); result = false; } else { result = knownRegistrationWithEqualAndFilter != r; if (result && knownRegistrationWithEqualAndFilter.getBitSetForTablesRegisteredWith() != r.getBitSetForTablesRegisteredWith()) { throw new RuntimeException("Error: registrations with equal AndFilter have different bit set, saying they would end up in different tables"); } } return result; } }