package sk.stuba.fiit.perconik.core.services.listeners;
import java.util.Map;
import java.util.Set;
import com.google.common.base.Optional;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import sk.stuba.fiit.perconik.core.IllegalListenerClassException;
import sk.stuba.fiit.perconik.core.Listener;
import sk.stuba.fiit.perconik.core.ListenerInstantiationException;
import sk.stuba.fiit.perconik.utilities.MoreSets;
import sk.stuba.fiit.perconik.utilities.reflect.ReflectionException;
import sk.stuba.fiit.perconik.utilities.reflect.accessor.AccessorConstructionException;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Maps.newConcurrentMap;
final class StandardListenerProvider extends AbstractListenerProvider {
private final BiMap<String, Class<? extends Listener>> nameToImplementation;
private final Map<Class<? extends Listener>, Listener> implementationToListenerCache;
private final ListenerProvider parent;
StandardListenerProvider(final Builder builder) {
this.nameToImplementation = HashBiMap.create(builder.nameToImplementation);
this.implementationToListenerCache = newConcurrentMap();
this.parent = builder.parent.or(ListenerProviders.superListenerProvider());
}
public static class Builder implements ListenerProvider.Builder {
final BiMap<String, Class<? extends Listener>> nameToImplementation;
Optional<ListenerProvider> parent;
public Builder() {
this.nameToImplementation = HashBiMap.create();
this.parent = Optional.absent();
}
public Builder add(final Class<? extends Listener> implementation) {
checkNotNull(implementation);
this.nameToImplementation.put(implementation.getName(), implementation.asSubclass(Listener.class));
return this;
}
public Builder addAll(final Iterable<Class<? extends Listener>> implementations) {
for (Class<? extends Listener> type: implementations) {
this.add(type);
}
return this;
}
public Builder parent(final ListenerProvider parent) {
checkState(!this.parent.isPresent());
this.parent = Optional.of(checkNotNull(parent));
return this;
}
public StandardListenerProvider build() {
return new StandardListenerProvider(this);
}
}
public static Builder builder() {
return new Builder();
}
@Override
protected ClassLoader loader() {
return this.getClass().getClassLoader();
}
public <L extends Listener> L forClass(final Class<L> implementation) {
Listener listener = this.implementationToListenerCache.get(cast(implementation));
if (listener != null) {
return implementation.cast(listener);
}
L instance;
try {
instance = StaticListenerLookup.forClass(implementation).get();
} catch (ReflectionException e) {
Throwable[] suppressions = e.getSuppressed();
Exception cause;
if (suppressions.length == 1 && suppressions[0] instanceof AccessorConstructionException) {
cause = new IllegalListenerClassException(suppressions[0]);
} else {
cause = new ListenerInstantiationException(e);
}
return this.parentForClass(implementation, cause);
}
if (!this.nameToImplementation.containsValue(implementation)) {
this.nameToImplementation.put(implementation.getName(), implementation);
}
this.implementationToListenerCache.put(implementation, instance);
assert this.nameToImplementation.size() >= this.implementationToListenerCache.size();
return instance;
}
public Class<? extends Listener> loadClass(final String name) throws ClassNotFoundException {
Class<? extends Listener> type = this.nameToImplementation.get(name);
if (type != null) {
return type;
}
try {
return cast(this.load(name));
} catch (Exception cause) {
return this.parentLoadClass(name, cause);
}
}
public Set<Class<? extends Listener>> classes() {
return MoreSets.newHashSet(this.nameToImplementation.values(), this.parent.classes());
}
public ListenerProvider parent() {
return this.parent;
}
}