package eu.stratosphere.util.reflect; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; 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 eu.stratosphere.sopremo.EvaluationException; public abstract class DynamicInvokable<MemberType extends Member, DeclaringType, ReturnType> implements Serializable { /** * */ private static final long serialVersionUID = 715358230985689455L; 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; @SuppressWarnings("unchecked") private void readObject(final ObjectInputStream ois) throws ClassNotFoundException, IOException { ois.defaultReadObject(); final int size = ois.readInt(); this.cachedSignatures = new HashMap<Signature, MemberType>(); this.originalSignatures = new HashMap<Signature, MemberType>(); for (int index = 0; index < size; index++) try { this.originalSignatures.put((Signature) ois.readObject(), this.findMember((Class<DeclaringType>) ois.readObject(), (Class<?>[]) ois.readObject())); } catch (final NoSuchMethodException e) { throw new EvaluationException("Cannot find registered java function " + this.getName(), e); } } protected abstract MemberType findMember(Class<DeclaringType> clazz, Class<?>[] parameterTypes) throws NoSuchMethodException; private void writeObject(final ObjectOutputStream oos) throws IOException { oos.defaultWriteObject(); oos.writeInt(this.originalSignatures.size()); for (final Entry<Signature, MemberType> entry : this.originalSignatures.entrySet()) { oos.writeObject(entry.getKey()); oos.writeObject(entry.getValue().getDeclaringClass()); oos.writeObject(this.getParameterTypes(entry.getValue())); } } public String getName() { return this.name; } public DynamicInvokable(final String name) { this.name = name; } public void addSignature(final MemberType member) { final Class<?>[] parameterTypes = this.getSignatureTypes(member); Signature signature; 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); } protected abstract boolean needsInstance(MemberType member); protected Class<?>[] getSignatureTypes(final MemberType member) { return this.getParameterTypes(member); } protected abstract boolean isVarargs(final MemberType member); protected abstract Class<?>[] getParameterTypes(final 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)); } public ReturnType invoke(final Object context, final Object... params) { final Class<?>[] paramTypes = this.getActualParameterTypes(params); final Signature signature = this.findBestSignature(new Signature(paramTypes)); if (signature == null) throw new EvaluationException(String.format("No method %s found for parameter types %s", this.getName(), Arrays.toString(paramTypes))); return this.invokeSignature(signature, context, params); } public ReturnType invokeStatic(final Object... params) { return this.invoke(null, params); } public abstract Class<ReturnType> getReturnType(); public boolean isInvokableFor(final Object... params) { final Class<?>[] paramTypes = this.getActualParameterTypes(params); return this.findBestSignature(new Signature(paramTypes)) != null; } public ReturnType invokeSignature(final Signature signature, final Object context, final Object... params) { try { return this.invokeDirectly(this.getMember(signature), context, signature.adjustParameters(params)); } catch (final Exception e) { throw new EvaluationException("Cannot invoke " + this.getMember(signature) + " with " + Arrays.toString(params), e); } } protected MemberType getMember(final Signature signature) { return this.cachedSignatures.get(signature); } protected abstract ReturnType invokeDirectly(final MemberType member, final Object context, Object[] params) throws IllegalAccessException, InvocationTargetException, IllegalArgumentException, InstantiationException; 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; } @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; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; DynamicInvokable<?, ?, ?> other = (DynamicInvokable<?, ?, ?>) obj; return this.name.equals(other.name) && this.originalSignatures.equals(other.originalSignatures); } public Collection<Signature> getSignatures() { return this.originalSignatures.keySet(); } }