/*
* Copyright (C) 2006-2016 DLR, Germany
*
* All rights reserved
*
* http://www.rcenvironment.de/
*/
package de.rcenvironment.toolkit.modules.objectbindings.internal;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import de.rcenvironment.toolkit.modules.introspection.api.StatusCollectionContributor;
import de.rcenvironment.toolkit.modules.introspection.api.StatusCollectionRegistry;
import de.rcenvironment.toolkit.modules.objectbindings.api.ObjectBindingsConsumer;
import de.rcenvironment.toolkit.modules.objectbindings.api.ObjectBindingsService;
import de.rcenvironment.toolkit.utils.common.AutoCreationMap;
import de.rcenvironment.toolkit.utils.internal.StringUtils;
import de.rcenvironment.toolkit.utils.text.TextLinesReceiver;
/**
* For simplicity, all methods of this class are currently synchronized. This should not add significant blocking, as object bindings are
* not expected to change rapidly, and consumer callback methods should terminate quickly. If this still turns out to be a problem in some
* application, locking could be reworked to a per-binding-class approach.
*
* @author Robert Mischke
*
*/
public class ObjectBindingsServiceImpl implements ObjectBindingsService {
private final Map<Class<?>, ObjectBindingsConsumer<?>> consumers = new HashMap<>();
private final AutoCreationMap<Class<?>, List<Object>> bindingLists =
new AutoCreationMap<Class<?>, List<Object>>() {
@Override
protected List<Object> createNewEntry(Class<?> key) {
return new LinkedList<>();
}
};
private final Log log = LogFactory.getLog(getClass());
public ObjectBindingsServiceImpl(StatusCollectionRegistry statusCollectionRegistry) {
statusCollectionRegistry.addContributor(new StatusCollectionContributor() {
@Override
public void printUnfinishedOperationsInformation(TextLinesReceiver receiver) {}
@Override
public void printDefaultStateInformation(TextLinesReceiver receiver) {
printStatus(receiver);
}
@Override
public String getUnfinishedOperationsDescription() {
return null;
}
@Override
public String getStandardDescription() {
return "Object Bindings";
}
});
}
@Override
public synchronized <T> void addBinding(Class<T> bindingClass, T instance, Object owner) {
ObjectBindingsConsumer<T> existingConsumer = getExistingConsumer(bindingClass);
// TODO check for duplicate binding
bindingLists.get(bindingClass).add(instance);
log.debug(StringUtils.format("Added binding for %s: %s", bindingClass.getName(), instance.toString()));
if (existingConsumer != null) {
log.debug(StringUtils.format("Reporting new binding for %s to existing consumer %s", bindingClass.getName(),
existingConsumer.toString()));
existingConsumer.addInstance(instance);
}
}
@Override
public synchronized <T> void removeBinding(Class<T> bindingClass, T instance) {
List<Object> bindingList = bindingLists.get(bindingClass);
// note: List#remove() will *not* work properly instead; see method comment
if (!removeByIdentity(instance, bindingList)) {
throw new IllegalStateException("This object instance was not registered/bound before: " + instance);
}
log.debug(StringUtils.format("Removed binding for %s: %s", bindingClass.getName(), instance.toString()));
ObjectBindingsConsumer<T> existingConsumer = getExistingConsumer(bindingClass);
if (existingConsumer != null) {
log.debug(StringUtils.format("Reporting removal of binding for %s to existing consumer %s", bindingClass.getName(),
existingConsumer.toString()));
existingConsumer.removeInstance(instance);
}
}
@Override
public synchronized void removeAllBindingsOfOwner(Object owner) {
log.debug("Note: batch removal of bindings not implemented yet"); // TODO (p2) implement
}
@Override
@SuppressWarnings("unchecked")
public synchronized <T> void setConsumer(Class<T> bindingClass, ObjectBindingsConsumer<T> newConsumer) {
List<T> boundInstances = (List<T>) bindingLists.get(bindingClass);
// unregister instances from old consumer, unless null
ObjectBindingsConsumer<T> previousConsumer = getExistingConsumer(bindingClass);
if (previousConsumer != null) {
for (Object instance : boundInstances) {
previousConsumer.removeInstance((T) instance);
}
}
// unregister instances with new consumer, unless null
if (newConsumer != null) {
for (Object instance : boundInstances) {
newConsumer.addInstance((T) instance);
}
}
log.debug(StringUtils.format("Setting the consumer of %s instances to %s",
bindingClass.getName(), newConsumer)); // note: consumer may be null
consumers.put(bindingClass, newConsumer);
}
@SuppressWarnings("unchecked")
private <T> ObjectBindingsConsumer<T> getExistingConsumer(Class<T> bindingClass) {
return (ObjectBindingsConsumer<T>) consumers.get(bindingClass);
}
private <T> boolean removeByIdentity(T instance, List<Object> bindingList) {
// this looping approach is required instead of List#remove() to ensure object *identity* is used;
// otherwise, equal (but not identical) bindings will get mixed up - misc_ro
Iterator<Object> iterator = bindingList.iterator();
while (iterator.hasNext()) {
if (iterator.next() == instance) {
iterator.remove();
return true;
}
}
return false;
}
private synchronized void printStatus(TextLinesReceiver receiver) {
for (Entry<Class<?>, List<Object>> e : bindingLists.getImmutableShallowCopy().entrySet()) {
final Class<?> clazz = e.getKey();
final String consumedBy;
final ObjectBindingsConsumer<?> consumer = consumers.get(clazz);
if (consumer != null) {
consumedBy = consumer.getClass().getName();
} else {
consumedBy = "<none>";
}
receiver.addLine(StringUtils.format("%s (consumed by %s)", clazz.getName(), consumedBy));
for (Object instance : e.getValue()) {
receiver.addLine(StringUtils.format(" - %s", instance.getClass().getName()));
}
}
}
}