blob: b71cd7c733fd820d03a8c44867eb2cb652499f10 [file] [log] [blame]
/*******************************************************************************
* 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&lt;&lt;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&lt;&lt;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;
}
}