001    /**
002     * Copyright (C) 2009-2011 the original author or authors.
003     * See the notice.md file distributed with this work for additional
004     * information regarding copyright ownership.
005     *
006     * Licensed under the Apache License, Version 2.0 (the "License");
007     * you may not use this file except in compliance with the License.
008     * You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    
019    package org.fusesource.restygwt.rebind;
020    
021    import java.lang.annotation.Annotation;
022    import java.lang.reflect.InvocationTargetException;
023    import java.util.ArrayList;
024    import java.util.Arrays;
025    import java.util.HashMap;
026    import java.util.HashSet;
027    import java.util.LinkedList;
028    import java.util.List;
029    import java.util.Map;
030    import java.util.Set;
031    import java.util.regex.Pattern;
032    
033    import javax.ws.rs.Consumes;
034    import javax.ws.rs.DELETE;
035    import javax.ws.rs.GET;
036    import javax.ws.rs.HEAD;
037    import javax.ws.rs.HeaderParam;
038    import javax.ws.rs.OPTIONS;
039    import javax.ws.rs.POST;
040    import javax.ws.rs.PUT;
041    import javax.ws.rs.Path;
042    import javax.ws.rs.PathParam;
043    import javax.ws.rs.Produces;
044    import javax.ws.rs.QueryParam;
045    
046    import org.fusesource.restygwt.client.AbstractAsyncCallback;
047    import org.fusesource.restygwt.client.AbstractRequestCallback;
048    import org.fusesource.restygwt.client.Attribute;
049    import org.fusesource.restygwt.client.Defaults;
050    import org.fusesource.restygwt.client.Dispatcher;
051    import org.fusesource.restygwt.client.JSONP;
052    import org.fusesource.restygwt.client.Json;
053    import org.fusesource.restygwt.client.Json.Style;
054    import org.fusesource.restygwt.client.JsonCallback;
055    import org.fusesource.restygwt.client.JsonpMethod;
056    import org.fusesource.restygwt.client.Method;
057    import org.fusesource.restygwt.client.MethodCallback;
058    import org.fusesource.restygwt.client.Options;
059    import org.fusesource.restygwt.client.OverlayCallback;
060    import org.fusesource.restygwt.client.Resource;
061    import org.fusesource.restygwt.client.ResponseFormatException;
062    import org.fusesource.restygwt.client.RestService;
063    import org.fusesource.restygwt.client.RestServiceProxy;
064    import org.fusesource.restygwt.client.TextCallback;
065    import org.fusesource.restygwt.client.XmlCallback;
066    
067    import com.google.gwt.core.client.JavaScriptObject;
068    import com.google.gwt.core.client.JsArray;
069    import com.google.gwt.core.client.JsArrayBoolean;
070    import com.google.gwt.core.client.JsArrayInteger;
071    import com.google.gwt.core.client.JsArrayNumber;
072    import com.google.gwt.core.client.JsArrayString;
073    import com.google.gwt.core.ext.GeneratorContext;
074    import com.google.gwt.core.ext.TreeLogger;
075    import com.google.gwt.core.ext.UnableToCompleteException;
076    import com.google.gwt.core.ext.typeinfo.JClassType;
077    import com.google.gwt.core.ext.typeinfo.JGenericType;
078    import com.google.gwt.core.ext.typeinfo.JMethod;
079    import com.google.gwt.core.ext.typeinfo.JParameter;
080    import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
081    import com.google.gwt.core.ext.typeinfo.JType;
082    import com.google.gwt.core.ext.typeinfo.JTypeParameter;
083    import com.google.gwt.http.client.RequestException;
084    import com.google.gwt.json.client.JSONArray;
085    import com.google.gwt.json.client.JSONObject;
086    import com.google.gwt.json.client.JSONParser;
087    import com.google.gwt.json.client.JSONValue;
088    import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
089    import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
090    import com.google.gwt.xml.client.Document;
091    
092    /**
093     *
094     * @author <a href="http://hiramchirino.com">Hiram Chirino</a>
095     *
096     *         Updates: added automatically create resource from Path annotation,
097     *         enhanced generics support
098     * @author <a href="http://www.acuedo.com">Dave Finch</a>
099     */
100    public class RestServiceClassCreator extends BaseSourceCreator {
101    
102        private static final String REST_SERVICE_PROXY_SUFFIX = "_Generated_RestServiceProxy_";
103    
104        private static final String METHOD_CLASS = Method.class.getName();
105        private static final String RESOURCE_CLASS = Resource.class.getName();
106        private static final String DISPATCHER_CLASS = Dispatcher.class.getName();
107        private static final String DEFAULTS_CLASS = Defaults.class.getName();
108        private static final String ABSTRACT_REQUEST_CALLBACK_CLASS = AbstractRequestCallback.class.getName();
109        private static final String ABSTRACT_ASYNC_CALLBACK_CLASS = AbstractAsyncCallback.class.getName();
110        private static final String JSON_PARSER_CLASS = JSONParser.class.getName();
111        private static final String JSON_ARRAY_CLASS = JSONArray.class.getName();
112        private static final String JSON_OBJECT_CLASS = JSONObject.class.getName();
113        private static final String REQUEST_EXCEPTION_CLASS = RequestException.class.getName();
114        private static final String RESPONSE_FORMAT_EXCEPTION_CLASS = ResponseFormatException.class.getName();
115        private static final String JSONP_METHOD_CLASS = JsonpMethod.class.getName();
116    
117        /*
118         * static class in which are some compile-time relevant infos.
119         *
120         * TODO (andi): too much flexibility and overhead with reflection here?
121         */
122        private static final Class<BindingDefaults> BINDING_DEFAULTS = BindingDefaults.class;
123    
124        private static final String METHOD_JSONP = "jsonp";
125        private static final String METHOD_PUT = "put";
126        private static final String METHOD_POST = "post";
127        private static final String METHOD_OPTIONS = "options";
128        private static final String METHOD_HEAD = "head";
129        private static final String METHOD_GET = "get";
130        private static final String METHOD_DELETE = "delete";
131    
132        private static final HashSet<String> REST_METHODS = new HashSet<String>(8);
133        static {
134            REST_METHODS.add(METHOD_DELETE);
135            REST_METHODS.add(METHOD_GET);
136            REST_METHODS.add(METHOD_HEAD);
137            REST_METHODS.add(METHOD_OPTIONS);
138            REST_METHODS.add(METHOD_POST);
139            REST_METHODS.add(METHOD_PUT);
140            REST_METHODS.add(METHOD_JSONP);
141        }
142    
143        private JClassType XML_CALLBACK_TYPE;
144        private JClassType METHOD_CALLBACK_TYPE;
145        private JClassType TEXT_CALLBACK_TYPE;
146        private JClassType JSON_CALLBACK_TYPE;
147        private JClassType OVERLAY_CALLBACK_TYPE;
148        private JClassType DOCUMENT_TYPE;
149        private JClassType METHOD_TYPE;
150        private JClassType STRING_TYPE;
151        private JClassType JSON_VALUE_TYPE;
152        private JClassType OVERLAY_VALUE_TYPE;
153        private Set<JClassType> OVERLAY_ARRAY_TYPES;
154        private Set<JClassType> QUERY_PARAM_LIST_TYPES;
155        private JClassType REST_SERVICE_TYPE;
156        private JsonEncoderDecoderInstanceLocator locator;
157    
158        public RestServiceClassCreator(TreeLogger logger, GeneratorContext context, JClassType source) throws UnableToCompleteException {
159            super(logger, context, source, REST_SERVICE_PROXY_SUFFIX);
160        }
161    
162        @Override
163        protected ClassSourceFileComposerFactory createComposerFactory() {
164            String parameters = "";
165            if(source instanceof JGenericType)
166            {
167                    JGenericType gtype = (JGenericType)source;
168                            StringBuilder builder = new StringBuilder();
169                            builder.append("<");
170                            boolean first = true;
171                            for(JTypeParameter arg : gtype.getTypeParameters())
172                            {
173                                    if(!first)
174                                            builder.append(",");
175                                    builder.append(arg.getName());
176                                    builder.append(" extends ");
177                                    builder.append(arg.getFirstBound().getParameterizedQualifiedSourceName());
178                                    first = false;
179                            }
180                            builder.append(">");
181                            parameters = builder.toString();
182            }
183            
184            ClassSourceFileComposerFactory composerFactory = new ClassSourceFileComposerFactory(packageName, shortName + parameters);
185            composerFactory.addImplementedInterface(source.getParameterizedQualifiedSourceName());
186            composerFactory.addImplementedInterface(RestServiceProxy.class.getName());
187            return composerFactory;
188        }
189    
190        @Override
191        protected void generate() throws UnableToCompleteException {
192    
193            if (source.isInterface() == null) {
194                error("Type is not an interface.");
195            }
196    
197            locator = new JsonEncoderDecoderInstanceLocator(context, logger);
198    
199            this.XML_CALLBACK_TYPE = find(XmlCallback.class);
200            this.METHOD_CALLBACK_TYPE = find(MethodCallback.class);
201            this.TEXT_CALLBACK_TYPE = find(TextCallback.class);
202            this.JSON_CALLBACK_TYPE = find(JsonCallback.class);
203            this.OVERLAY_CALLBACK_TYPE = find(OverlayCallback.class);
204            this.DOCUMENT_TYPE = find(Document.class);
205            this.METHOD_TYPE = find(Method.class);
206            this.STRING_TYPE = find(String.class);
207            this.JSON_VALUE_TYPE = find(JSONValue.class);
208            this.OVERLAY_VALUE_TYPE = find(JavaScriptObject.class);
209            this.OVERLAY_ARRAY_TYPES = new HashSet<JClassType>();
210            this.OVERLAY_ARRAY_TYPES.add(find(JsArray.class));
211            this.OVERLAY_ARRAY_TYPES.add(find(JsArrayBoolean.class));
212            this.OVERLAY_ARRAY_TYPES.add(find(JsArrayInteger.class));
213            this.OVERLAY_ARRAY_TYPES.add(find(JsArrayNumber.class));
214            this.OVERLAY_ARRAY_TYPES.add(find(JsArrayString.class));
215            this.QUERY_PARAM_LIST_TYPES = new HashSet<JClassType>();
216            this.QUERY_PARAM_LIST_TYPES.add(find(List.class));
217            this.QUERY_PARAM_LIST_TYPES.add(find(Set.class));
218                    this.REST_SERVICE_TYPE = find(RestService.class);
219                    
220            String path = null;
221            Path pathAnnotation = source.getAnnotation(Path.class);
222            if (pathAnnotation != null) {
223                path = pathAnnotation.value();
224            }
225    
226            RemoteServiceRelativePath relativePath = source.getAnnotation(RemoteServiceRelativePath.class);
227            if (relativePath != null) {
228                path = relativePath.value();
229            }
230    
231            if (path == null) {
232                p("private " + RESOURCE_CLASS + " resource = new " + RESOURCE_CLASS + "(" + DEFAULTS_CLASS + ".getServiceRoot());");
233            } else {
234                p("private " + RESOURCE_CLASS + " resource = new " + RESOURCE_CLASS + "(" + DEFAULTS_CLASS + ".getServiceRoot()).resolve("+quote(path)+");");
235            }
236            p();
237    
238            p("public void setResource(" + RESOURCE_CLASS + " resource) {").i(1);
239            {
240                p("this.resource = resource;");
241            }
242            i(-1).p("}");
243    
244            p("public " + RESOURCE_CLASS + " getResource() {").i(1);
245            {
246                p("return this.resource;");
247            }
248            i(-1).p("}");
249    
250    
251            Options options = source.getAnnotation(Options.class);
252            if( options!=null && options.dispatcher()!=Dispatcher.class ) {
253                p("private " + DISPATCHER_CLASS + " dispatcher = "+options.dispatcher().getName()+".INSTANCE;");
254            } else {
255                p("private " + DISPATCHER_CLASS + " dispatcher = "+DEFAULTS_CLASS+".getDispatcher();");
256            }
257    
258            p();
259            p("public void setDispatcher(" + DISPATCHER_CLASS + " dispatcher) {").i(1);
260            {
261                p("this.dispatcher = dispatcher;");
262            }
263            i(-1).p("}");
264    
265            p();
266            p("public " + DISPATCHER_CLASS + " getDispatcher() {").i(1);
267            {
268                p("return this.dispatcher;");
269            }
270            i(-1).p("}");
271    
272            for (JMethod method : source.getInheritableMethods()) {
273                    JClassType iface = method.getReturnType().isInterface();
274                    if(iface != null && REST_SERVICE_TYPE.isAssignableFrom(iface))
275                            writeSubresourceLocatorImpl(method);
276                    else
277                    writeMethodImpl(method);
278            }
279        }
280    
281        private String quote(String path) {
282            // TODO: unlikely to occur. but we should escape chars like newlines..
283            return "\"" + path + "\"";
284        }
285    
286        private boolean isOverlayArrayType(JClassType type) {
287            for (JClassType arrayType : OVERLAY_ARRAY_TYPES) {
288                if (type.isAssignableTo(arrayType)) {
289                    return true;
290                }
291            }
292            return false;
293        }
294    
295        private boolean isQueryParamListType(JClassType type) {
296            if (type.isParameterized() == null) {
297                return false;
298            }
299            for (JClassType listType : QUERY_PARAM_LIST_TYPES) {
300                if (type.isAssignableTo(listType)) {
301                    return true;
302                }
303            }
304            return false;
305        }
306            
307        private void writeSubresourceLocatorImpl(JMethod method) throws UnableToCompleteException
308        {
309            JClassType iface = method.getReturnType().isInterface();
310            if(iface == null || !REST_SERVICE_TYPE.isAssignableFrom(iface)) {
311                    error("Invalid subresource locator method. Method must have return type of an interface that extends RestService: " + method.getReadableDeclaration());
312            }
313            
314            Path pathAnnotation = method.getAnnotation(Path.class);
315            if (pathAnnotation == null) {
316                    error("Invalid subresource locator method. Method must have @Path annotation: " + method.getReadableDeclaration());
317            }
318            String pathExpression = wrap(pathAnnotation.value());
319    
320            for (JParameter arg : method.getParameters()) {
321                PathParam paramPath = arg.getAnnotation(PathParam.class);
322                if (paramPath != null) {
323                    pathExpression = pathExpression.replaceAll(Pattern.quote("{" + paramPath.value() + "}"), "\"+" + toStringExpression(arg) + "+\"");
324                    if (arg.getAnnotation(Attribute.class) != null) {
325                                            error("Attribute annotations not allowed on subresource locators");
326                    }
327                }
328            }
329    
330    
331            p(method.getReadableDeclaration(false, false, false, false, true) + " {").i(1);
332            {
333                    JType type = method.getReturnType();
334                    String name;
335                    if(type instanceof JClassType)
336                    {
337                    JClassType restService = (JClassType)type;
338                    RestServiceClassCreator generator = new RestServiceClassCreator(logger, context, restService);
339                    name = generator.create();
340                    }
341                    else
342                    {
343                            throw new UnsupportedOperationException("Subresource method may not return: " + type);
344                    }
345                    p(method.getReturnType().getQualifiedSourceName() + " __subresource = new " + name + "();");
346                    p("((" + RestServiceProxy.class.getName() + ")__subresource).setResource(getResource().resolve(" + pathExpression + "));");
347                    p("return __subresource;");
348            }
349            i(-1).p("}");
350        }
351        
352        private void writeMethodImpl(JMethod method) throws UnableToCompleteException {
353            if (method.getReturnType().isPrimitive() != JPrimitiveType.VOID) {
354                error("Invalid rest method. Method must have void return type: " + method.getReadableDeclaration());
355            }
356    
357            Json jsonAnnotation = source.getAnnotation(Json.class);
358            final Style classStyle = jsonAnnotation != null ? jsonAnnotation.style() : Style.DEFAULT;
359    
360            Options classOptions = source.getAnnotation(Options.class);
361            Options options = method.getAnnotation(Options.class);
362    
363            p(method.getReadableDeclaration(false, false, false, false, true) + " {").i(1);
364            {
365                String restMethod = getRestMethod(method);
366                LinkedList<JParameter> args = new LinkedList<JParameter>(Arrays.asList(method.getParameters()));
367    
368                // the last arg should be the callback.
369                if (args.isEmpty()) {
370                    error("Invalid rest method. Method must declare at least a callback argument: " + method.getReadableDeclaration());
371                }
372                JParameter callbackArg = args.removeLast();
373                JClassType callbackType = callbackArg.getType().isClassOrInterface();
374                JClassType methodCallbackType = METHOD_CALLBACK_TYPE;
375                if (callbackType == null || !callbackType.isAssignableTo(methodCallbackType)) {
376                    error("Invalid rest method. Last argument must be a " + methodCallbackType.getName() + " type: " + method.getReadableDeclaration());
377                }
378                JClassType resultType = getCallbackTypeGenericClass(callbackType);
379    
380                String pathExpression = null;
381                Path pathAnnotation = method.getAnnotation(Path.class);
382                if (pathAnnotation != null) {
383                    pathExpression = wrap(pathAnnotation.value());
384                }
385    
386                JParameter contentArg = null;
387                HashMap<String, JParameter> queryParams = new HashMap<String, JParameter>();
388                HashMap<String, JParameter> headerParams = new HashMap<String, JParameter>();
389    
390                for (JParameter arg : args) {
391                    PathParam paramPath = arg.getAnnotation(PathParam.class);
392                    if (paramPath != null) {
393                        if (pathExpression == null) {
394                            error("Invalid rest method.  Invalid @PathParam annotation. Method is missing the @Path annotation: " + method.getReadableDeclaration());
395                        }
396                        pathExpression = pathExpression.replaceAll(Pattern.quote("{" + paramPath.value() + "}"), "\"+" + toStringExpression(arg) + "+\"");
397                        if (arg.getAnnotation(Attribute.class) != null) {
398                            // allow part of the arg-object participate in as PathParam and the object goes over the wire
399                            contentArg = arg;
400                        }
401                        continue;
402                    }
403    
404                    QueryParam queryParam = arg.getAnnotation(QueryParam.class);
405                    if (queryParam != null) {
406                        queryParams.put(queryParam.value(), arg);
407                        continue;
408                    }
409    
410                    HeaderParam headerParam = arg.getAnnotation(HeaderParam.class);
411                    if (headerParam != null) {
412                        headerParams.put(headerParam.value(), arg);
413                        continue;
414                    }
415    
416                    if (contentArg != null) {
417                        error("Invalid rest method. Only one content parameter is supported: " + method.getReadableDeclaration());
418                    }
419                    contentArg = arg;
420                }
421    
422                String acceptTypeBuiltIn = null;
423                if (callbackType.equals(TEXT_CALLBACK_TYPE)) {
424                    acceptTypeBuiltIn = "CONTENT_TYPE_TEXT";
425                } else if (callbackType.equals(JSON_CALLBACK_TYPE)) {
426                    acceptTypeBuiltIn = "CONTENT_TYPE_JSON";
427                } else if (callbackType.isAssignableTo(OVERLAY_CALLBACK_TYPE)) {
428                    acceptTypeBuiltIn = "CONTENT_TYPE_JSON";
429                } else if (callbackType.equals(XML_CALLBACK_TYPE)) {
430                    acceptTypeBuiltIn = "CONTENT_TYPE_XML";
431                }
432    
433                p("final " + METHOD_CLASS + " __method =");
434    
435                p("this.resource");
436                if (pathExpression != null) {
437                    p(".resolve(" + pathExpression + ")");
438                }
439                for (Map.Entry<String, JParameter> entry : queryParams.entrySet()) {
440                    String expr = entry.getValue().getName();
441                    JClassType type = entry.getValue().getType().isClassOrInterface();
442                    if (type != null && isQueryParamListType(type)) {
443                        p(".addQueryParams(" + wrap(entry.getKey()) + ", " +
444                          toIteratedStringExpression(entry.getValue()) + ")");
445                    } else {
446                        p(".addQueryParam(" + wrap(entry.getKey()) + ", " +
447                          toStringExpression(entry.getValue().getType(), expr) + ")");
448                    }
449                }
450                // example: .get()
451                p("." + restMethod + "();");
452    
453                // Handle JSONP specific configuration...
454                JSONP jsonpAnnotation = method.getAnnotation(JSONP.class);
455    
456                final boolean isJsonp = restMethod.equals(METHOD_JSONP) && jsonpAnnotation!=null;
457                if( isJsonp ) {
458                    if( jsonpAnnotation.callbackParam().length() > 0 ) {
459                        p("(("+JSONP_METHOD_CLASS+")__method).callbackParam("+wrap(jsonpAnnotation.callbackParam())+");");
460                    }
461                    if( jsonpAnnotation.failureCallbackParam().length() > 0 ) {
462                        p("(("+JSONP_METHOD_CLASS+")__method).failureCallbackParam("+wrap(jsonpAnnotation.failureCallbackParam())+");");
463                    }
464                }
465    
466                // configure the dispatcher
467                if( options!=null && options.dispatcher()!=Dispatcher.class ) {
468                    // use the dispatcher configured for the method.
469                    p("__method.setDispatcher("+options.dispatcher().getName()+".INSTANCE);");
470                } else {
471                    // use the default dispatcher configured for the service..
472                    p("__method.setDispatcher(this.dispatcher);");
473                }
474    
475                // configure the expected statuses..
476                if( options!=null && options.expect().length!=0 ) {
477                    // Using method level defined expected status
478                    p("__method.expect("+join(options.expect(), ", ")+");");
479                } else if( classOptions!=null && classOptions.expect().length!=0 ) {
480                    // Using class level defined expected status
481                    p("__method.expect("+join(classOptions.expect(), ", ")+");");
482                }
483    
484                // configure the timeout
485                if( options!=null && options.timeout() >= 0 ) {
486                    // Using method level defined value
487                    p("__method.timeout("+options.timeout()+");");
488                } else if( classOptions!=null && classOptions.timeout() >= 0 ) {
489                    // Using class level defined value
490                    p("__method.timeout("+classOptions.timeout()+");");
491                }
492    
493                if(jsonpAnnotation == null) {
494                    Produces producesAnnotation = findAnnotationOnMethodOrEnclosingType(method, Produces.class);
495                    if (producesAnnotation != null) {
496                        p("__method.header(" + RESOURCE_CLASS + ".HEADER_ACCEPT, "+wrap(producesAnnotation.value()[0])+");");
497                    } else {
498                        // set the default accept header....
499                        if (acceptTypeBuiltIn != null) {
500                            p("__method.header(" + RESOURCE_CLASS + ".HEADER_ACCEPT, " + RESOURCE_CLASS + "." + acceptTypeBuiltIn + ");");
501                        } else {
502                            p("__method.header(" + RESOURCE_CLASS + ".HEADER_ACCEPT, " + RESOURCE_CLASS + ".CONTENT_TYPE_JSON);");
503                        }
504                    }
505    
506                    Consumes consumesAnnotation = findAnnotationOnMethodOrEnclosingType(method, Consumes.class);
507                    if (consumesAnnotation != null) {
508                        p("__method.header(" + RESOURCE_CLASS + ".HEADER_CONTENT_TYPE, "+wrap(consumesAnnotation.value()[0])+");");
509                    }
510    
511                    // and set the explicit headers now (could override the accept header)
512                    for (Map.Entry<String, JParameter> entry : headerParams.entrySet()) {
513                        String expr = entry.getValue().getName();
514                        p("__method.header(" + wrap(entry.getKey()) + ", " + toStringExpression(entry.getValue().getType(), expr) + ");");
515                    }
516                }
517    
518                if (contentArg != null) {
519                    if (contentArg.getType() == STRING_TYPE) {
520                        p("__method.text(" + contentArg.getName() + ");");
521                    } else if (contentArg.getType() == JSON_VALUE_TYPE) {
522                        p("__method.json(" + contentArg.getName() + ");");
523                    } else if (contentArg.getType().isClass() != null &&
524                               isOverlayArrayType(contentArg.getType().isClass())) {
525                        p("__method.json(new " + JSON_ARRAY_CLASS + "(" + contentArg.getName() + "));");
526                    } else if (contentArg.getType().isClass() != null &&
527                               contentArg.getType().isClass().isAssignableTo(OVERLAY_VALUE_TYPE)) {
528                        p("__method.json(new " + JSON_OBJECT_CLASS + "(" + contentArg.getName() + "));");
529                    } else if (contentArg.getType() == DOCUMENT_TYPE) {
530                        p("__method.xml(" + contentArg.getName() + ");");
531                    } else {
532                        JClassType contentClass = contentArg.getType().isClass();
533                        if (contentClass == null) {
534                            contentClass = contentArg.getType().isClassOrInterface();
535                            if (!locator.isCollectionType(contentClass)) {
536                                error("Content argument must be a class.");
537                            }
538                        }
539    
540                        jsonAnnotation = contentArg.getAnnotation(Json.class);
541                        Style style = jsonAnnotation != null ? jsonAnnotation.style() : classStyle;
542    
543                        // example:
544                        // .json(Listings$_Generated_JsonEncoder_$.INSTANCE.encode(arg0)
545                        // )
546                        p("__method.json(" + locator.encodeExpression(contentClass, contentArg.getName(), style) + ");");
547                    }
548                }
549    
550    
551                List<AnnotationResolver> annotationResolvers = getAnnotationResolvers(context, logger);
552                logger.log(TreeLogger.DEBUG, "found " + annotationResolvers.size() + " additional AnnotationResolvers");
553    
554                for (AnnotationResolver a : annotationResolvers) {
555                    logger.log(TreeLogger.DEBUG, "(" + a.getClass().getName() + ") resolve `" + source.getName()
556                            + "#" + method.getName() + "´ ...");
557                    final Map<String, String[]> addDataParams = a.resolveAnnotation(logger, source, method, restMethod);
558    
559                    if (addDataParams != null) {
560                        for (String s : addDataParams.keySet()) {
561                            final StringBuilder sb = new StringBuilder();
562                            final List<String> classList = Arrays.asList(addDataParams.get(s));
563    
564                            sb.append("[");
565                            for (int i = 0; i < classList.size(); ++i) {
566                                sb.append("\\\"").append(classList.get(i)).append("\\\"");
567    
568                                if ((i+1) <  classList.size()) {
569                                    sb.append(",");
570                                }
571                            }
572                            sb.append("]");
573    
574                            logger.log(TreeLogger.DEBUG, "add call with (\"" + s + "\", \"" +
575                                    sb.toString() + "\")");
576                            p("__method.addData(\"" + s + "\", \"" + sb.toString() + "\");");
577                        }
578                    }
579                }
580    
581    
582                if (acceptTypeBuiltIn != null) {
583                    p("__method.send(" + callbackArg.getName() + ");");
584                } else if ( isJsonp ){
585                        p("((" + JSONP_METHOD_CLASS + ")__method).send(new " + ABSTRACT_ASYNC_CALLBACK_CLASS + "<" + resultType.getParameterizedQualifiedSourceName() + ">((" + JSONP_METHOD_CLASS + ")__method, "
586                                        + callbackArg.getName() + ") {").i(1);
587                        {
588                            p("protected " + resultType.getParameterizedQualifiedSourceName() + " parseResult(" + JSON_OBJECT_CLASS + " result) throws Exception {").i(1);
589                            {
590                                if(resultType.getParameterizedQualifiedSourceName().equals("java.lang.Void")) {
591                                    p("return (java.lang.Void) null;");
592                                }
593                                else {
594                                    p("try {").i(1);
595                                    {
596                                        jsonAnnotation = method.getAnnotation(Json.class);
597                                        Style style = jsonAnnotation != null ? jsonAnnotation.style() : classStyle;
598                                        p("return " + locator.decodeExpression(resultType, "result", style) + ";");
599                                    }
600                                    i(-1).p("} catch (Throwable __e) {").i(1);
601                                    {
602                                        p("throw new " + RESPONSE_FORMAT_EXCEPTION_CLASS + "(\"Response was NOT a valid JSON document\", __e);");
603                                    }
604                                    i(-1).p("}");
605                                }
606                            }
607                            i(-1).p("}");
608                        }
609                        i(-1).p("});");
610                } else {
611                    p("try {").i(1);
612                    {
613                        p("__method.send(new " + ABSTRACT_REQUEST_CALLBACK_CLASS + "<" + resultType.getParameterizedQualifiedSourceName() + ">(__method, "
614                                        + callbackArg.getName() + ") {").i(1);
615                        {
616                            p("protected " + resultType.getParameterizedQualifiedSourceName() + " parseResult() throws Exception {").i(1);
617                            {
618                                if(resultType.getParameterizedQualifiedSourceName().equals("java.lang.Void")) {
619                                    p("return (java.lang.Void) null;");
620                                }
621                                else {
622                                    p("try {").i(1);
623                                    {
624                                        jsonAnnotation = method.getAnnotation(Json.class);
625                                        Style style = jsonAnnotation != null ? jsonAnnotation.style() : classStyle;
626                                        p("return " + locator.decodeExpression(resultType, JSON_PARSER_CLASS + ".parse(__method.getResponse().getText())", style) + ";");
627                                    }
628                                    i(-1).p("} catch (Throwable __e) {").i(1);
629                                    {
630                                        p("throw new " + RESPONSE_FORMAT_EXCEPTION_CLASS + "(\"Response was NOT a valid JSON document\", __e);");
631                                    }
632                                    i(-1).p("}");
633                                }
634                            }
635                            i(-1).p("}");
636                        }
637                        i(-1).p("});");
638                    }
639                    i(-1).p("} catch (" + REQUEST_EXCEPTION_CLASS + " __e) {").i(1);
640                    {
641                        p(callbackArg.getName() + ".onFailure(__method,__e);");
642                    }
643                    i(-1).p("}");
644                }
645            }
646            i(-1).p("}");
647        }
648    
649        private <T extends Annotation> T findAnnotationOnMethodOrEnclosingType(final JMethod method, final Class<T> annotationType) {
650            T annotation = method.getAnnotation(annotationType);
651            if (annotation == null) {
652                annotation = method.getEnclosingType().getAnnotation(annotationType);
653            }
654            return annotation;
655        }
656    
657        protected String toStringExpression(JParameter arg) {
658            Attribute attribute = arg.getAnnotation(Attribute.class);
659            if(attribute != null){
660                return arg.getName() + "." + attribute.value();
661            }
662            return toStringExpression(arg.getType(), arg.getName());
663        }
664    
665        protected String toStringExpression(JType type, String expr) {
666            if (type.isPrimitive() != null) {
667                return "\"\"+" + expr;
668            }
669            if (STRING_TYPE == type) {
670                return expr;
671            }
672            if (type.isClass() != null &&
673                isOverlayArrayType(type.isClass())) {
674              return "(new " + JSON_ARRAY_CLASS + "(" + expr + ")).toString()";
675            }
676            if (type.isClass() != null &&
677                OVERLAY_VALUE_TYPE.isAssignableFrom(type.isClass())) {
678              return "(new " + JSON_OBJECT_CLASS + "(" + expr + ")).toString()";
679            }
680    
681            return expr + ".toString()";
682        }
683    
684        protected String toIteratedStringExpression(JParameter arg) {
685            StringBuilder result = new StringBuilder();
686            result.append("new org.fusesource.restygwt.client.StringIterable (")
687                .append(arg.getName()).append(")");
688    
689            return result.toString();
690        }
691    
692        private JClassType getCallbackTypeGenericClass(final JClassType callbackType) throws UnableToCompleteException {
693            return branch("getCallbackTypeGenericClass()", new Branch<JClassType>() {
694                public JClassType execute() throws UnableToCompleteException {
695    
696                    for (JMethod method : callbackType.getOverridableMethods()) {
697                        debug("checking method: " + method.getName());
698                        if (method.getName().equals("onSuccess")) {
699                            JParameter[] parameters = method.getParameters();
700                            debug("checking method params: " + parameters.length);
701                            if (parameters.length == 2) {
702                                debug("checking first param: " + parameters[0].getType());
703                                if (parameters[0].getType() == METHOD_TYPE) {
704                                    debug("checking 2nd param: " + parameters[1].getType());
705                                    JType param2Type = parameters[1].getType();
706                                    JClassType type = param2Type.isClassOrInterface();
707                                    if (type == null) {
708                                        error("The type of the callback not supported: " + param2Type.getJNISignature());
709                                    }
710                                    debug("match: " + type);
711                                    return type;
712                                }
713                            }
714                        }
715                    }
716                    error("The type of the callback could not be determined: " + callbackType.getParameterizedQualifiedSourceName());
717                    return null;
718    
719                }
720            });
721        }
722    
723        private String getRestMethod(JMethod method) throws UnableToCompleteException {
724            String restMethod = null;
725            if (method.getAnnotation(DELETE.class) != null) {
726                restMethod = METHOD_DELETE;
727            } else if (method.getAnnotation(GET.class) != null) {
728                restMethod = METHOD_GET;
729            } else if (method.getAnnotation(HEAD.class) != null) {
730                restMethod = METHOD_HEAD;
731            } else if (method.getAnnotation(OPTIONS.class) != null) {
732                restMethod = METHOD_OPTIONS;
733            } else if (method.getAnnotation(POST.class) != null) {
734                restMethod = METHOD_POST;
735            } else if (method.getAnnotation(PUT.class) != null) {
736                restMethod = METHOD_PUT;
737            } else if (method.getAnnotation(JSONP.class) != null) {
738                restMethod = METHOD_JSONP;
739            } else {
740                restMethod = method.getName();
741                if (!REST_METHODS.contains(restMethod)) {
742                    error("Invalid rest method. It must either have a lower case rest method name or have a javax rs method annotation: " + method.getReadableDeclaration());
743                }
744            }
745            return restMethod;
746        }
747    
748        /**
749         * access additional AnnotationResolvers possibly added by
750         *
751         * {@link BindingDefaults#addAnnotationResolver(AnnotationResolver)}
752         * @return
753         */
754        @SuppressWarnings("unchecked")
755        private List<AnnotationResolver> getAnnotationResolvers(final GeneratorContext context, final TreeLogger logger) {
756            java.lang.reflect.Method m = null;
757            ArrayList args = new ArrayList();
758            ArrayList types = new ArrayList();
759    
760            types.add(GeneratorContext.class);
761            args.add(context);
762            types.add(TreeLogger.class);
763            args.add(logger);
764    
765            Object[] argValues = args.toArray();
766            Class[] argtypes = (Class[]) types.toArray(new Class[argValues.length]);
767    
768            try {
769                 m = BINDING_DEFAULTS.getMethod("getAnnotationResolvers", argtypes);
770            } catch (SecurityException e) {
771                throw new RuntimeException("could not call method `getAnnotationResolvers´ on "
772                        + BINDING_DEFAULTS, e);
773            } catch (NoSuchMethodException e) {
774                throw new RuntimeException("could not resolve method `getAnnotationResolvers´ on "
775                        + BINDING_DEFAULTS, e);
776            }
777    
778            List<AnnotationResolver> l = new ArrayList<AnnotationResolver>();
779            try {
780                 Object[] params = new Object[]{context};
781    
782                l = (List<AnnotationResolver>) m.invoke(null, context, logger);
783            } catch (IllegalArgumentException e) {
784                throw new RuntimeException("could not call method `getAnnotationResolvers´ on "
785                        + BINDING_DEFAULTS, e);
786            } catch (IllegalAccessException e) {
787                throw new RuntimeException("could not call method `getAnnotationResolvers´ on "
788                        + BINDING_DEFAULTS, e);
789            } catch (InvocationTargetException e) {
790                throw new RuntimeException("could not call method `getAnnotationResolvers´ on "
791                        + BINDING_DEFAULTS, e);
792            }
793    
794            return l;
795        }
796    }