/** Examine a RequestContext method to see if it returns a transportable type. */ private boolean validateContextMethodAndSetDataType( RequestMethod.Builder methodBuilder, JMethod method, boolean allowSetters) throws UnableToCompleteException { JClassType requestReturnType = method.getReturnType().isInterface(); JClassType invocationReturnType; if (requestReturnType == null) { // Primitive return type poison(badContextReturnType(method, requestInterface, instanceRequestInterface)); return false; } if (instanceRequestInterface.isAssignableFrom(requestReturnType)) { // Instance method invocation JClassType[] params = ModelUtils.findParameterizationOf(instanceRequestInterface, requestReturnType); methodBuilder.setInstanceType(getEntityProxyType(params[0])); invocationReturnType = params[1]; } else if (requestInterface.isAssignableFrom(requestReturnType)) { // Static method invocation JClassType[] params = ModelUtils.findParameterizationOf(requestInterface, requestReturnType); invocationReturnType = params[0]; } else { // Unhandled return type, must be something random poison(badContextReturnType(method, requestInterface, instanceRequestInterface)); return false; } // Validate the parameters boolean paramsOk = true; JParameter[] params = method.getParameters(); for (int i = 0; i < params.length; ++i) { JParameter param = params[i]; paramsOk = validateTransportableType(new RequestMethod.Builder(), param.getType(), false) && paramsOk; } // Validate any extra properties on the request type for (JMethod maybeSetter : requestReturnType.getInheritableMethods()) { if (JBeanMethod.SET.matches(maybeSetter) || JBeanMethod.SET_BUILDER.matches(maybeSetter)) { if (allowSetters) { methodBuilder.addExtraSetter(maybeSetter); } else { poison(noSettersAllowed(maybeSetter)); } } } return validateTransportableType(methodBuilder, invocationReturnType, true); }
/** Examines a type to see if it can be transported. */ private boolean validateTransportableType( RequestMethod.Builder methodBuilder, JType type, boolean requireObject) throws UnableToCompleteException { JClassType transportedClass = type.isClassOrInterface(); if (transportedClass == null) { if (requireObject) { poison( "The type %s cannot be transported by RequestFactory as" + " a return type", type.getQualifiedSourceName()); return false; } else { // Primitives always ok return true; } } if (ModelUtils.isValueType(oracle, transportedClass) || splittableType.equals(transportedClass)) { // Simple values, like Integer and String methodBuilder.setValueType(true); } else if (entityProxyInterface.isAssignableFrom(transportedClass) || valueProxyInterface.isAssignableFrom(transportedClass)) { // EntityProxy and ValueProxy return types methodBuilder.setEntityType(getEntityProxyType(transportedClass)); } else if (collectionInterface.isAssignableFrom(transportedClass)) { // Only allow certain collections for now JParameterizedType parameterized = transportedClass.isParameterized(); if (parameterized == null) { poison("Requests that return collections of List or Set must be parameterized"); return false; } if (listInterface.equals(parameterized.getBaseType())) { methodBuilder.setCollectionType(CollectionType.LIST); } else if (setInterface.equals(parameterized.getBaseType())) { methodBuilder.setCollectionType(CollectionType.SET); } else { poison( "Requests that return collections may be declared with" + " %s or %s only", listInterface.getQualifiedSourceName(), setInterface.getQualifiedSourceName()); return false; } // Also record the element type in the method builder JClassType elementType = ModelUtils.findParameterizationOf(collectionInterface, transportedClass)[0]; methodBuilder.setCollectionElementType(elementType); validateTransportableType(methodBuilder, elementType, requireObject); } else if (mapInterface.isAssignableFrom(transportedClass)) { JParameterizedType parameterized = transportedClass.isParameterized(); if (parameterized == null) { poison("Requests that return Maps must be parameterized"); return false; } if (mapInterface.equals(parameterized.getBaseType())) { methodBuilder.setCollectionType(CollectionType.MAP); } else { poison( "Requests that return maps may be declared with" + " %s only", mapInterface.getQualifiedSourceName()); return false; } // Also record the element type in the method builder JClassType[] params = ModelUtils.findParameterizationOf(mapInterface, transportedClass); JClassType keyType = params[0]; JClassType valueType = params[1]; methodBuilder.setMapKeyType(keyType); methodBuilder.setMapValueType(valueType); validateTransportableType(methodBuilder, keyType, requireObject); validateTransportableType(methodBuilder, valueType, requireObject); } else { // Unknown type, fail poison("Invalid Request parameterization %s", transportedClass.getQualifiedSourceName()); return false; } methodBuilder.setDataType(transportedClass); return true; }
private EntityProxyModel getEntityProxyType(JClassType entityProxyType) throws UnableToCompleteException { entityProxyType = ModelUtils.ensureBaseType(entityProxyType); EntityProxyModel toReturn = peers.get(entityProxyType); if (toReturn == null) { EntityProxyModel.Builder inProgress = peerBuilders.get(entityProxyType); if (inProgress != null) { toReturn = inProgress.peek(); } } if (toReturn == null) { EntityProxyModel.Builder builder = new EntityProxyModel.Builder(); peerBuilders.put(entityProxyType, builder); // Validate possible super-proxy types first for (JClassType supertype : entityProxyType.getFlattenedSupertypeHierarchy()) { List<EntityProxyModel> superTypes = new ArrayList<EntityProxyModel>(); if (supertype != entityProxyType && shouldAttemptProxyValidation(supertype)) { superTypes.add(getEntityProxyType(supertype)); } builder.setSuperProxyTypes(superTypes); } builder.setQualifiedBinaryName(ModelUtils.getQualifiedBaseBinaryName(entityProxyType)); builder.setQualifiedSourceName(ModelUtils.getQualifiedBaseSourceName(entityProxyType)); if (entityProxyInterface.isAssignableFrom(entityProxyType)) { builder.setType(Type.ENTITY); } else if (valueProxyInterface.isAssignableFrom(entityProxyType)) { builder.setType(Type.VALUE); } else { poison( "The type %s is not assignable to either %s or %s", entityProxyInterface.getQualifiedSourceName(), valueProxyInterface.getQualifiedSourceName()); // Cannot continue, since knowing the behavior is crucial die(poisonedMessage()); } // Get the server domain object type ProxyFor proxyFor = entityProxyType.getAnnotation(ProxyFor.class); ProxyForName proxyForName = entityProxyType.getAnnotation(ProxyForName.class); JsonRpcProxy jsonRpcProxy = entityProxyType.getAnnotation(JsonRpcProxy.class); if (proxyFor == null && proxyForName == null && jsonRpcProxy == null) { poison( "The %s type does not have a @%s, @%s, or @%s annotation", entityProxyType.getQualifiedSourceName(), ProxyFor.class.getSimpleName(), ProxyForName.class.getSimpleName(), JsonRpcProxy.class.getSimpleName()); } // Look at the methods declared on the EntityProxy List<RequestMethod> requestMethods = new ArrayList<RequestMethod>(); Map<String, JMethod> duplicatePropertyGetters = new HashMap<String, JMethod>(); for (JMethod method : entityProxyType.getInheritableMethods()) { if (method.getEnclosingType().equals(entityProxyInterface)) { // Ignore methods on EntityProxy continue; } RequestMethod.Builder methodBuilder = new RequestMethod.Builder(); methodBuilder.setDeclarationMethod(entityProxyType, method); JType transportedType; String name = method.getName(); if (JBeanMethod.GET.matches(method)) { transportedType = method.getReturnType(); String propertyName = JBeanMethod.GET.inferName(method); JMethod previouslySeen = duplicatePropertyGetters.get(propertyName); if (previouslySeen == null) { duplicatePropertyGetters.put(propertyName, method); } else { poison( "Duplicate accessors for property %s: %s() and %s()", propertyName, previouslySeen.getName(), method.getName()); } } else if (JBeanMethod.SET.matches(method) || JBeanMethod.SET_BUILDER.matches(method)) { transportedType = method.getParameters()[0].getType(); } else if (name.equals("stableId") && method.getParameters().length == 0) { // Ignore any overload of stableId continue; } else { poison("The method %s is neither a getter nor a setter", method.getReadableDeclaration()); continue; } validateTransportableType(methodBuilder, transportedType, false); RequestMethod requestMethod = methodBuilder.build(); requestMethods.add(requestMethod); } builder .setExtraTypes(checkExtraTypes(entityProxyType, false)) .setRequestMethods(requestMethods); toReturn = builder.build(); peers.put(entityProxyType, toReturn); peerBuilders.remove(entityProxyType); } return toReturn; }
private RequestQueueModel collectModel( TreeLogger logger, GeneratorContext context, JClassType toGenerate) throws UnableToCompleteException { RequestQueueModel.Builder rqBuilder = new RequestQueueModel.Builder(); TypeOracle typeOracle = context.getTypeOracle(); JClassType requestQueue = typeOracle.findType(RequestQueue.class.getName()); JClassType asyncCallback = typeOracle.findType(AsyncCallback.class.getName()); JClassType voidType = typeOracle.findType(Void.class.getName()); rqBuilder.setRequestQueueInterfaceName(toGenerate.getParameterizedQualifiedSourceName()); AsyncServiceModel.Builder serviceBuilder = new AsyncServiceModel.Builder(); for (JMethod m : toGenerate.getMethods()) { TreeLogger serviceLogger = logger.branch(Type.DEBUG, "Reading async service " + m.getReadableDeclaration()); // Skip those defined at RequestQueue if (m.getEnclosingType().equals(requestQueue)) { continue; } JClassType returnType = m.getReturnType().isClassOrInterface(); if (returnType == null) { serviceLogger.log(Type.ERROR, "Unexpected method return type " + returnType); throw new UnableToCompleteException(); } serviceBuilder.setAsyncServiceInterfaceName(returnType.getParameterizedQualifiedSourceName()); serviceBuilder.setDeclaredMethodName(m.getName()); Service serviceAnnotation = m.getAnnotation(Service.class); if (serviceAnnotation == null) { serviceLogger.log(Type.ERROR, "Missing @Service annotation"); throw new UnableToCompleteException(); } serviceBuilder.setService(serviceAnnotation.value().getName()); AsyncServiceMethodModel.Builder methodBuilder = new AsyncServiceMethodModel.Builder(); for (JMethod asyncMethod : m.getReturnType().isClassOrInterface().getMethods()) { TreeLogger methodLogger = serviceLogger.branch( Type.DEBUG, "Reading service method " + asyncMethod.getReadableDeclaration()); List<JType> types = new ArrayList<JType>(); methodBuilder.setReturnTypeName(voidType.getParameterizedQualifiedSourceName()); boolean asyncFound = false; for (JType param : asyncMethod.getParameterTypes()) { if (asyncFound) { methodLogger.log( Type.WARN, "Already passed an AsyncCallback param - is that what you meant?"); } if (param.isClassOrInterface() != null && param.isClassOrInterface().isAssignableTo(asyncCallback)) { JClassType boxedReturnType = ModelUtils.findParameterizationOf(asyncCallback, param.isClassOrInterface())[0]; methodBuilder .setHasCallback(true) .setReturnTypeName(boxedReturnType.getParameterizedQualifiedSourceName()); asyncFound = true; continue; // should be last, check for this... } types.add(param); } Set<JClassType> throwables = new HashSet<JClassType>(); Throws t = asyncMethod.getAnnotation(Throws.class); if (t != null) { for (Class<? extends Throwable> throwable : t.value()) { throwables.add(typeOracle.findType(throwable.getName())); } } methodBuilder .setMethodName(asyncMethod.getName()) .setArgTypes(types) .setThrowables(throwables); serviceBuilder.addMethod(methodBuilder.build()); } rqBuilder.addService(serviceBuilder.build()); } return rqBuilder.build(); }