Java tutorial
/** * Copyright 2010 CosmoCode GmbH * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package de.cosmocode.palava.core; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.base.Throwables; import com.google.common.collect.AbstractIterator; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.LinkedHashMultimap; import com.google.common.collect.Multimap; import com.google.common.collect.Multimaps; import com.google.common.collect.SetMultimap; import de.cosmocode.collections.Procedure; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Iterator; import java.util.Map.Entry; /** * Default implementation of the {@link Registry} interface. * * @author Willi Schoenborn * @since 2.0 */ final class DefaultRegistry extends AbstractRegistry { private static final Logger LOG = LoggerFactory.getLogger(DefaultRegistry.class); private static final Method TO_STRING; private static final Method EQUALS; private static final Method HASHCODE; static { try { TO_STRING = Object.class.getMethod("toString"); EQUALS = Object.class.getMethod("equals", Object.class); HASHCODE = Object.class.getMethod("hashCode"); } catch (NoSuchMethodException e) { throw new ExceptionInInitializerError(e); } } private final Multimap<Key<? extends Object>, Object> mapping; public DefaultRegistry() { final SetMultimap<Key<? extends Object>, Object> multimap = LinkedHashMultimap.create(); this.mapping = Multimaps.synchronizedSetMultimap(multimap); } @Override public <T> void register(Key<T> key, T listener) { Preconditions.checkNotNull(key, "Key"); Preconditions.checkNotNull(listener, "Listener"); LOG.trace("Registering {} for {}", listener, key); mapping.put(key, listener); } @Override public <T> Iterable<T> getListeners(Key<T> key) { Preconditions.checkNotNull(key, "Key"); @SuppressWarnings("unchecked") final Iterable<T> listeners = (Iterable<T>) mapping.get(key); return Iterables.unmodifiableIterable(listeners); } @Override public <T> Iterable<T> find(final Class<T> type, final Predicate<? super Object> predicate) { Preconditions.checkNotNull(type, "Type"); Preconditions.checkNotNull(predicate, "Predicate"); return new Iterable<T>() { @Override public Iterator<T> iterator() { return new AbstractIterator<T>() { private final Iterator<Entry<Key<?>, Object>> iterator = mapping.entries().iterator(); @Override protected T computeNext() { while (iterator.hasNext()) { final Entry<Key<?>, Object> entry = iterator.next(); final Key<?> key = entry.getKey(); if (key.getType() == type && predicate.apply(key.getMeta())) { @SuppressWarnings("unchecked") final T listener = (T) entry.getValue(); return listener; } } return endOfData(); } }; } }; } @Override public <T> T proxy(final Key<T> key) { return proxy(key, false); } @Override public <T> T silentProxy(Key<T> key) { return proxy(key, true); } private <T> T proxy(Key<T> key, boolean silent) { Preconditions.checkNotNull(key, "Key"); Preconditions.checkArgument(key.getType().isInterface(), "Type must be an interface"); Preconditions.checkArgument(!key.getType().isAnnotation(), "Type must not be an annotation"); final ClassLoader loader = getClass().getClassLoader(); final Class<?>[] interfaces = { key.getType() }; final InvocationHandler handler = new ProxyHandler<T>(key, silent); @SuppressWarnings("unchecked") final T proxy = (T) java.lang.reflect.Proxy.newProxyInstance(loader, interfaces, handler); LOG.debug("Created proxy for {}", key); return proxy; } /** * Inner class allowing to encapsulate proxy invocation handling. * * @param <T> * @author Willi Schoenborn */ private final class ProxyHandler<T> implements InvocationHandler { private final Key<T> key; private final boolean silent; public ProxyHandler(Key<T> key, boolean silent) { this.key = Preconditions.checkNotNull(key, "Key"); this.silent = silent; } @Override public Object invoke(Object proxy, final Method method, final Object[] args) { if (method.equals(TO_STRING)) { return String.format("%s.proxy(%s)", DefaultRegistry.this, key); } else if (method.equals(EQUALS)) { return equals(args[0]); } else if (method.equals(HASHCODE)) { return hashCode(); } else if (method.getReturnType() == void.class) { final Procedure<T> procedure = new Procedure<T>() { @Override public void apply(T listener) { try { method.invoke(listener, args); } catch (IllegalAccessException e) { throw new AssertionError(e); } catch (InvocationTargetException e) { throw propagate(e.getCause(), method.getExceptionTypes()); } } }; if (silent) { DefaultRegistry.this.notifySilently(key, procedure); } else { DefaultRegistry.this.notify(key, procedure); } return null; } else { throw new IllegalStateException(String.format("%s must return void", method)); } } private RuntimeException propagate(Throwable throwable, Class<?>[] types) { Throwables.propagateIfPossible(throwable); @SuppressWarnings("unchecked") final Class<? extends RuntimeException>[] exceptionTypes = Class[].class.cast(types); for (Class<? extends RuntimeException> type : exceptionTypes) { Throwables.propagateIfInstanceOf(throwable, type); } throw Throwables.propagate(throwable); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + getOuterType().hashCode(); result = prime * result + ((key == null) ? 0 : key.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (!(obj instanceof ProxyHandler<?>)) { return false; } final ProxyHandler<?> other = (ProxyHandler<?>) obj; if (!getOuterType().equals(other.getOuterType())) { return false; } if (key == null) { if (other.key != null) { return false; } } else if (!key.equals(other.key)) { return false; } return true; } private DefaultRegistry getOuterType() { return DefaultRegistry.this; } } @Override public <T> void notify(Key<T> key, Procedure<? super T> command) { Preconditions.checkNotNull(key, "Key"); Preconditions.checkNotNull(command, "Command"); LOG.trace("Notifying all listeners for {} using {}", key, command); for (T listener : getListeners(key)) { LOG.trace("notifying {} for {}", listener, key); command.apply(listener); } } @Override public <T> void notifySilent(Key<T> key, Procedure<? super T> command) { notifySilently(key, command); } @Override public <T> void notifySilently(Key<T> key, Procedure<? super T> command) { Preconditions.checkNotNull(key, "Key"); Preconditions.checkNotNull(command, "Command"); LOG.trace("Notifying all listeners for {} using {}", key, command); for (T listener : getListeners(key)) { LOG.trace("notifying {} for {}", listener, key); try { command.apply(listener); /*CHECKSTYLE:OFF*/ } catch (RuntimeException e) { /*CHECKSTYLE:ON*/ LOG.error("Notifying listener failed", e); } } } @Override public <T> boolean remove(Key<T> key, T listener) { Preconditions.checkNotNull(key, "Key"); Preconditions.checkNotNull(listener, "Listener"); LOG.trace("Removing {} from {}", listener, key); return mapping.remove(key, listener); } @Override public <T> boolean remove(T listener) { Preconditions.checkNotNull(listener, "Listener"); LOG.trace("Removing {}", listener); return mapping.values().removeAll(ImmutableSet.of(listener)); } @Override @SuppressWarnings("unchecked") public <T> Iterable<T> removeAll(Key<T> key) { Preconditions.checkNotNull(key, "Key"); LOG.trace("Removing all listeners from {}", key); return (Iterable<T>) mapping.removeAll(key); } }