EMMA Coverage Report (generated Wed Oct 26 13:47:17 EDT 2011)
[all classes][org.jclouds.rest.internal]

COVERAGE SUMMARY FOR SOURCE FILE [RestAnnotationProcessor.java]

nameclass, %method, %block, %line, %
RestAnnotationProcessor.java100% (16/16)98%  (78/80)83%  (3127/3746)85%  (548/648)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class RestAnnotationProcessor$MethodKey100% (1/1)100% (3/3)81%  (112/138)70%  (23.8/34)
equals (Object): boolean 100% (1/1)63%  (38/60)50%  (10/20)
hashCode (): int 100% (1/1)90%  (35/39)97%  (5.8/6)
RestAnnotationProcessor$MethodKey (Method): void 100% (1/1)100% (39/39)100% (8/8)
     
class RestAnnotationProcessor100% (1/1)96%  (52/54)83%  (2862/3455)85%  (503.2/593)
indexWithOnlyOneAnnotation (Method, String, Cache): Map 0%   (0/1)0%   (0/31)0%   (0/4)
replaceQuery (URI, String, Comparator): URI 0%   (0/1)0%   (0/9)0%   (0/1)
findOptionsIn (Method, Object []): HttpRequestOptions 100% (1/1)39%  (32/82)32%  (4.8/15)
getHttpMethodOrConstantOrThrowException (Method): String 100% (1/1)52%  (14/27)75%  (3/4)
getMapPayloadBinderOrNull (Method, Object []): MapBinder 100% (1/1)56%  (59/106)56%  (11.8/21)
decorateRequest (GeneratedHttpRequest): GeneratedHttpRequest 100% (1/1)58%  (179/307)61%  (23.2/38)
addProducesIfPresentOnTypeOrMethod (Multimap, Method): void 100% (1/1)63%  (22/35)71%  (5/7)
addForm (Multimap, FormParams, Collection): void 100% (1/1)65%  (30/46)67%  (4/6)
addMatrix (Multimap, MatrixParams, Collection): void 100% (1/1)65%  (30/46)67%  (4/6)
createExceptionParserOrThrowResourceNotFoundOn404IfNoAnnotation (Injector, Me... 100% (1/1)67%  (12/18)75%  (3/4)
getAcceptHeadersOrNull (Method): List 100% (1/1)69%  (22/32)75%  (6/8)
getMatrixParamKeyValues (Method, Object []): Multimap 100% (1/1)70%  (85/121)76%  (14.5/19)
createResponseParser (ParseSax$Factory, Injector, Method, HttpRequest): Function 100% (1/1)70%  (19/27)86%  (6/7)
getEndpointInParametersOrNull (Method, Object [], Injector): URI 100% (1/1)72%  (102/141)79%  (14.9/19)
getSaxResponseParserClassOrNull (Method): Class 100% (1/1)75%  (9/12)75%  (3/4)
setCaller (ClassMethodArgs): void 100% (1/1)75%  (24/32)64%  (7/11)
getQueryParamKeyValues (Method, Object []): Multimap 100% (1/1)77%  (93/121)82%  (15.5/19)
addMapPayload (Map, PayloadParams, Collection): void 100% (1/1)77%  (30/39)80%  (4/5)
buildPostParams (Method, Object []): Map 100% (1/1)78%  (76/98)80%  (11.9/15)
getFormParamKeyValues (Method, Object []): Multimap 100% (1/1)78%  (99/127)83%  (17.5/21)
getJsonParserKeyForMethodAnType (Method, Type): Key 100% (1/1)85%  (93/110)93%  (14/15)
createRequest (Method, Object []): GeneratedHttpRequest 100% (1/1)91%  (494/540)90%  (87/97)
findHttpRequestInArgs (Object []): HttpRequest 100% (1/1)92%  (24/26)83%  (5/6)
findPayloadInArgs (Object []): Payload 100% (1/1)95%  (37/39)88%  (7/8)
getParserOrThrowException (Method): Key 100% (1/1)95%  (115/121)89%  (16/18)
<static initializer> 100% (1/1)100% (81/81)100% (23/23)
RestAnnotationProcessor (Injector, Cache, String, ParseSax$Factory, HttpUtils... 100% (1/1)100% (53/53)100% (14/14)
access$100 (): Class 100% (1/1)100% (2/2)100% (1/1)
addConsumesIfPresentOnTypeOrMethod (Multimap, Method): void 100% (1/1)100% (12/12)100% (4/4)
addFormParams (Collection, Method, Object []): Multimap 100% (1/1)100% (59/59)100% (10/10)
addHeader (Multimap, Headers, Collection): void 100% (1/1)100% (27/27)100% (5/5)
addHeaderIfAnnotationPresentOnMethod (Multimap, Method, Collection): void 100% (1/1)100% (31/31)100% (7/7)
addMatrixParams (Collection, Method, Object []): Multimap 100% (1/1)100% (59/59)100% (10/10)
addPathAndGetTokens (Class, Method, Object [], UriBuilder): Multimap 100% (1/1)100% (25/25)100% (5/5)
addQuery (Multimap, QueryParams, Collection): void 100% (1/1)100% (46/46)100% (6/6)
addQueryParams (Collection, Method, Object []): Multimap 100% (1/1)100% (59/59)100% (10/10)
buildHeaders (Collection, Method, Object []): Multimap 100% (1/1)100% (67/67)100% (12/12)
createExceptionParserOrThrowResourceNotFoundOn404IfNoAnnotation (Method): Fun... 100% (1/1)100% (5/5)100% (1/1)
createMethodToIndexOfParamToAnnotation (Class): Cache 100% (1/1)100% (7/7)100% (1/1)
createResponseParser (Method, HttpRequest): Function 100% (1/1)100% (8/8)100% (1/1)
encodeValues (Multimap, char []): Multimap 100% (1/1)100% (26/26)100% (4/4)
filterOutContentHeaders (Multimap): Multimap 100% (1/1)100% (30/30)100% (5/5)
getDelegateOrNull (Method): Method 100% (1/1)100% (8/8)100% (1/1)
getEndpointFor (Method, Object [], Injector): URI 100% (1/1)100% (51/51)100% (9/9)
getFiltersIfAnnotated (Method): List 100% (1/1)100% (111/111)100% (14/14)
getHttpMethods (Method): Set 100% (1/1)100% (42/42)100% (7/7)
getJsonParserKeyForMethod (Method): Key 100% (1/1)100% (7/7)100% (2/2)
getParts (Method, Object [], Iterable): List 100% (1/1)100% (87/87)100% (16/16)
getPathParamKeyValues (Method, Object []): Multimap 100% (1/1)100% (121/121)100% (19/19)
getReturnTypeForMethod (Method): Type 100% (1/1)100% (40/40)100% (10/10)
getTransformerForMethod (Method, Injector): Function 100% (1/1)100% (54/54)100% (10/10)
indexWithAtLeastOneAnnotation (Method, Cache): Map 100% (1/1)100% (12/12)100% (2/2)
replaceQuery (Provider, URI, String, Comparator, char []): URI 100% (1/1)100% (19/19)100% (3/3)
shouldAddHostHeader (Method): boolean 100% (1/1)100% (13/13)100% (3/3)
     
class RestAnnotationProcessor$1100% (1/1)100% (2/2)100% (16/16)100% (2/2)
RestAnnotationProcessor$1 (Class): void 100% (1/1)100% (6/6)100% (1/1)
load (Method): Cache 100% (1/1)100% (10/10)100% (1/1)
     
class RestAnnotationProcessor$10100% (1/1)100% (1/1)100% (3/3)100% (1/1)
RestAnnotationProcessor$10 (): void 100% (1/1)100% (3/3)100% (1/1)
     
class RestAnnotationProcessor$11100% (1/1)100% (2/2)100% (11/11)100% (2/2)
RestAnnotationProcessor$11 (): void 100% (1/1)100% (3/3)100% (1/1)
apply (Set): boolean 100% (1/1)100% (8/8)100% (1/1)
     
class RestAnnotationProcessor$12100% (1/1)100% (2/2)100% (11/11)100% (2/2)
RestAnnotationProcessor$12 (): void 100% (1/1)100% (3/3)100% (1/1)
apply (Set): boolean 100% (1/1)100% (8/8)100% (1/1)
     
class RestAnnotationProcessor$2100% (1/1)100% (2/2)100% (11/11)100% (2/2)
RestAnnotationProcessor$2 (): void 100% (1/1)100% (3/3)100% (1/1)
apply (Map$Entry): Part 100% (1/1)100% (8/8)100% (1/1)
     
class RestAnnotationProcessor$3100% (1/1)100% (2/2)100% (35/35)100% (7/7)
RestAnnotationProcessor$3 (): void 100% (1/1)100% (3/3)100% (1/1)
load (Method): Set 100% (1/1)100% (32/32)100% (6/6)
     
class RestAnnotationProcessor$4100% (1/1)100% (2/2)100% (12/12)100% (2/2)
RestAnnotationProcessor$4 (Object []): void 100% (1/1)100% (6/6)100% (1/1)
apply (Integer): Object 100% (1/1)100% (6/6)100% (1/1)
     
class RestAnnotationProcessor$5100% (1/1)100% (1/1)100% (3/3)100% (1/1)
RestAnnotationProcessor$5 (): void 100% (1/1)100% (3/3)100% (1/1)
     
class RestAnnotationProcessor$6100% (1/1)100% (1/1)100% (3/3)100% (1/1)
RestAnnotationProcessor$6 (): void 100% (1/1)100% (3/3)100% (1/1)
     
class RestAnnotationProcessor$7100% (1/1)100% (1/1)100% (3/3)100% (1/1)
RestAnnotationProcessor$7 (): void 100% (1/1)100% (3/3)100% (1/1)
     
class RestAnnotationProcessor$8100% (1/1)100% (1/1)100% (3/3)100% (1/1)
RestAnnotationProcessor$8 (): void 100% (1/1)100% (3/3)100% (1/1)
     
class RestAnnotationProcessor$9100% (1/1)100% (1/1)100% (3/3)100% (1/1)
RestAnnotationProcessor$9 (): void 100% (1/1)100% (3/3)100% (1/1)
     
class RestAnnotationProcessor$GetAnnotationsForMethodParameterIndex100% (1/1)100% (3/3)100% (26/26)100% (6/6)
RestAnnotationProcessor$GetAnnotationsForMethodParameterIndex (Method, Class)... 100% (1/1)100% (9/9)100% (4/4)
access$000 (RestAnnotationProcessor$GetAnnotationsForMethodParameterIndex): C... 100% (1/1)100% (3/3)100% (1/1)
apply (Integer): Set 100% (1/1)100% (14/14)100% (1/1)
     
class RestAnnotationProcessor$GetAnnotationsForMethodParameterIndex$1100% (1/1)100% (2/2)100% (13/13)100% (2/2)
RestAnnotationProcessor$GetAnnotationsForMethodParameterIndex$1 (RestAnnotati... 100% (1/1)100% (6/6)100% (1/1)
apply (Annotation): boolean 100% (1/1)100% (7/7)100% (1/1)

1/**
2 * Licensed to jclouds, Inc. (jclouds) under one or more
3 * contributor license agreements.  See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership.  jclouds licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License.  You may obtain a copy of the License at
9 *
10 *   http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied.  See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19package org.jclouds.rest.internal;
20 
21import static com.google.common.base.Preconditions.checkArgument;
22import static com.google.common.collect.Collections2.filter;
23import static com.google.common.collect.Iterables.concat;
24import static com.google.common.collect.Iterables.get;
25import static com.google.common.collect.Iterables.transform;
26import static com.google.common.collect.Lists.newLinkedList;
27import static com.google.common.collect.Maps.filterValues;
28import static com.google.common.collect.Maps.newHashMap;
29import static com.google.common.collect.Sets.newTreeSet;
30import static java.util.Arrays.asList;
31import static javax.ws.rs.core.HttpHeaders.ACCEPT;
32import static javax.ws.rs.core.HttpHeaders.CONTENT_TYPE;
33import static javax.ws.rs.core.HttpHeaders.HOST;
34import static org.jclouds.io.Payloads.newPayload;
35 
36import java.io.InputStream;
37import java.lang.annotation.Annotation;
38import java.lang.reflect.Array;
39import java.lang.reflect.Method;
40import java.lang.reflect.ParameterizedType;
41import java.lang.reflect.Type;
42import java.lang.reflect.WildcardType;
43import java.net.URI;
44import java.util.Collection;
45import java.util.Collections;
46import java.util.Comparator;
47import java.util.List;
48import java.util.Map;
49import java.util.Set;
50import java.util.SortedSet;
51import java.util.Map.Entry;
52import java.util.concurrent.ExecutionException;
53 
54import javax.annotation.Resource;
55import javax.inject.Named;
56import javax.inject.Provider;
57import javax.ws.rs.Consumes;
58import javax.ws.rs.FormParam;
59import javax.ws.rs.HeaderParam;
60import javax.ws.rs.HttpMethod;
61import javax.ws.rs.MatrixParam;
62import javax.ws.rs.Path;
63import javax.ws.rs.PathParam;
64import javax.ws.rs.Produces;
65import javax.ws.rs.QueryParam;
66import javax.ws.rs.core.MediaType;
67import javax.ws.rs.core.UriBuilder;
68import javax.ws.rs.core.UriBuilderException;
69 
70import org.jclouds.Constants;
71import org.jclouds.functions.IdentityFunction;
72import org.jclouds.functions.OnlyElementOrNull;
73import org.jclouds.http.HttpRequest;
74import org.jclouds.http.HttpRequestFilter;
75import org.jclouds.http.HttpResponse;
76import org.jclouds.http.HttpUtils;
77import org.jclouds.http.functions.ParseFirstJsonValueNamed;
78import org.jclouds.http.functions.ParseJson;
79import org.jclouds.http.functions.ParseSax;
80import org.jclouds.http.functions.ParseURIFromListOrLocationHeaderIf20x;
81import org.jclouds.http.functions.ReleasePayloadAndReturn;
82import org.jclouds.http.functions.ReturnInputStream;
83import org.jclouds.http.functions.ReturnStringIf2xx;
84import org.jclouds.http.functions.ReturnTrueIf2xx;
85import org.jclouds.http.functions.UnwrapOnlyJsonValue;
86import org.jclouds.http.functions.UnwrapOnlyJsonValueInSet;
87import org.jclouds.http.functions.UnwrapOnlyNestedJsonValue;
88import org.jclouds.http.functions.UnwrapOnlyNestedJsonValueInSet;
89import org.jclouds.http.functions.ParseSax.HandlerWithResult;
90import org.jclouds.http.options.HttpRequestOptions;
91import org.jclouds.http.utils.ModifyRequest;
92import org.jclouds.internal.ClassMethodArgs;
93import org.jclouds.io.ContentMetadata;
94import org.jclouds.io.Payload;
95import org.jclouds.io.PayloadEnclosing;
96import org.jclouds.io.Payloads;
97import org.jclouds.io.payloads.MultipartForm;
98import org.jclouds.io.payloads.Part;
99import org.jclouds.io.payloads.Part.PartOptions;
100import org.jclouds.javax.annotation.Nullable;
101import org.jclouds.json.internal.GsonWrapper;
102import org.jclouds.logging.Logger;
103import org.jclouds.rest.Binder;
104import org.jclouds.rest.InputParamValidator;
105import org.jclouds.rest.InvocationContext;
106import org.jclouds.rest.annotations.BinderParam;
107import org.jclouds.rest.annotations.Endpoint;
108import org.jclouds.rest.annotations.EndpointParam;
109import org.jclouds.rest.annotations.ExceptionParser;
110import org.jclouds.rest.annotations.FormParams;
111import org.jclouds.rest.annotations.Headers;
112import org.jclouds.rest.annotations.MapBinder;
113import org.jclouds.rest.annotations.MatrixParams;
114import org.jclouds.rest.annotations.OnlyElement;
115import org.jclouds.rest.annotations.OverrideRequestFilters;
116import org.jclouds.rest.annotations.ParamParser;
117import org.jclouds.rest.annotations.PartParam;
118import org.jclouds.rest.annotations.PayloadParam;
119import org.jclouds.rest.annotations.PayloadParams;
120import org.jclouds.rest.annotations.QueryParams;
121import org.jclouds.rest.annotations.RequestFilters;
122import org.jclouds.rest.annotations.ResponseParser;
123import org.jclouds.rest.annotations.SelectJson;
124import org.jclouds.rest.annotations.SkipEncoding;
125import org.jclouds.rest.annotations.Unwrap;
126import org.jclouds.rest.annotations.VirtualHost;
127import org.jclouds.rest.annotations.WrapWith;
128import org.jclouds.rest.annotations.XMLResponseParser;
129import org.jclouds.rest.binders.BindMapToStringPayload;
130import org.jclouds.rest.binders.BindToJsonPayloadWrappedWith;
131import org.jclouds.rest.functions.MapHttp4xxCodesToExceptions;
132import org.jclouds.util.Maps2;
133import org.jclouds.util.Strings2;
134 
135import com.google.common.annotations.VisibleForTesting;
136import com.google.common.base.Function;
137import com.google.common.base.Functions;
138import com.google.common.base.Preconditions;
139import com.google.common.base.Predicate;
140import com.google.common.base.Predicates;
141import com.google.common.base.Throwables;
142import com.google.common.cache.Cache;
143import com.google.common.cache.CacheBuilder;
144import com.google.common.cache.CacheLoader;
145import com.google.common.collect.ImmutableList;
146import com.google.common.collect.ImmutableMultimap;
147import com.google.common.collect.ImmutableSet;
148import com.google.common.collect.Iterables;
149import com.google.common.collect.LinkedHashMultimap;
150import com.google.common.collect.LinkedListMultimap;
151import com.google.common.collect.Lists;
152import com.google.common.collect.Multimap;
153import com.google.common.collect.ImmutableSet.Builder;
154import com.google.common.util.concurrent.ListenableFuture;
155import com.google.inject.Inject;
156import com.google.inject.Injector;
157import com.google.inject.Key;
158import com.google.inject.TypeLiteral;
159import com.google.inject.util.Types;
160 
161/**
162 * Creates http methods based on annotations on a class or interface.
163 * 
164 * @author Adrian Cole
165 */
166public class RestAnnotationProcessor<T> {
167 
168   @Resource
169   protected Logger logger = Logger.NULL;
170 
171   private final Class<T> declaring;
172 
173   // TODO replace with Table object
174   static final Cache<Method, Cache<Integer, Set<Annotation>>> methodToIndexOfParamToBinderParamAnnotation = createMethodToIndexOfParamToAnnotation(BinderParam.class);
175   static final Cache<Method, Cache<Integer, Set<Annotation>>> methodToIndexOfParamToWrapWithAnnotation = createMethodToIndexOfParamToAnnotation(WrapWith.class);
176   static final Cache<Method, Cache<Integer, Set<Annotation>>> methodToIndexOfParamToHeaderParamAnnotations = createMethodToIndexOfParamToAnnotation(HeaderParam.class);
177   static final Cache<Method, Cache<Integer, Set<Annotation>>> methodToIndexOfParamToEndpointAnnotations = createMethodToIndexOfParamToAnnotation(Endpoint.class);
178   static final Cache<Method, Cache<Integer, Set<Annotation>>> methodToIndexOfParamToEndpointParamAnnotations = createMethodToIndexOfParamToAnnotation(EndpointParam.class);
179   static final Cache<Method, Cache<Integer, Set<Annotation>>> methodToIndexOfParamToMatrixParamAnnotations = createMethodToIndexOfParamToAnnotation(MatrixParam.class);
180   static final Cache<Method, Cache<Integer, Set<Annotation>>> methodToIndexOfParamToFormParamAnnotations = createMethodToIndexOfParamToAnnotation(FormParam.class);
181   static final Cache<Method, Cache<Integer, Set<Annotation>>> methodToIndexOfParamToQueryParamAnnotations = createMethodToIndexOfParamToAnnotation(QueryParam.class);
182   static final Cache<Method, Cache<Integer, Set<Annotation>>> methodToIndexOfParamToPathParamAnnotations = createMethodToIndexOfParamToAnnotation(PathParam.class);
183   static final Cache<Method, Cache<Integer, Set<Annotation>>> methodToIndexOfParamToPostParamAnnotations = createMethodToIndexOfParamToAnnotation(PayloadParam.class);
184   static final Cache<Method, Cache<Integer, Set<Annotation>>> methodToIndexOfParamToPartParamAnnotations = createMethodToIndexOfParamToAnnotation(PartParam.class);
185   static final Cache<Method, Cache<Integer, Set<Annotation>>> methodToIndexOfParamToParamParserAnnotations = createMethodToIndexOfParamToAnnotation(ParamParser.class);
186   static final Map<MethodKey, Method> delegationMap = newHashMap();
187 
188   static Cache<Method, Cache<Integer, Set<Annotation>>> createMethodToIndexOfParamToAnnotation(
189            final Class<? extends Annotation> annotation) {
190      return CacheBuilder.newBuilder().build(new CacheLoader<Method, Cache<Integer, Set<Annotation>>>() {
191         public Cache<Integer, Set<Annotation>> load(Method method) {
192            return CacheBuilder.newBuilder().build(CacheLoader.from(new GetAnnotationsForMethodParameterIndex(method, annotation)));
193         }
194      });
195   }
196 
197   static class GetAnnotationsForMethodParameterIndex implements Function<Integer, Set<Annotation>> {
198      private final Method method;
199      private final Class<?> clazz;
200 
201      protected GetAnnotationsForMethodParameterIndex(Method method, Class<?> clazz) {
202         this.method = method;
203         this.clazz = clazz;
204      }
205 
206      public Set<Annotation> apply(final Integer index) {
207         return ImmutableSet.<Annotation> copyOf(filter(ImmutableList.copyOf(method.getParameterAnnotations()[index]),
208               new Predicate<Annotation>() {
209                  public boolean apply(Annotation input) {
210                     return input.annotationType().equals(clazz);
211                  }
212               }));
213      }
214 
215   }
216 
217   private static final Class<? extends HttpRequestOptions[]> optionsVarArgsClass = new HttpRequestOptions[] {}
218         .getClass();
219 
220   private static final Function<? super Entry<String, String>, ? extends Part> ENTRY_TO_PART = new Function<Entry<String, String>, Part>() {
221 
222      @Override
223      public Part apply(Entry<String, String> from) {
224         return Part.create(from.getKey(), from.getValue());
225      }
226 
227   };
228 
229   static final Cache<Method, Set<Integer>> methodToIndexesOfOptions = CacheBuilder.newBuilder().build(
230         new CacheLoader<Method, Set<Integer>>() {
231            @Override
232            public Set<Integer> load(Method method) {
233               Builder<Integer> toReturn = ImmutableSet.<Integer> builder();
234               for (int index = 0; index < method.getParameterTypes().length; index++) {
235                  Class<?> type = method.getParameterTypes()[index];
236                  if (HttpRequestOptions.class.isAssignableFrom(type) || optionsVarArgsClass.isAssignableFrom(type))
237                     toReturn.add(index);
238               }
239               return toReturn.build();
240            }
241         });
242 
243   private final ParseSax.Factory parserFactory;
244   private final HttpUtils utils;
245   private final Provider<UriBuilder> uriBuilderProvider;
246   private final Cache<Class<?>, Boolean> seedAnnotationCache;
247   private final String apiVersion;
248   private char[] skips;
249 
250   @Inject
251   private InputParamValidator inputParamValidator;
252 
253   @VisibleForTesting
254   Function<HttpResponse, ?> createResponseParser(Method method, HttpRequest request) {
255      return createResponseParser(parserFactory, injector, method, request);
256   }
257 
258   @VisibleForTesting
259   public static Function<HttpResponse, ?> createResponseParser(ParseSax.Factory parserFactory, Injector injector,
260         Method method, HttpRequest request) {
261      Function<HttpResponse, ?> transformer;
262      Class<? extends HandlerWithResult<?>> handler = getSaxResponseParserClassOrNull(method);
263      if (handler != null) {
264         transformer = parserFactory.create(injector.getInstance(handler));
265      } else {
266         transformer = getTransformerForMethod(method, injector);
267      }
268      if (transformer instanceof InvocationContext<?>) {
269         ((InvocationContext<?>) transformer).setContext(request);
270      }
271      return transformer;
272   }
273 
274   @SuppressWarnings({ "rawtypes", "unchecked" })
275   public static Function<HttpResponse, ?> getTransformerForMethod(Method method, Injector injector) {
276      Function<HttpResponse, ?> transformer;
277      if (method.isAnnotationPresent(SelectJson.class)) {
278         Type returnVal = getReturnTypeForMethod(method);
279         if (method.isAnnotationPresent(OnlyElement.class))
280            returnVal = Types.newParameterizedType(Set.class, returnVal);
281         transformer = new ParseFirstJsonValueNamed(injector.getInstance(GsonWrapper.class),
282               TypeLiteral.get(returnVal), method.getAnnotation(SelectJson.class).value());
283         if (method.isAnnotationPresent(OnlyElement.class))
284            transformer = Functions.compose(new OnlyElementOrNull(), transformer);
285      } else {
286         transformer = injector.getInstance(getParserOrThrowException(method));
287      }
288      return transformer;
289   }
290 
291   @VisibleForTesting
292   Function<Exception, ?> createExceptionParserOrThrowResourceNotFoundOn404IfNoAnnotation(Method method) {
293      return createExceptionParserOrThrowResourceNotFoundOn404IfNoAnnotation(injector, method);
294   }
295 
296   @VisibleForTesting
297   public static Function<Exception, ?> createExceptionParserOrThrowResourceNotFoundOn404IfNoAnnotation(
298         Injector injector, Method method) {
299      ExceptionParser annotation = method.getAnnotation(ExceptionParser.class);
300      if (annotation != null) {
301         return injector.getInstance(annotation.value());
302      }
303      return injector.getInstance(MapHttp4xxCodesToExceptions.class);
304   }
305 
306   @SuppressWarnings("unchecked")
307   @Inject
308   public RestAnnotationProcessor(Injector injector, Cache<Class<?>, Boolean> seedAnnotationCache,
309         @Named(Constants.PROPERTY_API_VERSION) String apiVersion, ParseSax.Factory parserFactory, HttpUtils utils,
310         TypeLiteral<T> typeLiteral) throws ExecutionException {
311      this.declaring = (Class<T>) typeLiteral.getRawType();
312      this.injector = injector;
313      this.parserFactory = parserFactory;
314      this.utils = utils;
315      this.uriBuilderProvider = injector.getProvider(UriBuilder.class);
316      this.seedAnnotationCache = seedAnnotationCache;
317      seedAnnotationCache.get(declaring);
318      if (declaring.isAnnotationPresent(SkipEncoding.class)) {
319         skips = declaring.getAnnotation(SkipEncoding.class).value();
320      } else {
321         skips = new char[] {};
322      }
323      this.apiVersion = apiVersion;
324   }
325 
326   public Method getDelegateOrNull(Method in) {
327      return delegationMap.get(new MethodKey(in));
328   }
329 
330   public static class MethodKey {
331 
332      @Override
333      public int hashCode() {
334         final int prime = 31;
335         int result = 1;
336         result = prime * result + ((declaringPackage == null) ? 0 : declaringPackage.hashCode());
337         result = prime * result + ((name == null) ? 0 : name.hashCode());
338         result = prime * result + parametersTypeHashCode;
339         return result;
340      }
341 
342      @Override
343      public boolean equals(Object obj) {
344         if (this == obj)
345            return true;
346         if (obj == null)
347            return false;
348         if (getClass() != obj.getClass())
349            return false;
350         MethodKey other = (MethodKey) obj;
351         if (declaringPackage == null) {
352            if (other.declaringPackage != null)
353               return false;
354         } else if (!declaringPackage.equals(other.declaringPackage))
355            return false;
356         if (name == null) {
357            if (other.name != null)
358               return false;
359         } else if (!name.equals(other.name))
360            return false;
361         if (parametersTypeHashCode != other.parametersTypeHashCode)
362            return false;
363         return true;
364      }
365 
366      private final String name;
367      private final int parametersTypeHashCode;
368      private final Package declaringPackage;
369 
370      public MethodKey(Method method) {
371         this.name = method.getName();
372         this.declaringPackage = method.getDeclaringClass().getPackage();
373         int parametersTypeHashCode = 0;
374         for (Class<?> param : method.getParameterTypes())
375            parametersTypeHashCode += param.hashCode();
376         this.parametersTypeHashCode = parametersTypeHashCode;
377      }
378 
379   }
380 
381   final Injector injector;
382 
383   private ClassMethodArgs caller;
384   private URI callerEndpoint;
385 
386   public void setCaller(ClassMethodArgs caller) {
387      try {
388         seedAnnotationCache.get(caller.getMethod().getDeclaringClass());
389      } catch (ExecutionException e) {
390         Throwables.propagate(e);
391      }
392      this.caller = caller;
393      try {
394         callerEndpoint = getEndpointFor(caller.getMethod(), caller.getArgs(), injector);
395      } catch (IllegalStateException e) {
396      } catch (ExecutionException e) {
397         Throwables.propagate(e);
398      }
399   }
400 
401   public GeneratedHttpRequest<T> createRequest(Method method, Object... args) {
402      try {
403         inputParamValidator.validateMethodParametersOrThrow(method, args);
404         ClassMethodArgs cma = logger.isTraceEnabled() ? new ClassMethodArgs(method.getDeclaringClass(), method, args)
405               : null;
406 
407         URI endpoint = callerEndpoint;
408         try {
409            if (endpoint == null) {
410               endpoint = getEndpointFor(method, args, injector);
411               logger.trace("using endpoint %s for %s", endpoint, cma);
412            } else {
413               logger.trace("using endpoint %s from caller %s for %s", caller, endpoint, cma);
414            }
415         } catch (IllegalStateException e) {
416            logger.trace("looking up default endpoint for %s", cma);
417            endpoint = injector.getInstance(Key.get(URI.class, org.jclouds.location.Provider.class));
418            logger.trace("using default endpoint %s for %s", endpoint, cma);
419         }
420         GeneratedHttpRequest.Builder<T> requestBuilder;
421         HttpRequest r = RestAnnotationProcessor.findHttpRequestInArgs(args);
422         if (r != null) {
423            requestBuilder = GeneratedHttpRequest.Builder.<T> from(r);
424            endpoint = r.getEndpoint();
425         } else {
426            requestBuilder = GeneratedHttpRequest.<T> builder();
427            requestBuilder.method(getHttpMethodOrConstantOrThrowException(method));
428         }
429 
430         requestBuilder.declaring(declaring).javaMethod(method).args(args).skips(skips);
431         requestBuilder.filters(getFiltersIfAnnotated(method));
432 
433         UriBuilder builder = uriBuilderProvider.get().uri(endpoint);
434 
435         Multimap<String, String> tokenValues = LinkedHashMultimap.create();
436 
437         tokenValues.put(Constants.PROPERTY_API_VERSION, apiVersion);
438 
439         tokenValues.putAll(addPathAndGetTokens(declaring, method, args, builder));
440 
441         Multimap<String, String> formParams = addFormParams(tokenValues.entries(), method, args);
442         Multimap<String, String> queryParams = addQueryParams(tokenValues.entries(), method, args);
443         Multimap<String, String> matrixParams = addMatrixParams(tokenValues.entries(), method, args);
444         Multimap<String, String> headers = buildHeaders(tokenValues.entries(), method, args);
445         if (r != null)
446            headers.putAll(r.getHeaders());
447 
448         if (shouldAddHostHeader(method)) {
449            StringBuilder hostHeader = new StringBuilder(endpoint.getHost());
450            if (endpoint.getPort() != -1)
451               hostHeader.append(":").append(endpoint.getPort());
452            headers.put(HOST, hostHeader.toString());
453         }
454 
455         Payload payload = null;
456         HttpRequestOptions options = findOptionsIn(method, args);
457         if (options != null) {
458            injector.injectMembers(options);// TODO test case
459            for (Entry<String, String> header : options.buildRequestHeaders().entries()) {
460               headers.put(header.getKey(), Strings2.replaceTokens(header.getValue(), tokenValues.entries()));
461            }
462            for (Entry<String, String> matrix : options.buildMatrixParameters().entries()) {
463               matrixParams.put(matrix.getKey(), Strings2.replaceTokens(matrix.getValue(), tokenValues.entries()));
464            }
465            for (Entry<String, String> query : options.buildQueryParameters().entries()) {
466               queryParams.put(query.getKey(), Strings2.replaceTokens(query.getValue(), tokenValues.entries()));
467            }
468            for (Entry<String, String> form : options.buildFormParameters().entries()) {
469               formParams.put(form.getKey(), Strings2.replaceTokens(form.getValue(), tokenValues.entries()));
470            }
471 
472            String pathSuffix = options.buildPathSuffix();
473            if (pathSuffix != null) {
474               builder.path(pathSuffix);
475            }
476            String stringPayload = options.buildStringPayload();
477            if (stringPayload != null)
478               payload = Payloads.newStringPayload(stringPayload);
479         }
480 
481         if (matrixParams.size() > 0) {
482            for (String key : matrixParams.keySet())
483               builder.matrixParam(key, Lists.newArrayList(matrixParams.get(key)).toArray());
484         }
485 
486         if (queryParams.size() > 0) {
487            builder.replaceQuery(ModifyRequest.makeQueryLine(queryParams, null, skips));
488         }
489 
490         requestBuilder.headers(filterOutContentHeaders(headers));
491 
492         try {
493            requestBuilder.endpoint(builder.buildFromEncodedMap(Maps2.convertUnsafe(tokenValues)));
494         } catch (IllegalArgumentException e) {
495            throw new IllegalStateException(e);
496         } catch (UriBuilderException e) {
497            throw new IllegalStateException(e);
498         }
499 
500         if (payload == null)
501            payload = findPayloadInArgs(args);
502         List<? extends Part> parts = getParts(method, args, concat(tokenValues.entries(), formParams.entries()));
503         if (parts.size() > 0) {
504            if (formParams.size() > 0) {
505               parts = newLinkedList(concat(transform(formParams.entries(), ENTRY_TO_PART), parts));
506            }
507            payload = new MultipartForm(BOUNDARY, parts);
508         } else if (formParams.size() > 0) {
509            payload = Payloads.newUrlEncodedFormPayload(formParams, skips);
510         } else if (headers.containsKey(CONTENT_TYPE)) {
511            if (payload == null)
512               payload = Payloads.newByteArrayPayload(new byte[] {});
513            payload.getContentMetadata().setContentType(Iterables.get(headers.get(CONTENT_TYPE), 0));
514         }
515         if (payload != null) {
516            requestBuilder.payload(payload);
517         }
518         GeneratedHttpRequest<T> request = requestBuilder.build();
519 
520         org.jclouds.rest.MapBinder mapBinder = getMapPayloadBinderOrNull(method, args);
521         if (mapBinder != null) {
522            Map<String, String> mapParams = buildPostParams(method, args);
523            if (method.isAnnotationPresent(PayloadParams.class)) {
524               PayloadParams params = method.getAnnotation(PayloadParams.class);
525               addMapPayload(mapParams, params, headers.entries());
526            }
527            request = mapBinder.bindToRequest(request, mapParams);
528         } else {
529            request = decorateRequest(request);
530         }
531 
532         if (request.getPayload() != null)
533            request.getPayload().getContentMetadata().setPropertiesFromHttpHeaders(headers);
534         utils.checkRequestHasRequiredProperties(request);
535         return request;
536      } catch (ExecutionException e) {
537         Throwables.propagate(e);
538         return null;
539      }
540   }
541 
542   public static Multimap<String, String> filterOutContentHeaders(Multimap<String, String> headers) {
543      // TODO make a filter like {@link Maps.filterKeys} instead of this
544      ImmutableMultimap.Builder<String, String> headersBuilder = ImmutableMultimap.builder();
545      // http message usually comes in as a null key header, let's filter it
546      // out.
547      for (String header : Iterables.filter(headers.keySet(), Predicates.notNull())) {
548         if (!ContentMetadata.HTTP_HEADERS.contains(header)) {
549            headersBuilder.putAll(header, headers.get(header));
550         }
551      }
552      return headersBuilder.build();
553   }
554 
555   public static final String BOUNDARY = "--JCLOUDS--";
556 
557   private Multimap<String, String> addPathAndGetTokens(Class<?> clazz, Method method, Object[] args, UriBuilder builder) throws ExecutionException {
558      if (clazz.isAnnotationPresent(Path.class))
559         builder.path(clazz);
560      if (method.isAnnotationPresent(Path.class))
561         builder.path(method);
562      return encodeValues(getPathParamKeyValues(method, args), skips);
563   }
564 
565   public URI replaceQuery(URI in, String newQuery, @Nullable Comparator<Entry<String, String>> sorter) {
566      return replaceQuery(uriBuilderProvider, in, newQuery, sorter, skips);
567   }
568 
569   public static URI replaceQuery(Provider<UriBuilder> uriBuilderProvider, URI in, String newQuery,
570         @Nullable Comparator<Entry<String, String>> sorter, char... skips) {
571      UriBuilder builder = uriBuilderProvider.get().uri(in);
572      builder.replaceQuery(ModifyRequest.makeQueryLine(ModifyRequest.parseQueryToMap(newQuery), sorter, skips));
573      return builder.build();
574   }
575 
576   private Multimap<String, String> addMatrixParams(Collection<Entry<String, String>> tokenValues, Method method,
577         Object... args) throws ExecutionException {
578      Multimap<String, String> matrixMap = LinkedListMultimap.create();
579      if (declaring.isAnnotationPresent(MatrixParams.class)) {
580         MatrixParams matrix = declaring.getAnnotation(MatrixParams.class);
581         addMatrix(matrixMap, matrix, tokenValues);
582      }
583 
584      if (method.isAnnotationPresent(MatrixParams.class)) {
585         MatrixParams matrix = method.getAnnotation(MatrixParams.class);
586         addMatrix(matrixMap, matrix, tokenValues);
587      }
588 
589      for (Entry<String, String> matrix : getMatrixParamKeyValues(method, args).entries()) {
590         matrixMap.put(matrix.getKey(), Strings2.replaceTokens(matrix.getValue(), tokenValues));
591      }
592      return matrixMap;
593   }
594 
595   private Multimap<String, String> addFormParams(Collection<Entry<String, String>> tokenValues, Method method,
596         Object... args) throws ExecutionException {
597      Multimap<String, String> formMap = LinkedListMultimap.create();
598      if (declaring.isAnnotationPresent(FormParams.class)) {
599         FormParams form = declaring.getAnnotation(FormParams.class);
600         addForm(formMap, form, tokenValues);
601      }
602 
603      if (method.isAnnotationPresent(FormParams.class)) {
604         FormParams form = method.getAnnotation(FormParams.class);
605         addForm(formMap, form, tokenValues);
606      }
607 
608      for (Entry<String, String> form : getFormParamKeyValues(method, args).entries()) {
609         formMap.put(form.getKey(), Strings2.replaceTokens(form.getValue(), tokenValues));
610      }
611      return formMap;
612   }
613 
614   private Multimap<String, String> addQueryParams(Collection<Entry<String, String>> tokenValues, Method method,
615         Object... args) throws ExecutionException {
616      Multimap<String, String> queryMap = LinkedListMultimap.create();
617      if (declaring.isAnnotationPresent(QueryParams.class)) {
618         QueryParams query = declaring.getAnnotation(QueryParams.class);
619         addQuery(queryMap, query, tokenValues);
620      }
621 
622      if (method.isAnnotationPresent(QueryParams.class)) {
623         QueryParams query = method.getAnnotation(QueryParams.class);
624         addQuery(queryMap, query, tokenValues);
625      }
626 
627      for (Entry<String, String> query : getQueryParamKeyValues(method, args).entries()) {
628         queryMap.put(query.getKey(), Strings2.replaceTokens(query.getValue(), tokenValues));
629      }
630      return queryMap;
631   }
632 
633   private void addForm(Multimap<String, String> formParams, FormParams form,
634         Collection<Entry<String, String>> tokenValues) {
635      for (int i = 0; i < form.keys().length; i++) {
636         if (form.values()[i].equals(FormParams.NULL)) {
637            formParams.removeAll(form.keys()[i]);
638            formParams.put(form.keys()[i], null);
639         } else {
640            formParams.put(form.keys()[i], Strings2.replaceTokens(form.values()[i], tokenValues));
641         }
642      }
643   }
644 
645   private void addQuery(Multimap<String, String> queryParams, QueryParams query,
646         Collection<Entry<String, String>> tokenValues) {
647      for (int i = 0; i < query.keys().length; i++) {
648         if (query.values()[i].equals(QueryParams.NULL)) {
649            queryParams.removeAll(query.keys()[i]);
650            queryParams.put(query.keys()[i], null);
651         } else {
652            queryParams.put(query.keys()[i], Strings2.replaceTokens(query.values()[i], tokenValues));
653         }
654      }
655   }
656 
657   private void addMatrix(Multimap<String, String> matrixParams, MatrixParams matrix,
658         Collection<Entry<String, String>> tokenValues) {
659      for (int i = 0; i < matrix.keys().length; i++) {
660         if (matrix.values()[i].equals(MatrixParams.NULL)) {
661            matrixParams.removeAll(matrix.keys()[i]);
662            matrixParams.put(matrix.keys()[i], null);
663         } else {
664            matrixParams.put(matrix.keys()[i], Strings2.replaceTokens(matrix.values()[i], tokenValues));
665         }
666      }
667   }
668 
669   private void addMapPayload(Map<String, String> postParams, PayloadParams mapDefaults,
670         Collection<Entry<String, String>> tokenValues) {
671      for (int i = 0; i < mapDefaults.keys().length; i++) {
672         if (mapDefaults.values()[i].equals(PayloadParams.NULL)) {
673            postParams.put(mapDefaults.keys()[i], null);
674         } else {
675            postParams.put(mapDefaults.keys()[i], Strings2.replaceTokens(mapDefaults.values()[i], tokenValues));
676         }
677      }
678   }
679 
680   @VisibleForTesting
681   List<HttpRequestFilter> getFiltersIfAnnotated(Method method) {
682      List<HttpRequestFilter> filters = Lists.newArrayList();
683      if (declaring.isAnnotationPresent(RequestFilters.class)) {
684         for (Class<? extends HttpRequestFilter> clazz : declaring.getAnnotation(RequestFilters.class).value()) {
685            HttpRequestFilter instance = injector.getInstance(clazz);
686            filters.add(instance);
687            logger.trace("adding filter %s from annotation on %s", instance, declaring.getName());
688         }
689      }
690      if (method.isAnnotationPresent(RequestFilters.class)) {
691         if (method.isAnnotationPresent(OverrideRequestFilters.class))
692            filters.clear();
693         for (Class<? extends HttpRequestFilter> clazz : method.getAnnotation(RequestFilters.class).value()) {
694            HttpRequestFilter instance = injector.getInstance(clazz);
695            filters.add(instance);
696            logger.trace("adding filter %s from annotation on %s", instance, method.getName());
697         }
698      }
699      return filters;
700   }
701 
702   @VisibleForTesting
703   public static URI getEndpointInParametersOrNull(Method method, final Object[] args, Injector injector)
704         throws ExecutionException {
705      Map<Integer, Set<Annotation>> map = indexWithAtLeastOneAnnotation(method,
706            methodToIndexOfParamToEndpointParamAnnotations);
707      if (map.size() >= 1 && args.length > 0) {
708         EndpointParam firstAnnotation = (EndpointParam) get(get(map.values(), 0), 0);
709         Function<Object, URI> parser = injector.getInstance(firstAnnotation.parser());
710 
711         if (map.size() == 1) {
712            int index = map.keySet().iterator().next();
713            try {
714               URI returnVal = parser.apply(args[index]);
715               checkArgument(returnVal != null,
716                     String.format("endpoint for [%s] not configured for %s", args[index], method));
717               return returnVal;
718            } catch (NullPointerException e) {
719               throw new IllegalArgumentException(String.format("argument at index %d on method %s", index, method), e);
720            }
721         } else {
722            SortedSet<Integer> keys = newTreeSet(map.keySet());
723            Iterable<Object> argsToParse = transform(keys, new Function<Integer, Object>() {
724 
725               @Override
726               public Object apply(Integer from) {
727                  return args[from];
728               }
729 
730            });
731            try {
732               URI returnVal = parser.apply(argsToParse);
733               checkArgument(returnVal != null,
734                     String.format("endpoint for [%s] not configured for %s", argsToParse, method));
735               return returnVal;
736            } catch (NullPointerException e) {
737               throw new IllegalArgumentException(String.format("illegal argument in [%s] for method %s", argsToParse,
738                     method), e);
739            }
740         }
741      }
742      return null;
743   }
744 
745   public static URI getEndpointFor(Method method, Object[] args, Injector injector) throws ExecutionException {
746      URI endpoint = getEndpointInParametersOrNull(method, args, injector);
747      if (endpoint == null) {
748         Endpoint annotation;
749         if (method.isAnnotationPresent(Endpoint.class)) {
750            annotation = method.getAnnotation(Endpoint.class);
751         } else if (method.getDeclaringClass().isAnnotationPresent(Endpoint.class)) {
752            annotation = method.getDeclaringClass().getAnnotation(Endpoint.class);
753         } else {
754            throw new IllegalStateException("no annotations on class or method: " + method);
755         }
756         return injector.getInstance(Key.get(URI.class, annotation.value()));
757      }
758      return endpoint;
759   }
760 
761   public static final TypeLiteral<ListenableFuture<Boolean>> futureBooleanLiteral = new TypeLiteral<ListenableFuture<Boolean>>() {
762   };
763   public static final TypeLiteral<ListenableFuture<String>> futureStringLiteral = new TypeLiteral<ListenableFuture<String>>() {
764   };
765   public static final TypeLiteral<ListenableFuture<Void>> futureVoidLiteral = new TypeLiteral<ListenableFuture<Void>>() {
766   };
767   public static final TypeLiteral<ListenableFuture<URI>> futureURILiteral = new TypeLiteral<ListenableFuture<URI>>() {
768   };
769   public static final TypeLiteral<ListenableFuture<InputStream>> futureInputStreamLiteral = new TypeLiteral<ListenableFuture<InputStream>>() {
770   };
771   public static final TypeLiteral<ListenableFuture<HttpResponse>> futureHttpResponseLiteral = new TypeLiteral<ListenableFuture<HttpResponse>>() {
772   };
773 
774   @SuppressWarnings({ "unchecked", "rawtypes" })
775   public static Key<? extends Function<HttpResponse, ?>> getParserOrThrowException(Method method) {
776      ResponseParser annotation = method.getAnnotation(ResponseParser.class);
777      if (annotation == null) {
778         if (method.getReturnType().equals(void.class)
779               || TypeLiteral.get(method.getGenericReturnType()).equals(futureVoidLiteral)) {
780            return Key.get(ReleasePayloadAndReturn.class);
781         } else if (method.getReturnType().equals(boolean.class) || method.getReturnType().equals(Boolean.class)
782               || TypeLiteral.get(method.getGenericReturnType()).equals(futureBooleanLiteral)) {
783            return Key.get(ReturnTrueIf2xx.class);
784         } else if (method.getReturnType().equals(InputStream.class)
785               || TypeLiteral.get(method.getGenericReturnType()).equals(futureInputStreamLiteral)) {
786            return Key.get(ReturnInputStream.class);
787         } else if (method.getReturnType().equals(HttpResponse.class)
788               || TypeLiteral.get(method.getGenericReturnType()).equals(futureHttpResponseLiteral)) {
789            return Key.get((Class) IdentityFunction.class);
790         } else if (getAcceptHeadersOrNull(method).contains(MediaType.APPLICATION_JSON)) {
791            return getJsonParserKeyForMethod(method);
792         } else if (method.getReturnType().equals(String.class)
793               || TypeLiteral.get(method.getGenericReturnType()).equals(futureStringLiteral)) {
794            return Key.get(ReturnStringIf2xx.class);
795         } else if (method.getReturnType().equals(URI.class)
796               || TypeLiteral.get(method.getGenericReturnType()).equals(futureURILiteral)) {
797            return Key.get(ParseURIFromListOrLocationHeaderIf20x.class);
798         } else {
799            throw new IllegalStateException("You must specify a ResponseParser annotation on: " + method.toString());
800         }
801      }
802      return Key.get(annotation.value());
803   }
804 
805   public static Key<? extends Function<HttpResponse, ?>> getJsonParserKeyForMethod(Method method) {
806      Type returnVal = getReturnTypeForMethod(method);
807      return getJsonParserKeyForMethodAnType(method, returnVal);
808   }
809 
810   public static Type getReturnTypeForMethod(Method method) {
811      Type returnVal;
812      if (method.getReturnType().getTypeParameters().length == 0) {
813         returnVal = method.getReturnType();
814      } else if (method.getReturnType().equals(ListenableFuture.class)) {
815         ParameterizedType futureType = ((ParameterizedType) method.getGenericReturnType());
816         returnVal = futureType.getActualTypeArguments()[0];
817         if (returnVal instanceof WildcardType)
818            returnVal = WildcardType.class.cast(returnVal).getUpperBounds()[0];
819      } else {
820         returnVal = method.getGenericReturnType();
821      }
822      return returnVal;
823   }
824 
825   @SuppressWarnings({ "unchecked", "rawtypes" })
826   public static Key<? extends Function<HttpResponse, ?>> getJsonParserKeyForMethodAnType(Method method, Type returnVal) {
827      ParameterizedType parserType;
828      if (method.isAnnotationPresent(Unwrap.class)) {
829         int depth = method.getAnnotation(Unwrap.class).depth();
830         Class edgeCollection = method.getAnnotation(Unwrap.class).edgeCollection();
831         if (depth == 1 && edgeCollection == Map.class)
832            parserType = Types.newParameterizedType(UnwrapOnlyJsonValue.class, returnVal);
833         else if (depth == 2 && edgeCollection == Map.class)
834            parserType = Types.newParameterizedType(UnwrapOnlyNestedJsonValue.class, returnVal);
835         else if (depth == 2 && edgeCollection == Set.class)
836            parserType = Types.newParameterizedType(UnwrapOnlyJsonValueInSet.class, returnVal);
837         else if (depth == 3 && edgeCollection == Set.class)
838            parserType = Types.newParameterizedType(UnwrapOnlyNestedJsonValueInSet.class, returnVal);
839         else
840            throw new IllegalStateException(String.format("depth(%d) edgeCollection(%s) not yet supported for @Unwrap",
841                  depth, edgeCollection));
842      } else {
843         parserType = Types.newParameterizedType(ParseJson.class, returnVal);
844      }
845      return (Key<? extends Function<HttpResponse, ?>>) Key.get(parserType);
846   }
847 
848   public static Class<? extends HandlerWithResult<?>> getSaxResponseParserClassOrNull(Method method) {
849      XMLResponseParser annotation = method.getAnnotation(XMLResponseParser.class);
850      if (annotation != null) {
851         return annotation.value();
852      }
853      return null;
854   }
855 
856   public org.jclouds.rest.MapBinder getMapPayloadBinderOrNull(Method method, Object... args) {
857      if (args != null) {
858         for (Object arg : args) {
859            if (arg instanceof Object[]) {
860               Object[] postBinders = (Object[]) arg;
861               if (postBinders.length == 0) {
862               } else if (postBinders.length == 1) {
863                  if (postBinders[0] instanceof org.jclouds.rest.MapBinder) {
864                     org.jclouds.rest.MapBinder binder = (org.jclouds.rest.MapBinder) postBinders[0];
865                     injector.injectMembers(binder);
866                     return binder;
867                  }
868               } else {
869                  if (postBinders[0] instanceof org.jclouds.rest.MapBinder) {
870                     throw new IllegalArgumentException("we currently do not support multiple varargs postBinders in: "
871                           + method.getName());
872                  }
873               }
874            } else if (arg instanceof org.jclouds.rest.MapBinder) {
875               org.jclouds.rest.MapBinder binder = (org.jclouds.rest.MapBinder) arg;
876               injector.injectMembers(binder);
877               return binder;
878            }
879         }
880      }
881      if (method.isAnnotationPresent(MapBinder.class)) {
882         return injector.getInstance(method.getAnnotation(MapBinder.class).value());
883      } else if (method.isAnnotationPresent(org.jclouds.rest.annotations.Payload.class)) {
884         return injector.getInstance(BindMapToStringPayload.class);
885      }
886      return null;
887   }
888 
889   public static Set<String> getHttpMethods(Method method) {
890      Builder<String> methodsBuilder = ImmutableSet.<String> builder();
891      for (Annotation annotation : method.getAnnotations()) {
892         HttpMethod http = annotation.annotationType().getAnnotation(HttpMethod.class);
893         if (http != null)
894            methodsBuilder.add(http.value());
895      }
896      Set<String> methods = methodsBuilder.build();
897      return (methods.size() == 0) ? null : methods;
898   }
899 
900   public String getHttpMethodOrConstantOrThrowException(Method method) {
901      Set<String> requests = getHttpMethods(method);
902      if (requests == null || requests.size() != 1) {
903         throw new IllegalStateException(
904               "You must use at least one, but no more than one http method or pathparam annotation on: "
905                     + method.toString());
906      }
907      return requests.iterator().next();
908   }
909 
910   public boolean shouldAddHostHeader(Method method) {
911      if (declaring.isAnnotationPresent(VirtualHost.class) || method.isAnnotationPresent(VirtualHost.class)) {
912         return true;
913      }
914      return false;
915   }
916 
917   private static final Predicate<Set<?>> notEmpty = new Predicate<Set<?>>() {
918      public boolean apply(Set<?> input) {
919         return input.size() >= 1;
920      }
921   };
922 
923   public GeneratedHttpRequest<T> decorateRequest(GeneratedHttpRequest<T> request) throws NegativeArraySizeException,
924            ExecutionException {
925      OUTER: for (Entry<Integer, Set<Annotation>> entry : concat(//
926               filterValues(methodToIndexOfParamToBinderParamAnnotation.get(request.getJavaMethod()).asMap(), notEmpty)
927                        .entrySet(), //
928               filterValues(methodToIndexOfParamToWrapWithAnnotation.get(request.getJavaMethod()).asMap(), notEmpty)
929                        .entrySet())) {
930         boolean shouldBreak = false;
931         Annotation annotation = Iterables.get(entry.getValue(), 0);
932         Binder binder;
933         if (annotation instanceof BinderParam)
934            binder = injector.getInstance(BinderParam.class.cast(annotation).value());
935         else
936            binder = injector.getInstance(BindToJsonPayloadWrappedWith.Factory.class).create(
937                     WrapWith.class.cast(annotation).value());
938         if (request.getArgs().size() >= entry.getKey() + 1 && request.getArgs().get(entry.getKey()) != null) {
939            Object input;
940            Class<?> parameterType = request.getJavaMethod().getParameterTypes()[entry.getKey()];
941            Class<? extends Object> argType = request.getArgs().get(entry.getKey()).getClass();
942            if (!argType.isArray() && request.getJavaMethod().isVarArgs() && parameterType.isArray()) {
943               int arrayLength = request.getArgs().size() - request.getJavaMethod().getParameterTypes().length + 1;
944               if (arrayLength == 0)
945                  break OUTER;
946               input = (Object[]) Array.newInstance(request.getArgs().get(entry.getKey()).getClass(), arrayLength);
947               System.arraycopy(request.getArgs().toArray(), entry.getKey(), input, 0, arrayLength);
948               shouldBreak = true;
949            } else if (argType.isArray() && request.getJavaMethod().isVarArgs() && parameterType.isArray()) {
950               input = request.getArgs().get(entry.getKey());
951            } else {
952               input = request.getArgs().get(entry.getKey());
953               if (input.getClass().isArray()) {
954                  Object[] payloadArray = (Object[]) input;
955                  input = payloadArray.length > 0 ? payloadArray[0] : null;
956               }
957            }
958            if (input != null) {
959               request = binder.bindToRequest(request, input);
960            }
961            if (shouldBreak)
962               break OUTER;
963         } else {
964            // either arg is null, or request.getArgs().size() < entry.getKey() + 1
965            // in either case, we require that null be allowed
966            // (first, however, let's make sure we have enough args on the actual method)
967            if (entry.getKey() >= request.getJavaMethod().getParameterAnnotations().length) {
968               // not known whether this happens
969               throw new IllegalArgumentException("Argument index " + (entry.getKey() + 1)
970                        + " is out of bounds for method " + request.getJavaMethod());
971            }
972 
973            if (request.getJavaMethod().isVarArgs()
974                     && entry.getKey() + 1 == request.getJavaMethod().getParameterTypes().length)
975               // allow null/missing for var args
976               continue OUTER;
977 
978            Annotation[] annotations = request.getJavaMethod().getParameterAnnotations()[entry.getKey()];
979            for (Annotation a : annotations) {
980               if (Nullable.class.isAssignableFrom(a.annotationType()))
981                  continue OUTER;
982            }
983            Preconditions.checkNotNull(null, request.getJavaMethod().getName() + " parameter " + (entry.getKey() + 1));
984         }
985      }
986 
987      return request;
988   }
989 
990   public static Map<Integer, Set<Annotation>> indexWithOnlyOneAnnotation(Method method, String description,
991         Cache<Method, Cache<Integer, Set<Annotation>>> toRefine) throws ExecutionException {
992      Map<Integer, Set<Annotation>> indexToPayloadAnnotation = indexWithAtLeastOneAnnotation(method, toRefine);
993      if (indexToPayloadAnnotation.size() > 1) {
994         throw new IllegalStateException(String.format(
995               "You must not specify more than one %s annotation on: %s; found %s", description, method.toString(),
996               indexToPayloadAnnotation));
997      }
998      return indexToPayloadAnnotation;
999   }
1000 
1001   private static Map<Integer, Set<Annotation>> indexWithAtLeastOneAnnotation(Method method,
1002         Cache<Method, Cache<Integer, Set<Annotation>>> toRefine) throws ExecutionException {
1003      Map<Integer, Set<Annotation>> indexToPayloadAnnotation = filterValues(toRefine.get(method).asMap(),
1004            new Predicate<Set<Annotation>>() {
1005               public boolean apply(Set<Annotation> input) {
1006                  return input.size() == 1;
1007               }
1008            });
1009      return indexToPayloadAnnotation;
1010   }
1011 
1012   private HttpRequestOptions findOptionsIn(Method method, Object... args) throws ExecutionException {
1013      for (int index : methodToIndexesOfOptions.get(method)) {
1014         if (args.length >= index + 1) {// accomodate varargs
1015            if (args[index] instanceof Object[]) {
1016               Object[] options = (Object[]) args[index];
1017               if (options.length == 0) {
1018               } else if (options.length == 1) {
1019                  if (options[0] instanceof HttpRequestOptions) {
1020                     HttpRequestOptions binder = (HttpRequestOptions) options[0];
1021                     injector.injectMembers(binder);
1022                     return binder;
1023                  }
1024               } else {
1025                  if (options[0] instanceof HttpRequestOptions) {
1026                     throw new IllegalArgumentException("we currently do not support multiple varargs options in: "
1027                           + method.getName());
1028                  }
1029               }
1030            } else {
1031               return (HttpRequestOptions) args[index];
1032            }
1033         }
1034      }
1035      return null;
1036   }
1037 
1038   public Multimap<String, String> buildHeaders(Collection<Entry<String, String>> tokenValues, Method method,
1039         final Object... args) throws ExecutionException {
1040      Multimap<String, String> headers = LinkedHashMultimap.create();
1041      addHeaderIfAnnotationPresentOnMethod(headers, method, tokenValues);
1042      Cache<Integer, Set<Annotation>> indexToHeaderParam = methodToIndexOfParamToHeaderParamAnnotations.get(method);
1043      for (Entry<Integer, Set<Annotation>> entry : indexToHeaderParam.asMap().entrySet()) {
1044         for (Annotation key : entry.getValue()) {
1045            String value = args[entry.getKey()].toString();
1046            value = Strings2.replaceTokens(value, tokenValues);
1047            headers.put(((HeaderParam) key).value(), value);
1048         }
1049      }
1050      addProducesIfPresentOnTypeOrMethod(headers, method);
1051      addConsumesIfPresentOnTypeOrMethod(headers, method);
1052      return headers;
1053   }
1054 
1055   void addConsumesIfPresentOnTypeOrMethod(Multimap<String, String> headers, Method method) {
1056      List<String> accept = getAcceptHeadersOrNull(method);
1057      if (accept.size() > 0)
1058         headers.replaceValues(ACCEPT, accept);
1059   }
1060 
1061   private static List<String> getAcceptHeadersOrNull(Method method) {
1062      List<String> accept = Collections.emptyList();
1063      if (method.getDeclaringClass().isAnnotationPresent(Consumes.class)) {
1064         Consumes header = method.getDeclaringClass().getAnnotation(Consumes.class);
1065         accept = asList(header.value());
1066      }
1067      if (method.isAnnotationPresent(Consumes.class)) {
1068         Consumes header = method.getAnnotation(Consumes.class);
1069         accept = asList(header.value());
1070      }
1071      return accept;
1072   }
1073 
1074   void addProducesIfPresentOnTypeOrMethod(Multimap<String, String> headers, Method method) {
1075      if (declaring.isAnnotationPresent(Produces.class)) {
1076         Produces header = declaring.getAnnotation(Produces.class);
1077         headers.replaceValues(CONTENT_TYPE, asList(header.value()));
1078      }
1079      if (method.isAnnotationPresent(Produces.class)) {
1080         Produces header = method.getAnnotation(Produces.class);
1081         headers.replaceValues(CONTENT_TYPE, asList(header.value()));
1082      }
1083   }
1084 
1085   public void addHeaderIfAnnotationPresentOnMethod(Multimap<String, String> headers, Method method,
1086         Collection<Entry<String, String>> tokenValues) {
1087      if (declaring.isAnnotationPresent(Headers.class)) {
1088         Headers header = declaring.getAnnotation(Headers.class);
1089         addHeader(headers, header, tokenValues);
1090      }
1091      if (method.isAnnotationPresent(Headers.class)) {
1092         Headers header = method.getAnnotation(Headers.class);
1093         addHeader(headers, header, tokenValues);
1094      }
1095   }
1096 
1097   private void addHeader(Multimap<String, String> headers, Headers header,
1098         Collection<Entry<String, String>> tokenValues) {
1099      for (int i = 0; i < header.keys().length; i++) {
1100         String value = header.values()[i];
1101         value = Strings2.replaceTokens(value, tokenValues);
1102         headers.put(header.keys()[i], value);
1103      }
1104 
1105   }
1106 
1107   List<? extends Part> getParts(Method method, Object[] args, Iterable<Entry<String, String>> iterable) throws ExecutionException {
1108      List<Part> parts = newLinkedList();
1109      Cache<Integer, Set<Annotation>> indexToPartParam = methodToIndexOfParamToPartParamAnnotations.get(method);
1110      for (Entry<Integer, Set<Annotation>> entry : indexToPartParam.asMap().entrySet()) {
1111         for (Annotation key : entry.getValue()) {
1112            PartParam param = (PartParam) key;
1113            PartOptions options = new PartOptions();
1114            if (!PartParam.NO_CONTENT_TYPE.equals(param.contentType()))
1115               options.contentType(param.contentType());
1116            if (!PartParam.NO_FILENAME.equals(param.filename()))
1117               options.filename(Strings2.replaceTokens(param.filename(), iterable));
1118            Object arg = args[entry.getKey()];
1119            Preconditions.checkNotNull(arg, param.name());
1120            Part part = Part.create(param.name(), newPayload(arg), options);
1121            parts.add(part);
1122         }
1123      }
1124      return parts;
1125   }
1126 
1127   public static HttpRequest findHttpRequestInArgs(Object[] args) {
1128      if (args == null)
1129         return null;
1130      for (int i = 0; i < args.length; i++)
1131         if (args[i] instanceof HttpRequest)
1132            return HttpRequest.class.cast(args[i]);
1133      return null;
1134   }
1135 
1136   public static Payload findPayloadInArgs(Object[] args) {
1137      if (args == null)
1138         return null;
1139      for (int i = 0; i < args.length; i++)
1140         if (args[i] instanceof Payload)
1141            return Payload.class.cast(args[i]);
1142         else if (args[i] instanceof PayloadEnclosing)
1143            return PayloadEnclosing.class.cast(args[i]).getPayload();
1144      return null;
1145   }
1146 
1147   private Multimap<String, String> getPathParamKeyValues(Method method, Object... args) throws ExecutionException {
1148      Multimap<String, String> pathParamValues = LinkedHashMultimap.create();
1149      Cache<Integer, Set<Annotation>> indexToPathParam = methodToIndexOfParamToPathParamAnnotations.get(method);
1150 
1151      Cache<Integer, Set<Annotation>> indexToParamExtractor = methodToIndexOfParamToParamParserAnnotations.get(method);
1152      for (Entry<Integer, Set<Annotation>> entry : indexToPathParam.asMap().entrySet()) {
1153         for (Annotation key : entry.getValue()) {
1154            Set<Annotation> extractors = indexToParamExtractor.get(entry.getKey());
1155            String paramKey = ((PathParam) key).value();
1156            String paramValue;
1157            if (extractors != null && extractors.size() > 0) {
1158               ParamParser extractor = (ParamParser) extractors.iterator().next();
1159               paramValue = injector.getInstance(extractor.value()).apply(args[entry.getKey()]);
1160            } else {
1161               paramValue = args[entry.getKey()].toString();
1162            }
1163            pathParamValues.put(paramKey, paramValue);
1164         }
1165      }
1166 
1167      if (method.isAnnotationPresent(PathParam.class) && method.isAnnotationPresent(ParamParser.class)) {
1168         String paramKey = method.getAnnotation(PathParam.class).value();
1169         String paramValue = injector.getInstance(method.getAnnotation(ParamParser.class).value()).apply(args);
1170         pathParamValues.put(paramKey, paramValue);
1171 
1172      }
1173      return pathParamValues;
1174   }
1175 
1176   private Multimap<String, String> encodeValues(Multimap<String, String> unencoded, char... skips) {
1177      Multimap<String, String> encoded = LinkedHashMultimap.create();
1178      for (Entry<String, String> entry : unencoded.entries()) {
1179         encoded.put(entry.getKey(), Strings2.urlEncode(entry.getValue(), skips));
1180      }
1181      return encoded;
1182   }
1183 
1184   private Multimap<String, String> getMatrixParamKeyValues(Method method, Object... args) throws ExecutionException {
1185      Multimap<String, String> matrixParamValues = LinkedHashMultimap.create();
1186      Cache<Integer, Set<Annotation>> indexToMatrixParam = methodToIndexOfParamToMatrixParamAnnotations.get(method);
1187 
1188      Cache<Integer, Set<Annotation>> indexToParamExtractor = methodToIndexOfParamToParamParserAnnotations.get(method);
1189      for (Entry<Integer, Set<Annotation>> entry : indexToMatrixParam.asMap().entrySet()) {
1190         for (Annotation key : entry.getValue()) {
1191            Set<Annotation> extractors = indexToParamExtractor.get(entry.getKey());
1192            String paramKey = ((MatrixParam) key).value();
1193            String paramValue;
1194            if (extractors != null && extractors.size() > 0) {
1195               ParamParser extractor = (ParamParser) extractors.iterator().next();
1196               paramValue = injector.getInstance(extractor.value()).apply(args[entry.getKey()]);
1197            } else {
1198               paramValue = args[entry.getKey()].toString();
1199            }
1200            matrixParamValues.put(paramKey, paramValue);
1201         }
1202      }
1203 
1204      if (method.isAnnotationPresent(MatrixParam.class) && method.isAnnotationPresent(ParamParser.class)) {
1205         String paramKey = method.getAnnotation(MatrixParam.class).value();
1206         String paramValue = injector.getInstance(method.getAnnotation(ParamParser.class).value()).apply(args);
1207         matrixParamValues.put(paramKey, paramValue);
1208 
1209      }
1210      return matrixParamValues;
1211   }
1212 
1213   private Multimap<String, String> getFormParamKeyValues(Method method, Object... args) throws ExecutionException {
1214      Multimap<String, String> formParamValues = LinkedHashMultimap.create();
1215      Cache<Integer, Set<Annotation>> indexToFormParam = methodToIndexOfParamToFormParamAnnotations.get(method);
1216 
1217      Cache<Integer, Set<Annotation>> indexToParamExtractor = methodToIndexOfParamToParamParserAnnotations.get(method);
1218      for (Entry<Integer, Set<Annotation>> entry : indexToFormParam.asMap().entrySet()) {
1219         for (Annotation key : entry.getValue()) {
1220            Set<Annotation> extractors = indexToParamExtractor.get(entry.getKey());
1221            String paramKey = ((FormParam) key).value();
1222            String paramValue;
1223            if (extractors != null && extractors.size() > 0) {
1224               ParamParser extractor = (ParamParser) extractors.iterator().next();
1225               paramValue = injector.getInstance(extractor.value()).apply(args[entry.getKey()]);
1226            } else {
1227               Object pvo = args[entry.getKey()];
1228               Preconditions.checkNotNull(pvo, paramKey);
1229               paramValue = pvo.toString();
1230            }
1231            formParamValues.put(paramKey, paramValue);
1232         }
1233      }
1234 
1235      if (method.isAnnotationPresent(FormParam.class) && method.isAnnotationPresent(ParamParser.class)) {
1236         String paramKey = method.getAnnotation(FormParam.class).value();
1237         String paramValue = injector.getInstance(method.getAnnotation(ParamParser.class).value()).apply(args);
1238         formParamValues.put(paramKey, paramValue);
1239 
1240      }
1241      return formParamValues;
1242   }
1243 
1244   private Multimap<String, String> getQueryParamKeyValues(Method method, Object... args) throws ExecutionException {
1245      Multimap<String, String> queryParamValues = LinkedHashMultimap.create();
1246      Cache<Integer, Set<Annotation>> indexToQueryParam = methodToIndexOfParamToQueryParamAnnotations.get(method);
1247 
1248      Cache<Integer, Set<Annotation>> indexToParamExtractor = methodToIndexOfParamToParamParserAnnotations.get(method);
1249      for (Entry<Integer, Set<Annotation>> entry : indexToQueryParam.asMap().entrySet()) {
1250         for (Annotation key : entry.getValue()) {
1251            Set<Annotation> extractors = indexToParamExtractor.get(entry.getKey());
1252            String paramKey = ((QueryParam) key).value();
1253            String paramValue;
1254            if (extractors != null && extractors.size() > 0) {
1255               ParamParser extractor = (ParamParser) extractors.iterator().next();
1256               paramValue = injector.getInstance(extractor.value()).apply(args[entry.getKey()]);
1257            } else {
1258               paramValue = args[entry.getKey()].toString();
1259            }
1260            queryParamValues.put(paramKey, paramValue);
1261         }
1262      }
1263 
1264      if (method.isAnnotationPresent(QueryParam.class) && method.isAnnotationPresent(ParamParser.class)) {
1265         String paramKey = method.getAnnotation(QueryParam.class).value();
1266         String paramValue = injector.getInstance(method.getAnnotation(ParamParser.class).value()).apply(args);
1267         queryParamValues.put(paramKey, paramValue);
1268 
1269      }
1270      return queryParamValues;
1271   }
1272 
1273   private Map<String, String> buildPostParams(Method method, Object... args) throws ExecutionException {
1274      Map<String, String> postParams = newHashMap();
1275      Cache<Integer, Set<Annotation>> indexToPathParam = methodToIndexOfParamToPostParamAnnotations.get(method);
1276      Cache<Integer, Set<Annotation>> indexToParamExtractor = methodToIndexOfParamToParamParserAnnotations.get(method);
1277      for (Entry<Integer, Set<Annotation>> entry : indexToPathParam.asMap().entrySet()) {
1278         for (Annotation key : entry.getValue()) {
1279            Set<Annotation> extractors = indexToParamExtractor.get(entry.getKey());
1280            String paramKey = ((PayloadParam) key).value();
1281            String paramValue;
1282            if (extractors != null && extractors.size() > 0) {
1283               ParamParser extractor = (ParamParser) extractors.iterator().next();
1284               paramValue = injector.getInstance(extractor.value()).apply(args[entry.getKey()]);
1285            } else {
1286               paramValue = args[entry.getKey()] != null ? args[entry.getKey()].toString() : null;
1287            }
1288            postParams.put(paramKey, paramValue);
1289 
1290         }
1291      }
1292      return postParams;
1293   }
1294 
1295}

[all classes][org.jclouds.rest.internal]
EMMA 2.0.5312 (C) Vladimir Roubtsov