/*
* Copyright 2006-2010 Brian S O'Neill
*
* 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 org.cojen.dirmi.info;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.security.MessageDigest;
import java.security.DigestOutputStream;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import org.cojen.classfile.MethodDesc;
import org.cojen.classfile.TypeDesc;
import org.cojen.util.WeakCanonicalSet;
import org.cojen.dirmi.Asynchronous;
import org.cojen.dirmi.Batched;
import org.cojen.dirmi.CallMode;
import org.cojen.dirmi.Completion;
import org.cojen.dirmi.Disposer;
import org.cojen.dirmi.Ordered;
import org.cojen.dirmi.Pipe;
import org.cojen.dirmi.RemoteFailure;
import org.cojen.dirmi.Timeout;
import org.cojen.dirmi.TimeoutParam;
import org.cojen.dirmi.TimeoutUnit;
import org.cojen.dirmi.Trace;
import org.cojen.dirmi.Unbatched;
import org.cojen.dirmi.util.Cache;
/**
* Supports examination of {@code Remote} types, returning all metadata
* associated with it. As part of the examination, all annotations are gathered
* up. All examined data is cached, so repeat examinations are fast, unless the
* examination failed.
*
* @author Brian S O'Neill
*/
public class RemoteIntrospector {
private static final Cache<Class<?>, Class<?>> cInterfaceCache;
private static final Cache<Class<?>, RInfo> cInfoCache;
private static final WeakCanonicalSet cParameterCache;
static {
cInterfaceCache = Cache.newWeakIdentityCache(17);
cInfoCache = Cache.newWeakIdentityCache(17);
cParameterCache = new WeakCanonicalSet();
}
static <T> RParameter<T> intern(RParameter<T> param) {
return (RParameter<T>) cParameterCache.put(param);
}
/**
* Returns the Remote interface implemented by the given Remote object.
*
* @param remoteObj remote object to examine
* @throws IllegalArgumentException if remote is null or malformed
*/
public static <R extends Remote> Class<? extends Remote> getRemoteType(R remoteObj)
throws IllegalArgumentException
{
if (remoteObj == null) {
throw new IllegalArgumentException("Remote object must not be null");
}
Class clazz = remoteObj.getClass();
synchronized (cInterfaceCache) {
Class theOne = cInterfaceCache.get(clazz);
if (theOne != null) {
return theOne;
}
// Only consider the one that implements Remote.
for (Class iface : clazz.getInterfaces()) {
if (Modifier.isPublic(iface.getModifiers()) &&
Remote.class.isAssignableFrom(iface))
{
if (theOne != null) {
throw new IllegalArgumentException
("At most one Remote interface may be directly implemented: " +
clazz.getName());
}
theOne = iface;
}
}
if (theOne == null) {
throw new IllegalArgumentException
("No Remote types directly implemented: " + clazz.getName());
}
cInterfaceCache.put(clazz, theOne);
return theOne;
}
}
/**
* @param remote remote interface to examine
* @throws IllegalArgumentException if remote is null or malformed
*/
public static RemoteInfo examine(Class<? extends Remote> remote)
throws IllegalArgumentException
{
if (remote == null) {
throw new IllegalArgumentException("Remote interface must not be null");
}
synchronized (cInfoCache) {
RInfo info = cInfoCache.get(remote);
if (info != null) {
return info;
}
if (!remote.isInterface()) {
throw new IllegalArgumentException("Remote type must be an interface: " + remote);
}
if (!Modifier.isPublic(remote.getModifiers())) {
throw new IllegalArgumentException
("Remote interface must be public: " + remote.getName());
}
if (!Remote.class.isAssignableFrom(remote)) {
throw new IllegalArgumentException
("Remote interface must extend java.rmi.Remote: " + remote.getName());
}
if (java.io.Serializable.class.isAssignableFrom(remote)) {
throw new IllegalArgumentException
("Remote interface cannot extend java.io.Serializable: " + remote.getName());
}
SortedMap<String, RMethod> methodMap = new TreeMap<String, RMethod>();
for (Method m : remote.getMethods()) {
if (!m.getDeclaringClass().isInterface() || isObjectMethod(m)) {
continue;
}
String key;
{
Class<?>[] params = m.getParameterTypes();
TypeDesc[] paramDescs = new TypeDesc[params.length];
for (int i=0; i<params.length; i++) {
paramDescs[i] = TypeDesc.forClass(params[i]);
}
MethodDesc desc = MethodDesc.forArguments
(TypeDesc.forClass(m.getReturnType()), paramDescs);
key = m.getName() + ':' + desc;
}
if (!methodMap.containsKey(key)) {
methodMap.put(key, new RMethod(m));
continue;
}
RMethod existing = methodMap.get(key);
RMethod candidate = new RMethod(m);
if (existing.equals(candidate)) {
continue;
}
// Same method inherited from multiple parent interfaces. Only
// exceptions are allowed to differ. If so, select the
// intersection of the exceptions.
candidate = existing.intersectExceptions(candidate);
methodMap.put(key, candidate);
}
for (RMethod method : methodMap.values()) {
if (method.isAsynchronous()) {
if (method.getReturnType() != null) {
Class returnType = method.getReturnType().getType();
if (Pipe.class == returnType) {
if (method.isBatched()) {
throw new IllegalArgumentException
("Asynchronous batched method cannot return Pipe: " +
method.methodDesc());
}
// Verify one parameter is a pipe.
int count = 0;
for (RemoteParameter param : method.getParameterTypes()) {
if (param.getType() == returnType) {
count++;
}
}
if (count != 1) {
throw new IllegalArgumentException
("Asynchronous method which returns Pipe must have " +
"exactly one matching Pipe input parameter: " +
method.methodDesc());
}
} else if (Future.class == returnType || Completion.class == returnType) {
// Okay.
} else if (method.isBatched()) {
if (!Remote.class.isAssignableFrom(returnType)) {
throw new IllegalArgumentException
("Asynchronous batched method must return void, " +
"a Remote object, Completion or Future: "
+ method.methodDesc());
}
} else {
throw new IllegalArgumentException
("Asynchronous method must return void, Pipe, " +
"Completion or Future: " + method.methodDesc());
}
}
}
}
// Gather all implemented interfaces which implement Remote.
SortedSet<String> interfaces = new TreeSet<String>();
gatherRemoteInterfaces(interfaces, remote);
info = new RInfo(remote, interfaces, new LinkedHashSet<RMethod>(methodMap.values()));
cInfoCache.put(remote, info);
// Now that RInfo is in the cache, call resolve to check remote
// parameters.
try {
info.resolve();
} catch (IllegalArgumentException e) {
cInfoCache.remove(remote);
throw e;
}
return info;
}
}
private static void gatherRemoteInterfaces(Set<String> interfaces, Class clazz) {
for (Class i : clazz.getInterfaces()) {
if (Remote.class.isAssignableFrom(i)) {
if (interfaces.add(i.getName())) {
gatherRemoteInterfaces(interfaces, i);
}
}
}
if (clazz.isInterface() && Remote.class.isAssignableFrom(clazz)) {
interfaces.add(clazz.getName());
}
}
private static boolean isObjectMethod(Method m) {
try {
return Object.class.getMethod(m.getName(), m.getParameterTypes()) != null;
} catch (NoSuchMethodException e) {
return false;
}
}
private RemoteIntrospector() {
}
private static class RInfo implements RemoteInfo {
private static final long serialVersionUID = 1L;
// Id is assigned by resolve method.
private long mId;
private final String mName;
private final SortedSet<String> mInterfaceNames;
private final Set<RMethod> mMethods;
private final transient RParameter<? extends Throwable> mRemoteFailureException;
private final transient boolean mRemoteFailureExceptionDeclared;
private final transient long mTimeout;
private final transient TimeUnit mTimeoutUnit;
private transient Map<String, Set<RMethod>> mMethodsByName;
RInfo(Class<? extends Remote> remote, SortedSet<String> interfaces, Set<RMethod> methods) {
mName = remote.getName();
mInterfaceNames = Collections.unmodifiableSortedSet(interfaces);
mMethods = Collections.unmodifiableSet(methods);
{
RemoteFailure ann = remote.getAnnotation(RemoteFailure.class);
if (ann == null) {
mRemoteFailureException = RParameter.make(RemoteException.class);
mRemoteFailureExceptionDeclared = true;
} else {
mRemoteFailureException = RParameter.make(ann.exception());
mRemoteFailureExceptionDeclared = ann.declared();
}
}
{
Timeout ann = remote.getAnnotation(Timeout.class);
if (ann == null) {
mTimeout = -1;
} else {
long timeout = ann.value();
mTimeout = timeout < 0 ? -1 : timeout;
}
}
{
TimeoutUnit ann = remote.getAnnotation(TimeoutUnit.class);
if (ann == null) {
mTimeoutUnit = TimeUnit.MILLISECONDS;
} else {
TimeUnit unit = ann.value();
mTimeoutUnit = (unit == null) ? TimeUnit.MILLISECONDS : unit;
}
}
}
public String getName() {
return mName;
}
public long getInfoId() {
return mId;
}
public Set<String> getInterfaceNames() {
return mInterfaceNames;
}
public Set<? extends RemoteMethod> getRemoteMethods() {
return mMethods;
}
public Set<? extends RemoteMethod> getRemoteMethods(String name) {
if (mMethodsByName == null) {
Map<String, Set<RMethod>> methodsByName = new HashMap<String, Set<RMethod>>();
for (RMethod method : mMethods) {
String methodName = method.getName();
Set<RMethod> set = methodsByName.get(methodName);
if (set == null) {
set = new LinkedHashSet<RMethod>();
methodsByName.put(methodName, set);
}
set.add(method);
}
// Pass through again, making sure each contained set is unmodifiable.
for (Map.Entry<String, Set<RMethod>> entry : methodsByName.entrySet()) {
entry.setValue(Collections.unmodifiableSet(entry.getValue()));
}
mMethodsByName = methodsByName;
}
Set<? extends RemoteMethod> methods = mMethodsByName.get(name);
if (methods == null) {
methods = Collections.emptySet();
}
return methods;
}
public RemoteMethod getRemoteMethod(String name, RemoteParameter... params)
throws NoSuchMethodException
{
int paramsLength = params == null ? 0 : params.length;
search:
for (RemoteMethod method : getRemoteMethods(name)) {
List<? extends RemoteParameter> paramTypes = method.getParameterTypes();
if (paramTypes.size() == paramsLength) {
for (int i=0; i<paramsLength; i++) {
if (!paramTypes.get(i).equalTypes(params[i])) {
continue search;
}
}
return method;
}
}
throw new NoSuchMethodException(name);
}
@Override
public int hashCode() {
return mName.hashCode() + (int) (mId >> 32) + (int) mId;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof RInfo) {
RInfo other = (RInfo) obj;
return mName.equals(other.mName) && (mId == other.mId) &&
mInterfaceNames.equals(other.mInterfaceNames) &&
getRemoteMethods().equals(other.getRemoteMethods());
}
return false;
}
@Override
public String toString() {
StringBuilder b = new StringBuilder();
b.append("RemoteInfo {id=");
b.append(mId);
b.append(", name=");
b.append(mName);
b.append('}');
return b.toString();
}
RParameter<? extends Throwable> getRemoteFailureException() {
return mRemoteFailureException;
}
boolean isRemoteFailureExceptionDeclared() {
return mRemoteFailureExceptionDeclared;
}
long getTimeout() {
return mTimeout;
}
TimeUnit getTimeoutUnit() {
return mTimeoutUnit;
}
void resolve() {
Set<Class> validExceptions = new HashSet<Class>();
for (RMethod method : mMethods) {
method.resolve(this, validExceptions);
}
// Now assign the id by computing a hashcode of all elements.
MessageDigest digest;
try {
digest = MessageDigest.getInstance("SHA-1");
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
OutputStream nullOut = new OutputStream() {
public void write(int b) {}
public void write(byte[] b, int off, int len) {}
};
DataOutput digestOutput = new DataOutputStream
(new DigestOutputStream(nullOut, digest));
try {
mixIn(digestOutput);
} catch (IOException e) {
throw new AssertionError(e);
}
byte[] hash = digest.digest();
long id = 0;
for (int i=0; i<hash.length; i++) {
id ^= (hash[i] & 0xffL) << ((i & 7) << 3);
}
mId = id;
// Finally, assign method ids using a portion of the secure hash as
// a base. Even if a collision exists in the 64-bit identifier,
// method ids might not collide.
int methodId = 0;
for (int i=0; i<4; i++) {
methodId ^= (hash[i] & 0xff) << ((i & 3) << 3);
}
for (RMethod method : mMethods) {
if (method.isAsynchronous()) {
if ((methodId & 1) == 0) {
methodId++;
}
} else if ((methodId & 1) != 0) {
methodId++;
}
method.setId(methodId);
methodId++;
}
}
void mixIn(DataOutput digest) throws IOException {
digest.writeUTF(mName);
for (String name : mInterfaceNames) {
digest.writeUTF(name);
}
for (RMethod method : mMethods) {
method.mixIn(digest);
}
}
}
private static class RMethod implements RemoteMethod {
private static final long serialVersionUID = 1L;
// Id is assigned after RInfo has been resolved.
private int mId;
private final String mName;
private RParameter mReturnType;
private List<RParameter<Object>> mParameterTypes;
private final SortedSet<RParameter<Throwable>> mExceptionTypes;
private final CallMode mCallMode;
private final boolean mBatched;
private final boolean mUnbatched;
private final boolean mOrdered;
private final boolean mDisposer;
private final transient Trace mTrace;
private RParameter<? extends Throwable> mRemoteFailureException;
private boolean mRemoteFailureExceptionDeclared;
private long mTimeout;
private TimeUnit mTimeoutUnit;
private transient Method mMethod;
RMethod(Method m) {
if (!Modifier.isPublic(m.getModifiers())) {
throw new IllegalArgumentException
("Remote method must be public: " + methodDesc(m));
}
mName = m.getName();
if (m.isAnnotationPresent(Batched.class)) {
mBatched = true;
mCallMode = m.getAnnotation(Batched.class).value();
if (m.isAnnotationPresent(Asynchronous.class)) {
throw new IllegalArgumentException
("Method cannot be annotated as both @Asynchronous and @Batched: " +
methodDesc(m));
}
} else {
mBatched = false;
Asynchronous ann = m.getAnnotation(Asynchronous.class);
if (ann == null) {
mCallMode = null;
} else {
mCallMode = ann.value();
}
}
if (m.isAnnotationPresent(Unbatched.class)) {
if (mBatched) {
throw new IllegalArgumentException
("Method cannot be annotated as both @Batched and @Unbatched: " +
methodDesc(m));
}
mUnbatched = true;
} else {
mUnbatched = false;
}
mOrdered = m.isAnnotationPresent(Ordered.class);
mDisposer = m.isAnnotationPresent(Disposer.class);
mTrace = m.getAnnotation(Trace.class);
// First pass, treat all params as serialized. Resolve on second pass.
// This allows remote methods to pass instances of declaring class without
// causing the introspector to overflow the stack.
Class<?> returnType = m.getReturnType();
if (returnType == null) {
mReturnType = null;
} else {
if (!Modifier.isPublic(returnType.getModifiers())) {
throw new IllegalArgumentException
("Remote method return type must be public: " + methodDesc(m));
}
mReturnType = RParameter.make(returnType, mCallMode != null);
}
Class<?>[] paramsTypes = m.getParameterTypes();
if (paramsTypes == null || paramsTypes.length == 0) {
mParameterTypes = null;
} else {
mParameterTypes = new ArrayList<RParameter<Object>>(paramsTypes.length);
Annotation[][] paramsAnns = m.getParameterAnnotations();
// First pass, find any timeout and unit parameters.
int timeoutValueParam = -1;
int timeoutUnitParam = -1;
boolean defaultTimeoutUnitParam = false;
for (int i=0; i<paramsTypes.length; i++) {
Class paramType = paramsTypes[i];
if (!Modifier.isPublic(paramType.getModifiers())) {
throw new IllegalArgumentException
("Remote method parameter types must be public: " + methodDesc(m));
}
Annotation[] paramAnns = paramsAnns[i];
if (paramAnns != null) {
for (Annotation ann : paramAnns) {
if (!(ann instanceof TimeoutParam)) {
continue;
}
if (paramType == TimeUnit.class) {
if (timeoutUnitParam >= 0 && !defaultTimeoutUnitParam) {
throw new IllegalArgumentException
("At most one timeout unit parameter allowed: " +
methodDesc(m));
}
timeoutUnitParam = i;
defaultTimeoutUnitParam = false;
continue;
}
TypeDesc desc = TypeDesc.forClass(paramType).toPrimitiveType();
if (desc != null &&
desc != TypeDesc.BOOLEAN && desc != TypeDesc.CHAR)
{
if (timeoutValueParam >= 0) {
throw new IllegalArgumentException
("At most one timeout value parameter allowed: " +
methodDesc(m));
}
timeoutValueParam = i;
// If next parameter type is a TimeUnit, it is
// selected to be the timeout unit parameter.
if (timeoutUnitParam < 0 &&
i + 1 < paramsTypes.length &&
paramsTypes[i + 1] == TimeUnit.class)
{
timeoutUnitParam = i + 1;
defaultTimeoutUnitParam = true;
}
continue;
}
throw new IllegalArgumentException
("Timeout parameter can only apply to primitive " +
"numerical types or TimeUnit, not " +
TypeDesc.forClass(paramType).getFullName() +
": " + methodDesc(m));
}
}
}
for (int i=0; i<paramsTypes.length; i++) {
Class paramType = paramsTypes[i];
mParameterTypes.add(RParameter.make
(paramType, mCallMode != null,
timeoutValueParam == i, timeoutUnitParam == i));
}
}
Class<?>[] exceptionTypes = m.getExceptionTypes();
if (exceptionTypes == null || exceptionTypes.length == 0) {
mExceptionTypes = null;
} else {
SortedSet<RParameter<Throwable>> set = new TreeSet<RParameter<Throwable>>();
for (Class exceptionType : exceptionTypes) {
if (!Modifier.isPublic(exceptionType.getModifiers())) {
throw new IllegalArgumentException
("Remote method declared exception types must be public: " +
methodDesc(m, exceptionType));
}
set.add(RParameter.make(exceptionType));
}
mExceptionTypes = Collections.unmodifiableSortedSet(set);
}
{
RemoteFailure ann = m.getAnnotation(RemoteFailure.class);
if (ann == null) {
// Use inherited values from RemoteInfo, filled in when
// resolve is called.
} else {
mRemoteFailureException = RParameter.make(ann.exception());
mRemoteFailureExceptionDeclared = ann.declared();
}
}
{
Timeout ann = m.getAnnotation(Timeout.class);
if (ann == null) {
// Use inherited values from RemoteInfo, filled in when
// resolve is called. Store min value to indicate this.
mTimeout = Long.MIN_VALUE;
} else {
long timeout = ann.value();
mTimeout = timeout < 0 ? -1 : timeout;
}
}
{
TimeoutUnit ann = m.getAnnotation(TimeoutUnit.class);
if (ann == null) {
// Use inherited values from RemoteInfo, filled in when
// resolve is called.
} else {
TimeUnit unit = ann.value();
mTimeoutUnit = (unit == null) ? TimeUnit.MILLISECONDS : unit;
}
}
// Hang on to this until resolve is called.
mMethod = m;
}
private RMethod(RMethod existing, SortedSet<RParameter<Throwable>> exceptionTypes) {
mId = existing.mId;
mName = existing.mName;
mReturnType = existing.mReturnType;
mParameterTypes = existing.mParameterTypes;
mExceptionTypes = Collections.unmodifiableSortedSet(exceptionTypes);
mCallMode = existing.mCallMode;
mBatched = existing.mBatched;
mUnbatched = existing.mUnbatched;
mOrdered = existing.mOrdered;
mDisposer = existing.mDisposer;
mTrace = existing.mTrace;
mRemoteFailureException = existing.mRemoteFailureException;
mRemoteFailureExceptionDeclared = existing.mRemoteFailureExceptionDeclared;
mMethod = existing.mMethod;
}
public String getName() {
return mName;
}
public int getMethodId() {
return mId;
}
public RemoteParameter getReturnType() {
return mReturnType;
}
public List<? extends RemoteParameter<?>> getParameterTypes() {
if (mParameterTypes == null) {
return Collections.emptyList();
}
return mParameterTypes;
}
public Set<? extends RemoteParameter<? extends Throwable>> getExceptionTypes() {
if (mExceptionTypes == null) {
return Collections.emptySet();
}
return mExceptionTypes;
}
public String getSignature() {
return getSignature(null);
}
public boolean isAsynchronous() {
return mCallMode != null;
}
public CallMode getAsynchronousCallMode() {
return mCallMode;
}
public boolean isBatched() {
return mBatched;
}
public boolean isUnbatched() {
return mUnbatched;
}
public boolean isOrdered() {
return mOrdered;
}
public boolean isDisposer() {
return mDisposer;
}
public RemoteParameter<? extends Throwable> getRemoteFailureException() {
return mRemoteFailureException;
}
public boolean isRemoteFailureExceptionDeclared() {
return mRemoteFailureExceptionDeclared;
}
public long getTimeout() {
return mTimeout;
}
public TimeUnit getTimeoutUnit() {
return mTimeoutUnit;
}
public Trace getTraceAnnotation() {
return mTrace;
}
@Override
public int hashCode() {
return mName.hashCode() + mId;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof RMethod) {
RMethod other = (RMethod) obj;
return mName.equals(other.mName) && (mId == other.mId) &&
getParameterTypes().equals(other.getParameterTypes()) &&
getExceptionTypes().equals(other.getExceptionTypes()) &&
(mCallMode == other.mCallMode) &&
(mBatched == other.mBatched) &&
(mRemoteFailureException == other.getRemoteFailureException()) &&
(mRemoteFailureExceptionDeclared == other.isRemoteFailureExceptionDeclared());
}
return false;
}
@Override
public String toString() {
StringBuilder b = new StringBuilder();
b.append("RemoteMethod {id=");
b.append(mId);
b.append(", sig=\"");
b.append(getSignature(null));
b.append('"');
b.append('}');
return b.toString();
}
/**
* @param className optional
*/
String getSignature(String className) {
StringBuilder b = new StringBuilder();
b.append(getReturnType() == null ? "void" : getReturnType());
b.append(' ');
if (className != null) {
b.append(className);
b.append('.');
}
b.append(getName());
b.append('(');
int count = 0;
for (RemoteParameter param : getParameterTypes()) {
if (count++ > 0) {
b.append(", ");
}
b.append(param);
}
b.append(')');
Set<? extends RemoteParameter> exceptions = getExceptionTypes();
if (exceptions.size() > 0) {
b.append(" throws ");
count = 0;
for (RemoteParameter param : exceptions) {
if (count++ > 0) {
b.append(", ");
}
b.append(param);
}
}
return b.toString();
}
RMethod intersectExceptions(RMethod other) {
if (this == other) {
return this;
}
if (!mName.equals(other.mName)) {
// This indicates a bug in RemoteIntrospector.
throw new IllegalArgumentException("name mismatch");
}
if (mId != other.mId) {
// This indicates a bug in RemoteIntrospector.
throw new IllegalArgumentException("id mismatch");
}
if (!getParameterTypes().equals(other.getParameterTypes())) {
// This indicates a bug in RemoteIntrospector.
throw new IllegalArgumentException("parameter types mismatch");
}
if (mCallMode != other.mCallMode || mBatched != other.mBatched) {
// This is user error.
String conflictType;
if (!mBatched && !other.mBatched) {
conflictType = "@Asynchronous";
} else if ((mBatched && (other.mBatched || other.mCallMode == null)) ||
(other.mBatched) && (mBatched || mCallMode == null))
{
conflictType = "@Batched";
} else {
conflictType = "@Asynchronous/@Batched";
}
throw new IllegalArgumentException
("Inherited methods conflict in use of " + conflictType + " annotation: " +
methodDesc() + " and " + other.methodDesc());
}
SortedSet<RParameter<Throwable>> subset = new TreeSet<RParameter<Throwable>>();
for (RParameter exceptionType : mExceptionTypes) {
if (other.declaresException(exceptionType)) {
subset.add(exceptionType);
}
}
for (RParameter exceptionType : other.mExceptionTypes) {
if (this.declaresException(exceptionType)) {
subset.add(exceptionType);
}
}
return new RMethod(this, subset);
}
boolean declaresException(RemoteParameter exceptionType) {
return declaresException(exceptionType.getType());
}
boolean declaresException(Class<?> exceptionType) {
if (mExceptionTypes == null) {
return false;
}
for (RemoteParameter declared : mExceptionTypes) {
if (declared.getType().isAssignableFrom(exceptionType)) {
return true;
}
}
return false;
}
void resolve(RInfo info, Set<Class> validExceptions) {
if (mParameterTypes != null) {
int size = mParameterTypes.size();
// If any individual parameter cannot be unshared, then none
// can be unshared. This is because a complex serialized object
// might refer to any parameter or even itself.
boolean noneUnshared = false;
for (int i=0; i<size; i++) {
if (!mParameterTypes.get(i).isUnshared()) {
noneUnshared = true;
break;
}
}
for (int i=0; i<size; i++) {
RParameter param = mParameterTypes.get(i);
Class type = param.getType();
boolean unshared = !noneUnshared && param.isUnshared();
// Can only be truly unshared if no other parameter is of same type.
if (unshared) {
for (int j=i+1; j<size; j++) {
RParameter jp = mParameterTypes.get(j);
if (type == jp.getType()) {
unshared = false;
// Mark parameter as unshared for when we see it again.
mParameterTypes.set(j, jp.toUnshared(false));
break;
}
}
}
mParameterTypes.set(i, param.toUnshared(unshared));
}
mParameterTypes = Collections.unmodifiableList(mParameterTypes);
}
if (mRemoteFailureException == null) {
// Inherit from remote interface.
mRemoteFailureException = info.getRemoteFailureException();
mRemoteFailureExceptionDeclared = info.isRemoteFailureExceptionDeclared();
}
if (mTimeout == Long.MIN_VALUE) {
// Inherit from remote interface.
mTimeout = info.getTimeout();
}
if (mTimeoutUnit == null) {
// Inherit from remote interface.
mTimeoutUnit = info.getTimeoutUnit();
}
Class<? extends Throwable> failExClass = mRemoteFailureException.getType();
if (!failExClass.isAssignableFrom(RemoteException.class)) {
if (!Modifier.isPublic(failExClass.getModifiers())) {
throw new IllegalArgumentException
("Remote failure exception must be public: " + failExClass.getName());
}
// Check for valid constructor.
validCheck: {
if (validExceptions.contains(failExClass)) {
break validCheck;
}
for (Constructor ctor : failExClass.getConstructors()) {
Class[] paramTypes = ctor.getParameterTypes();
if (paramTypes.length != 1) {
continue;
}
if (paramTypes[0].isAssignableFrom(RemoteException.class)) {
validExceptions.add(failExClass);
break validCheck;
}
}
throw new IllegalArgumentException
("Remote failure exception does not have a public single-argument " +
"constructor which accepts a RemoteException: " + failExClass.getName());
}
}
if (isChecked(failExClass) && isRemoteFailureExceptionDeclared()) {
if (!declaresException(failExClass)) {
String message = "Method must declare throwing " + failExClass.getName();
if (isBatched() || !isAsynchronous()) {
message += " (or a superclass)";
}
throw new IllegalArgumentException
(message + ": " + methodDesc() +
"; use @RemoteFailure to override behavior");
}
}
if (isAsynchronous() && !isBatched()) {
// Asynchronous non-batched methods can only declare throwing
// the remote failure exception.
Class<?>[] exceptionTypes = mMethod.getExceptionTypes();
if (exceptionTypes != null) {
for (Class exceptionType : exceptionTypes) {
if (isChecked(exceptionType) && exceptionType != failExClass) {
throw new IllegalArgumentException
("Asynchronous method can only declare throwing " +
failExClass.getName() + ": " +
methodDesc(mMethod, exceptionType) +
"; use @RemoteFailure override behavior");
}
}
}
}
// Won't need this again.
mMethod = null;
}
private static boolean isChecked(Class<? extends Throwable> exClass) {
return !(RuntimeException.class.isAssignableFrom(exClass) ||
Error.class.isAssignableFrom(exClass));
}
static String methodDesc(Method m) {
return methodDesc(m, null);
}
static String methodDesc(Method m, Class exceptionType) {
String name = m.getDeclaringClass().getName() + '.' + m.getName();
StringBuilder b = new StringBuilder();
b.append('"');
b.append(MethodDesc.forMethod(m).toMethodSignature(name));
if (exceptionType != null) {
b.append(" throws ");
b.append(exceptionType.getName());
}
b.append('"');
return b.toString();
}
String methodDesc() {
return methodDesc(mMethod);
}
void mixIn(DataOutput digest) throws IOException {
digest.writeUTF(mName);
if (mReturnType != null) {
mReturnType.mixIn(digest);
}
if (mParameterTypes != null) {
for (RParameter<?> param : mParameterTypes) {
param.mixIn(digest);
}
}
if (mExceptionTypes != null) {
for (RParameter<?> type : mExceptionTypes) {
type.mixIn(digest);
}
}
if (mCallMode != null) {
digest.writeUTF(mCallMode.name());
}
digest.writeBoolean(mBatched);
mRemoteFailureException.mixIn(digest);
digest.writeBoolean(mRemoteFailureExceptionDeclared);
digest.writeLong(mTimeout);
digest.writeUTF(mTimeoutUnit.name());
}
void setId(int id) {
mId = id;
}
}
private static class RParameter<T> implements RemoteParameter<T>, Comparable<RParameter> {
private static final long serialVersionUID = 1L;
private static final int FLAG_UNSHARED = 1;
private static final int FLAG_TIMEOUT = 2;
private static final int FLAG_TIMEOUT_UNIT = 4;
static <T> RParameter<T> make(Class<T> type) {
return make(type, false, false, false);
}
static <T> RParameter<T> make(Class<T> type, boolean asynchronous) {
return make(type, asynchronous, false, false);
}
static <T> RParameter<T> make(Class<T> type, boolean asynchronous,
boolean timeout, boolean timeoutUnit)
{
if (type == void.class || type == null) {
return null;
}
boolean unshared = type.isPrimitive() ||
String.class.isAssignableFrom(type) ||
(asynchronous && Pipe.class.isAssignableFrom(type)) ||
TypeDesc.forClass(type).toPrimitiveType() != null;
int flags =
(unshared ? FLAG_UNSHARED : 0) |
(timeout ? FLAG_TIMEOUT : 0) |
(timeoutUnit ? FLAG_TIMEOUT_UNIT : 0);
return intern(new RParameter<T>(type, flags));
}
private final Class<T> mType;
private final int mFlags;
private final String mDescriptor;
private RParameter(Class<T> type, int flags) {
mType = type;
mFlags = flags;
mDescriptor = TypeDesc.forClass(type).getDescriptor().intern();
}
public Class<T> getType() {
return mType;
}
public boolean isUnshared() {
return (mFlags & FLAG_UNSHARED) != 0;
}
public boolean isTimeout() {
return (mFlags & FLAG_TIMEOUT) != 0;
}
public boolean isTimeoutUnit() {
return (mFlags & FLAG_TIMEOUT_UNIT) != 0;
}
public boolean equalTypes(RemoteParameter other) {
if (this == other) {
return true;
}
return getType().getName().equals(other.getType().getName());
}
@Override
public int hashCode() {
return mType.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj instanceof RParameter) {
RParameter other = (RParameter) obj;
return mFlags == other.mFlags && mType.equals(other.mType);
}
return false;
}
@Override
public String toString() {
return TypeDesc.forClass(mType).getFullName();
}
@Override
public int compareTo(RParameter other) {
int compare = mType.getName().compareTo(other.mType.getName());
if (compare == 0) {
if (mFlags < other.mFlags) {
compare = -1;
} else if (mFlags > other.mFlags) {
compare = 1;
}
}
return compare;
}
RParameter toUnshared(boolean unshared) {
int flags = unshared ? (mFlags | FLAG_UNSHARED) : (mFlags & ~FLAG_UNSHARED);
if (flags == mFlags) {
return this;
}
return intern(new RParameter(mType, flags));
}
void mixIn(DataOutput digest) throws IOException {
digest.writeUTF(mType.getName());
digest.writeInt(mFlags);
}
private Object readResolve() {
return intern(this);
}
}
}