package eu.stratosphere.util.reflect; import java.lang.reflect.AccessibleObject; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Member; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.esotericsoftware.kryo.Kryo; import com.esotericsoftware.kryo.KryoCopyable; import com.esotericsoftware.kryo.KryoSerializable; import com.esotericsoftware.kryo.io.Input; import com.esotericsoftware.kryo.io.Output; public abstract class DynamicInvokable<MemberType extends Member, DeclaringType, ReturnType> implements KryoSerializable, KryoCopyable<DynamicInvokable<MemberType, DeclaringType, ReturnType>> { public static final Log LOG = LogFactory.getLog(DynamicInvokable.class); private transient Map<Signature, MemberType> cachedSignatures = new HashMap<Signature, MemberType>(); private transient Map<Signature, MemberType> originalSignatures = new HashMap<Signature, MemberType>(); private final String name; private final static List<Class<? extends Signature>> SignatureTypes = Arrays.asList(Signature.class, ArraySignature.class, VarArgSignature.class); public DynamicInvokable(final String name) { this.name = name; } /** * Initializes DynamicInvokable. */ DynamicInvokable() { this.name = null; } public void addSignature(final MemberType member) { final Class<?>[] parameterTypes = this.getSignatureTypes(member); Signature signature; if (member instanceof AccessibleObject) ((AccessibleObject) member).setAccessible(true); if (this.isVarargs(member)) signature = new VarArgSignature(parameterTypes); else signature = new Signature(parameterTypes); this.originalSignatures.put(signature, member); // Cache flushing might be more intelligent in the future. // However, how often are method signatures actually added after first // invocation? this.cachedSignatures.clear(); this.cachedSignatures.putAll(this.originalSignatures); } /* * (non-Javadoc) * @see com.esotericsoftware.kryo.KryoCopyable#copy(com.esotericsoftware.kryo.Kryo) */ @SuppressWarnings("unchecked") @Override public DynamicInvokable<MemberType, DeclaringType, ReturnType> copy(final Kryo kryo) { final DynamicInvokable<MemberType, DeclaringType, ReturnType> copy = kryo.newInstance(this.getClass()); ReflectUtil.setField(copy, DynamicInvokable.class, "name", this.name); copy.originalSignatures.putAll(this.originalSignatures); return copy; } @Override public boolean equals(final Object obj) { if (this == obj) return true; if (obj == null) return false; if (this.getClass() != obj.getClass()) return false; final DynamicInvokable<?, ?, ?> other = (DynamicInvokable<?, ?, ?>) obj; return this.name.equals(other.name) && this.originalSignatures.equals(other.originalSignatures); } public String getName() { return this.name; } public abstract Class<ReturnType> getReturnType(); public Collection<Signature> getSignatures() { return this.originalSignatures.keySet(); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + this.name.hashCode(); result = prime * result + this.originalSignatures.hashCode(); return result; } public ReturnType invoke(final Object context, final Object... params) throws Throwable { final Class<?>[] paramTypes = this.getActualParameterTypes(params); final Signature signature = this.findBestSignature(new Signature(paramTypes)); if (signature == null) throw new IllegalArgumentException(String.format("No method %s found for parameter types %s", this.getName(), Arrays.toString(paramTypes))); return this.invokeSignature(signature, context, params); } public ReturnType invokeSignature(final Signature signature, final Object context, final Object... params) throws Throwable { try { return this.invokeDirectly(this.getMember(signature), context, signature.adjustParameters(params)); } catch (final Error e) { throw e; } catch (final InvocationTargetException e) { throw e.getCause(); } } public ReturnType invokeStatic(final Object... params) throws Throwable { return this.invoke(null, params); } public boolean isInvokableFor(final Object... params) { final Class<?>[] paramTypes = this.getActualParameterTypes(params); return this.findBestSignature(new Signature(paramTypes)) != null; } /* * (non-Javadoc) * @see com.esotericsoftware.kryo.KryoSerializable#read(com.esotericsoftware.kryo.Kryo, * com.esotericsoftware.kryo.io.Input) */ @SuppressWarnings("unchecked") @Override public void read(final Kryo kryo, final Input input) { ReflectUtil.setField(this, DynamicInvokable.class, "name", kryo.readObject(input, String.class)); final int size = input.readInt(); this.cachedSignatures = new HashMap<Signature, MemberType>(); this.originalSignatures = new HashMap<Signature, MemberType>(); for (int index = 0; index < size; index++) try { final int signatureType = input.readByte(); final Signature signature = kryo.readObject(input, SignatureTypes.get(signatureType)); final String name = kryo.readObject(input, String.class); final Class<DeclaringType> clazz = kryo.readObject(input, Class.class); final Class<?>[] params = kryo.readObject(input, Class[].class); this.originalSignatures.put(signature, this.findMember(name, clazz, params)); } catch (final NoSuchMethodException e) { throw new IllegalStateException("Cannot find registered java function " + this.getName(), e); } } /* * (non-Javadoc) * @see com.esotericsoftware.kryo.KryoSerializable#write(com.esotericsoftware.kryo.Kryo, * com.esotericsoftware.kryo.io.Output) */ @Override public void write(final Kryo kryo, final Output output) { kryo.writeObject(output, this.name); output.writeInt(this.originalSignatures.size()); for (final Entry<Signature, MemberType> entry : this.originalSignatures.entrySet()) { output.writeByte(SignatureTypes.indexOf(entry.getKey().getClass())); kryo.writeObject(output, entry.getKey()); kryo.writeObject(output, entry.getValue().getName()); kryo.writeObject(output, entry.getValue().getDeclaringClass()); kryo.writeObject(output, this.getParameterTypes(entry.getValue())); } } protected abstract MemberType findMember(String name, Class<DeclaringType> clazz, Class<?>[] parameterTypes) throws NoSuchMethodException; protected Class<?>[] getActualParameterTypes(final Object[] params) { final Class<?>[] paramTypes = new Class<?>[params.length]; for (int index = 0; index < paramTypes.length; index++) paramTypes[index] = params[index] == null ? null : params[index].getClass(); return paramTypes; } protected MemberType getMember(final Signature signature) { return this.originalSignatures.get(signature); } protected abstract Class<?>[] getParameterTypes(final MemberType member); protected Class<?>[] getSignatureTypes(final MemberType member) { return this.getParameterTypes(member); } protected abstract ReturnType invokeDirectly(final MemberType member, final Object context, Object[] params) throws IllegalAccessException, InvocationTargetException, IllegalArgumentException, InstantiationException; protected abstract boolean isVarargs(final MemberType member); protected abstract boolean needsInstance(MemberType member); private Signature findBestSignature(final Signature signature) { MemberType member = this.getMember(signature); if (member != null) return signature; int minDistance = Integer.MAX_VALUE; boolean ambiguous = false; Signature bestSignatureSoFar = null; for (final Entry<Signature, MemberType> originalSignature : this.originalSignatures.entrySet()) { final int distance = originalSignature.getKey().getDistance(signature); if (distance < 0) continue; if (distance < minDistance) { minDistance = distance; bestSignatureSoFar = originalSignature.getKey(); ambiguous = false; } else if (distance == minDistance) ambiguous = true; } if (minDistance == Integer.MAX_VALUE) return null; if (ambiguous && LOG.isWarnEnabled()) this.warnForAmbiguity(signature, minDistance); member = minDistance == Signature.INCOMPATIBLE ? null : this.originalSignatures.get(bestSignatureSoFar); this.cachedSignatures.put(bestSignatureSoFar, member); return bestSignatureSoFar; } private void warnForAmbiguity(final Signature signature, final int minDistance) { final List<Signature> ambigiousSignatures = new ArrayList<Signature>(); for (final Entry<Signature, MemberType> originalSignature : this.originalSignatures.entrySet()) { final int distance = originalSignature.getKey().getDistance(signature); if (distance == minDistance) ambigiousSignatures.add(originalSignature.getKey()); } LOG.warn(String.format("multiple matching signatures found for the member %s and parameters types %s: %s", this.getName(), Arrays.toString(signature.getParameterTypes()), ambigiousSignatures)); } }