| /******************************************************************************* |
| * Copyright (c) 2009, 2011 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; |
| } |
| } |