ServiceListDispatcherMixin.java
/***************************************************************************
Copyright 2014 Emily Estes
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 net.metanotion.util;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/** This dispatcher uses reflection to find method parameters marked with the {@link net.metanotion.util.Service}
annotation and lookup them at message evaluation time from an instance of {@link net.metanotion.util.Unknown}.
@param <O> The type of the class the child dispatcher's messages are evaluated against.
*/
public final class ServiceListDispatcherMixin<O> implements Dispatcher<Unknown, Map.Entry<String,Iterable>> {
private final Class<O> klazz;
private final Dispatcher<O,Map.Entry<String,Iterable>> disp;
private final Map<String,Iterable<ParamGetter>> methodMap = new HashMap<>();
/** Create a new service list dispatcher.
@param klazz the class to find parameters marked with {@literal @}Service annotations and construct a
dispatcher against.
@param disp The dispatcher to delegate the final parameter list with.
*/
public ServiceListDispatcherMixin(final Class<O> klazz, final Dispatcher<O,Map.Entry<String,Iterable>> disp) {
this.klazz = klazz;
this.disp = disp;
final ArrayList<ParamGetter> fieldGetters = new ArrayList<>();
for(final Field f: klazz.getFields()) {
methodMap.put(f.getName(), fieldGetters);
}
for(final Method m: klazz.getMethods()) {
final ArrayList<ParamGetter> getters = new ArrayList<>();
final Class[] pTypes = m.getParameterTypes();
final Annotation[][] parameterAnnotations = m.getParameterAnnotations();
for(int i=0; i < pTypes.length; i++) {
ParamGetter g = ServiceListDispatcherMixin.NP_INSTANCE;
for(final Annotation a: parameterAnnotations[i]) {
if(a instanceof Service) { g = new ServiceGetter(pTypes[i]); }
}
getters.add(g);
}
methodMap.put(m.getName(), getters);
}
}
private interface ParamGetter {
public Object getParam(Unknown u, Iterator<Object> params);
}
private static final ParamGetter NP_INSTANCE = new ParamGetter() {
@Override public Object getParam(final Unknown u, final Iterator<Object> params) { return params.next(); }
};
private static final class ServiceGetter implements ParamGetter {
private final Class klazz;
public ServiceGetter(final Class klazz) { this.klazz = klazz; }
@Override public Object getParam(final Unknown u, final Iterator<Object> params) {
params.next();
return u.lookupInterface(klazz);
}
}
private final class Msg implements Message<Unknown> {
private final Map.Entry<String,Iterable> data;
private final Iterable<ParamGetter> getters;
public Msg(final Map.Entry<String,Iterable> data, final Iterable<ParamGetter> getters) {
this.data = data;
this.getters = getters;
}
@Override public Object call(final Unknown u) {
final ArrayList<Object> params = new ArrayList<>();
final Iterator it = data.getValue().iterator();
for(final ParamGetter g: getters) { params.add(g.getParam(u, it)); }
final Message<O> m = disp.dispatch(new Pair<String,Iterable>(data.getKey(), params));
return m.call(u.lookupInterface(m.receiverType()));
}
@Override public Class<Unknown> receiverType() { return Unknown.class; }
}
@Override public Message<Unknown> dispatch(final Map.Entry<String,Iterable> data) {
final Iterable<ParamGetter> m = methodMap.get(data.getKey());
if(m == null) { throw new NullPointerException("Unknown method '" + data.getKey() + "'"); }
return new Msg(data, m);
}
}