Skip to content

Instantly share code, notes, and snippets.

@earwin
Created March 20, 2011 19:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save earwin/dfebaf79f5524e6ea8b4 to your computer and use it in GitHub Desktop.
Save earwin/dfebaf79f5524e6ea8b4 to your computer and use it in GitHub Desktop.
package ru.hh.search.core.util.visitor;
import com.google.common.base.Joiner;
public class AmbigousMethodException extends RuntimeException {
public AmbigousMethodException(Class visitorClass, Class visitableClass, Iterable<VisitorMethod> matched) {
super(visitorClass.getSimpleName() + " has ambigous visit() methods for " + visitableClass.getName() + ": " + Joiner.on(", ").join(matched));
}
}
package ru.hh.search.core.util.collections;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import static com.google.common.collect.Maps.newHashMap;
import java.util.Map;
public class CopyOnWriteMap<K, V> implements Map<K, V> {
/*
We're publishing map unsafely, but the map is all-round-final itself, so while
read threads might see a stale map, they always see internally consistent map.
*/
private final Object sync = new Object();
private ImmutableMap<K, V> map = ImmutableMap.of();
public static <K, V> CopyOnWriteMap<K, V> create() {
return new CopyOnWriteMap<K, V>();
}
public boolean isEmpty() {
return map.isEmpty();
}
public int size() {
return map.size();
}
public ImmutableCollection<V> values() {
return map.values();
}
public ImmutableSet<K> keySet() {
return map.keySet();
}
public ImmutableSet<Entry<K, V>> entrySet() {
return map.entrySet();
}
public V get(Object key) {
return map.get(key);
}
public boolean containsValue(Object value) {
return map.containsValue(value);
}
public boolean containsKey(Object key) {
return map.containsKey(key);
}
public void clear() {
synchronized (sync) {
map = ImmutableMap.of();
}
}
public void putAll(Map<? extends K, ? extends V> map) {
synchronized (sync) {
Map<K, V> temporary = newHashMap(this.map);
temporary.putAll(map);
this.map = ImmutableMap.copyOf(temporary);
}
}
public V remove(Object o) {
synchronized (sync) {
Map<K, V> temporary = newHashMap(map);
V value = temporary.remove(o);
map = ImmutableMap.copyOf(temporary);
return value;
}
}
public V put(K k, V v) {
synchronized (sync) {
Map<K, V> temporary = newHashMap(map);
V value = temporary.put(k, v);
map = ImmutableMap.copyOf(temporary);
return value;
}
}
public String toString() {
return map.toString();
}
}
package ru.hh.search.core.util.visitor;
public class NoMatchingMethodException extends RuntimeException {
public NoMatchingMethodException(Class visitorClass, Class visitableClass) {
super("No visit() method on " + visitorClass.getSimpleName() + " accepts " + visitableClass.getName());
}
}
package ru.hh.search.core.util.visitor;
import static com.google.common.collect.Lists.newArrayList;
import java.lang.reflect.Method;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import net.sf.cglib.reflect.FastClass;
import ru.hh.search.core.util.collections.CopyOnWriteMap;
class PerClassDispatcher {
private static final String VISITOR_METHOD_NAME = "visit";
private final FastClass visitorClass;
private final List<VisitorMethod> visitorMethods;
private final Map<Class, VisitorMethod> typeToMethod = CopyOnWriteMap.create();
public PerClassDispatcher(Class visitorClass) {
this.visitorClass = FastClass.create(visitorClass);
this.visitorMethods = newArrayList();
for (Method method : visitorClass.getMethods()) { // Loop over visitor's methods
Class[] params = method.getParameterTypes();
if (!VISITOR_METHOD_NAME.equals(method.getName()) || params.length != 1) // Pick up those with proper signature
continue;
VisitorMethod newMethod = new VisitorMethod(params[0], this.visitorClass.getMethod(method));
// This topologically sorts methods, respecting their parameter's inheritance relation
ListIterator<VisitorMethod> methods = visitorMethods.listIterator();
while (methods.hasNext())
if (methods.next().moreGeneralThan(newMethod)) { // find first method more general than us
methods.previous(); // position ourselves before it
break;
}
methods.add(newMethod); // and insert
}
}
public Object dispatch(Object visitor, Object argument) {
Class argumentClass = argument.getClass();
VisitorMethod method = typeToMethod.get(argumentClass);
if (method == null)
typeToMethod.put(argumentClass, method = resolveMethod(argumentClass));
return method.invoke(visitor, argument);
}
private VisitorMethod resolveMethod(Class visitableClass) {
List<VisitorMethod> matched = newArrayList();
// Look up all matching methods, discarding those that can be compared via inheritance relation
// Topo-sort in constructor guarantees we see most specific methods first
for (VisitorMethod visitorMethod : visitorMethods)
if (visitorMethod.moreGeneralThan(visitableClass) && !visitorMethod.moreGeneralThanAny(matched))
matched.add(visitorMethod);
if (matched.size() == 0)
throw new NoMatchingMethodException(visitorClass.getJavaClass(), visitableClass);
if (matched.size() > 1)
throw new AmbigousMethodException(visitorClass.getJavaClass(), visitableClass, matched);
return matched.get(0);
}
}
package ru.hh.search.core.util.visitor;
import com.google.common.base.Function;
import java.util.Map;
import ru.hh.search.core.util.collections.CopyOnWriteMap;
public class ReflectiveVisitor<T> implements Function<Object, T> {
private static final Map<Class<?>, PerClassDispatcher> dispatcherCache = CopyOnWriteMap.create();
private final Object visitor;
private final PerClassDispatcher dispatcher;
/**
* Use if you extend ReflectiveVisitor. Dispatches over its own 'visit' methods.
*/
protected ReflectiveVisitor() {
this.visitor = this;
dispatcher = getDispatcher(visitor.getClass());
}
/**
* Use to wrap another object and dispatch over its 'visit' methods
*/
public ReflectiveVisitor(Object visitor) {
this.visitor = visitor;
dispatcher = getDispatcher(visitor.getClass());
}
private static PerClassDispatcher getDispatcher(Class<?> visitorClass) {
PerClassDispatcher dispatcher = dispatcherCache.get(visitorClass);
if (dispatcher == null)
dispatcherCache.put(visitorClass, dispatcher = new PerClassDispatcher(visitorClass));
return dispatcher;
}
@SuppressWarnings("unchecked")
public T apply(Object argument) {
return (T) dispatcher.dispatch(visitor, argument);
}
}
package ru.hh.search.core.util.visitor;
import com.google.common.base.Throwables;
import static com.google.common.base.Throwables.propagate;
import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import net.sf.cglib.reflect.FastMethod;
class VisitorMethod {
private final Class type;
private final FastMethod method;
VisitorMethod(Class type, FastMethod method) {
this.type = type;
this.method = method;
}
public Class getType() {
return type;
}
public boolean moreGeneralThan(VisitorMethod method) {
return moreGeneralThan(method.getType());
}
public boolean moreGeneralThan(Class type) {
return this.type.isAssignableFrom(type);
}
public boolean moreGeneralThanAny(Collection<VisitorMethod> methods) {
for (VisitorMethod method : methods)
if (moreGeneralThan(method))
return true;
return false;
}
public Object invoke(Object visitor, Object argument) {
try {
return method.invoke(visitor, new Object[]{argument});
} catch (InvocationTargetException e) {
throw propagate(e.getCause());
}
}
public String toString() {
return type.getSimpleName();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment