| /******************************************************************************* |
| * Copyright (c) 2009-2018 SAP AG and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v2.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v20.html |
| * |
| * Contributors: |
| * SAP AG - initial API and implementation |
| ******************************************************************************/ |
| package org.eclipse.ocl.examples.eventmanager.framework; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.eclipse.emf.common.notify.Notification; |
| import org.eclipse.ocl.examples.eventmanager.EventFilter; |
| import org.eclipse.ocl.examples.eventmanager.filters.AndFilter; |
| import org.eclipse.ocl.examples.eventmanager.util.Bag; |
| import org.eclipse.ocl.examples.eventmanager.util.BagImpl; |
| import org.eclipse.ocl.examples.eventmanager.util.CompositeBag; |
| |
| /** |
| * EventFilterTables are used to connect {@link EventFilter event filters} and |
| * {@link Registration Registrations}. For each filter type, a special |
| * subtype of this class exists which handles exactly all instances of this |
| * filter type. It is responsible for computing all possible registration |
| * candidates that match an event in the context of the filter type it handles. |
| * Additionally it encapsulates the knowledge how to get the necessary |
| * information from the passed event (see {@link #getAffectedObject}). |
| * |
| * @author Daniel Vocke, Axel Uhl |
| */ |
| public abstract class TableForEventFilter { |
| |
| /** |
| * list of all (negated) registration that are stored in this table, keyed |
| * by the bit set identifying the tables in which the registrations in the |
| * value set are stored. This means in particular that the array only |
| * contains values at bit set indexes that have the bit for this table set. |
| */ |
| private final Bag<Registration>[] completeNoBag; |
| |
| /** |
| * The total number of filter tables managed by the enclosing |
| * {@link RegistrationManagerTableBased} instance |
| */ |
| private final int numberOfFilterTables; |
| |
| private final Bag<Registration>[] emptyRegistrationArray; |
| |
| private enum SetSelection { YES, NO }; |
| |
| /** |
| * index which is needed for deregistration purposes |
| */ |
| protected final HashMap<Registration, Object> filterCriteriaByRegistration = new HashMap<Registration, Object>(); |
| |
| /** |
| * registrations are contained in a FilterTableEntry. This structure is |
| * needed to find the registrations for a filter criterion which is being |
| * retrieved by an event. |
| */ |
| protected final Map<Object, FilterTableEntry> tableEntryByFilterCriterion = new HashMap<Object, FilterTableEntry>(); |
| |
| @SuppressWarnings("unchecked") |
| protected TableForEventFilter(int numberOfFilterTables) { |
| this.numberOfFilterTables = numberOfFilterTables; |
| completeNoBag = (Bag<Registration>[]) new Bag<?>[1<<numberOfFilterTables]; |
| emptyRegistrationArray = (Bag<Registration>[]) new Bag<?>[1<<numberOfFilterTables]; |
| } |
| |
| /** |
| * This method encapsulates the knowledge which information that is |
| * contained by the passed event is of interest in the context of the |
| * current EventFilterTable. |
| */ |
| public abstract Object getAffectedObject(Notification event); |
| |
| protected String criterionToString(Object criterion) { |
| return criterion.toString(); |
| } |
| |
| /** |
| * stores the passed {@link Registration}. The Registration will be stored |
| * as "interested in events meeting the filterCriterion of the passed event |
| * in the context of the appropriate EventFilterTable subclass" |
| * |
| * @param filter |
| * expected to be an elementary leaf predicate in an {@link AndFilter} |
| * in a filter tree in DNF |
| */ |
| @SuppressWarnings("unchecked") |
| void register(EventFilter filter, Registration registration) { |
| FilterTableEntry entry = tableEntryByFilterCriterion.get(filter.getFilterCriterion()); |
| if (entry == null) { |
| entry = new FilterTableEntry(numberOfFilterTables); |
| tableEntryByFilterCriterion.put(filter.getFilterCriterion(), entry); |
| } |
| |
| if (filter.isNegated()) { |
| entry.addNegatedRegistrations(registration); |
| int tableBitSet = registration.getBitSetForTablesRegisteredWith(); |
| Bag<Registration> completeNoBagForTableCombination = completeNoBag[tableBitSet]; |
| if (completeNoBagForTableCombination == null) { |
| completeNoBagForTableCombination = new BagImpl<Registration>(); |
| completeNoBag[tableBitSet] = completeNoBagForTableCombination; |
| } |
| completeNoBagForTableCombination.add(registration); |
| } else { |
| entry.addRegistrations(registration); |
| } |
| |
| // Maintain inverse Map for better performance when deregistering |
| Object criterion = filterCriteriaByRegistration.get(registration); |
| if (criterion == null) { |
| filterCriteriaByRegistration.put(registration, filter.getFilterCriterion()); |
| } else { |
| // Registration used for other criterion already, store in a list (happens rarely) |
| if (criterion instanceof List<?>) { |
| ((List<Object>)criterion).add(filter.getFilterCriterion()); |
| } else { |
| List<Object> criteria = new ArrayList<Object>(2); |
| criteria.add(criterion); |
| criteria.add(filter.getFilterCriterion()); |
| filterCriteriaByRegistration.put(registration, criteria); |
| } |
| } |
| } |
| |
| /** |
| * removes all entries affecting the passed registration. |
| * |
| * @param registration |
| */ |
| @SuppressWarnings("unchecked") |
| void deregister(Registration registration) { |
| Object criterion = filterCriteriaByRegistration.get(registration); |
| if (criterion != null) { |
| // registration is stored in this table |
| if (criterion instanceof List<?>) { |
| for (Object filterCriterion : (List<Object>) criterion) { |
| _deregister(registration, filterCriterion); |
| } |
| } else { |
| _deregister(registration, criterion); |
| } |
| filterCriteriaByRegistration.remove(registration); |
| } |
| } |
| |
| /** |
| * this method does the main work for {@link #deregister(Registration)} |
| * @param registration |
| * @param criterion |
| */ |
| private void _deregister(Registration registration, Object criterion){ |
| FilterTableEntry entry = tableEntryByFilterCriterion.get(criterion); |
| if (entry != null) { |
| entry.remove(registration); |
| if (entry.isEmpty()) { |
| tableEntryByFilterCriterion.remove(criterion); |
| } |
| } |
| Bag<Registration> completeNoBagEntry = completeNoBag[registration.getBitSetForTablesRegisteredWith()]; |
| if (completeNoBagEntry != null) { // could be that there were no negated registrations in this table combination |
| if (completeNoBagEntry.remove(registration)) { |
| if (completeNoBagEntry.isEmpty()) { |
| completeNoBag[registration.getBitSetForTablesRegisteredWith()] = null; |
| } |
| } |
| } |
| } |
| |
| /** |
| * Fetches the "Yes" entries for the criterion specific to this table, |
| * extracted from <code>event</code>. See also |
| * {@link FilterTableEntry#getYesSets()} and |
| * {@link #getAffectedObject(Notification)}. The result is an array with |
| * <code>1<<numberOfBitSetsWithAtLeastOneRegistration</code> elements |
| * where each element represents the bit set corresponding to the element's |
| * index in the array. The caller must not modify the array returned. |
| */ |
| Bag<Registration>[] getYesSetsFor(Notification event, |
| int numberOfBitSetsWithAtLeastOneRegistration, |
| int[] bitSetsWithAtLeastOneRegistration) { |
| return getSetsFor(event, SetSelection.YES, |
| numberOfBitSetsWithAtLeastOneRegistration, |
| bitSetsWithAtLeastOneRegistration); |
| } |
| |
| /** |
| * Fetches the "No" entries for the criterion specific to this table, |
| * extracted from <code>event</code>. See also |
| * {@link FilterTableEntry#getNoSets()} and |
| * {@link #getAffectedObject(Notification)}. The result is an array with |
| * <code>1<<numberOfBitSetsWithAtLeastOneRegistration</code> elements |
| * where each element represents the bit set corresponding to the element's |
| * index in the array. The caller must not modify the array returned. |
| */ |
| Bag<Registration>[] getNoSetsFor(Notification event, |
| int numberOfBitSetsWithAtLeastOneRegistration, |
| int[] bitSetsWithAtLeastOneRegistration) { |
| return getSetsFor(event, SetSelection.NO, |
| numberOfBitSetsWithAtLeastOneRegistration, |
| bitSetsWithAtLeastOneRegistration); |
| } |
| |
| @SuppressWarnings("unchecked") |
| private Bag<Registration>[] getSetsFor(Notification event, |
| SetSelection yesNoSelection, |
| int numberOfBitSetsWithAtLeastOneRegistration, |
| int[] bitSetsWithAtLeastOneRegistration) { |
| // returns the filter criterion which is of interest in context of the current EventFilterTable |
| Object affectedFilterTableEntryKeys = getAffectedObject(event); |
| Bag<Registration>[] resultBagArray; |
| // will contain all affected FilterTableEntries that have a registration |
| Set<FilterTableEntry> filterTableEntries = getFilterTableEntries(affectedFilterTableEntryKeys); |
| switch (filterTableEntries.size()) { |
| case 0: |
| resultBagArray = emptyRegistrationArray; |
| break; |
| case 1: |
| if (yesNoSelection == SetSelection.YES) { |
| resultBagArray = filterTableEntries.iterator().next().getYesSets(); |
| } else { |
| resultBagArray = filterTableEntries.iterator().next().getNoSets(); |
| } |
| break; |
| default: |
| resultBagArray = (Bag<Registration>[]) new Bag<?>[1<<numberOfFilterTables]; |
| for (int i = 0; i < numberOfBitSetsWithAtLeastOneRegistration; i++) { |
| List<Bag<Registration>> bagList = new ArrayList<Bag<Registration>>(); |
| for (FilterTableEntry filterTableEntry : filterTableEntries) { |
| Bag<Registration> yesSetForTableEntryAtBitSet; |
| if (yesNoSelection == SetSelection.YES) { |
| yesSetForTableEntryAtBitSet = filterTableEntry.getYesSet(bitSetsWithAtLeastOneRegistration[i]); |
| } else { |
| yesSetForTableEntryAtBitSet = filterTableEntry.getNoSet(bitSetsWithAtLeastOneRegistration[i]); |
| } |
| if (yesSetForTableEntryAtBitSet != null) { |
| bagList.add(yesSetForTableEntryAtBitSet); |
| } |
| } |
| CompositeBag<Registration> compositeBagAtBitSet = null; |
| if (!bagList.isEmpty()) { |
| compositeBagAtBitSet = new CompositeBag<Registration>( |
| (Bag<Registration>[]) bagList.toArray(new Bag<?>[0])); |
| } |
| resultBagArray[bitSetsWithAtLeastOneRegistration[i]] = compositeBagAtBitSet; |
| } |
| } |
| return resultBagArray; |
| } |
| |
| /** |
| * @return a list with unique entries; this is guaranteed even in case |
| * <code>affectedFilterTableEntryKeys</code> is a non-{@link Set} |
| * collection by projecting it into a {@link Set} in this case. |
| */ |
| private Set<FilterTableEntry> getFilterTableEntries(Object affectedFilterTableEntryKeys) { |
| Set<FilterTableEntry> filterTableEntries = Collections.emptySet(); |
| if (affectedFilterTableEntryKeys instanceof Collection<?>) { |
| if (!(affectedFilterTableEntryKeys instanceof Set<?>)) { |
| // only consider distinct elements, therefore project into a set |
| affectedFilterTableEntryKeys = new HashSet<Object>((Collection<?>) affectedFilterTableEntryKeys); |
| } |
| // Some EventFilterTables return multiple matching criteria. For example the ClassFilterTable (including |
| // Subclasses) returns more than one matching row if there were registrations for a class and its superclass |
| // when the subclass was changed. |
| if (!tableEntryByFilterCriterion.isEmpty()) { |
| filterTableEntries = new HashSet<FilterTableEntry>(); |
| for (Object criterion : (Set<?>) affectedFilterTableEntryKeys) { |
| FilterTableEntry fte = tableEntryByFilterCriterion.get(criterion); |
| if (fte != null) { |
| filterTableEntries.add(fte); |
| } |
| } |
| } |
| } else if (affectedFilterTableEntryKeys != null) { |
| FilterTableEntry tableEntry = tableEntryByFilterCriterion.get(affectedFilterTableEntryKeys); |
| if (tableEntry != null) { // Perhaps there is no registration for the affected object? |
| filterTableEntries = Collections.singleton(tableEntry); |
| } |
| } |
| return filterTableEntries; |
| } |
| |
| protected boolean isEmpty() { |
| if (!tableEntryByFilterCriterion.isEmpty()) { |
| return false; |
| } |
| for (Bag<Registration> completeNoBagEntry : completeNoBag) { |
| if (completeNoBagEntry != null) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| |
| boolean isRegistered(Registration reg){ |
| return filterCriteriaByRegistration.containsKey(reg); |
| } |
| |
| /** |
| * Each type of {@link TableForEventFilter} is directly associated to a |
| * filter type, but some <code>MoinEventFilters</code> support additional |
| * modifiers that affect the filtering ( for example the |
| * <code>includeCompositions</code> flag on <code>InstanceFilterTable</code> |
| * or the <code>includeSubclasses</code> flag on <code>ClassFilter</code>). |
| * In those cases, there will be more than one instance of the |
| * EventFilterTable and in order to be able to determine the right instance |
| * for a given <code>MoinEventFilter</code>, both, the |
| * <code>MoinEventFilter</code> and the <code>EventFilterTable</code> must |
| * implement a <code>getIdentifier()</code> method. The default |
| * implementation simply returns its {@link Class}, but filters and their |
| * tables which support modifying flags return a {@link List} containing the |
| * {@link Class} and all modifiers. |
| * |
| * @return an Identifier that allows associating the instance to a filter |
| * type. |
| */ |
| public abstract Class<? extends EventFilter> getIdentifier(); |
| |
| public String toString() { |
| StringBuilder result = new StringBuilder(); |
| result.append(getClass().getSimpleName()); |
| result.append(": "); |
| boolean appendedSomeNoSetStuff = false; |
| for (int i = 0; i < completeNoBag.length; i++) { |
| if (completeNoBag[i] != null) { |
| appendedSomeNoSetStuff = true; |
| result.append(", completeNoSet[" + i + "].size()="); |
| result.append(completeNoBag[i].size()); |
| } |
| } |
| if (appendedSomeNoSetStuff) { |
| result.append(", "); |
| } |
| result.append("FilterTableByCriterion=(\n"); |
| boolean first = true; |
| for (Object criterion : tableEntryByFilterCriterion.keySet()) { |
| if (!first) { |
| result.append(",\n"); |
| } else { |
| first = false; |
| } |
| result.append(" "); |
| result.append(criterionToString(criterion)); |
| result.append(":\n"); |
| result.append(tableEntryByFilterCriterion.get(criterion)); |
| } |
| result.append(")"); |
| return result.toString(); |
| } |
| |
| /** |
| * The registrations registered negatedly, grouped by the bit sets |
| * representing the set of tables in which the registrations from the set |
| * are registered |
| */ |
| public Bag<Registration>[] getCompleteNoBag() { |
| return completeNoBag; |
| } |
| } |