-
-
Save earwin/dfebaf79f5524e6ea8b4 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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