Skip to content

Commit

Permalink
Merge pull request square#620 from square/jw/annotations
Browse files Browse the repository at this point in the history
Use the annotations directly for parameter handling.
  • Loading branch information
JakeWharton committed Oct 6, 2014
2 parents 5176dcb + 46598ad commit 3436cfb
Show file tree
Hide file tree
Showing 12 changed files with 453 additions and 232 deletions.
297 changes: 165 additions & 132 deletions retrofit/src/main/java/retrofit/RequestBuilder.java

Large diffs are not rendered by default.

4 changes: 1 addition & 3 deletions retrofit/src/main/java/retrofit/RestAdapter.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,7 @@
* Method parameters can be used to replace parts of the URL by annotating them with
* {@link retrofit.http.Path @Path}. Replacement sections are denoted by an identifier surrounded
* by curly braces (e.g., "{foo}"). To add items to the query string of a URL use
* {@link retrofit.http.Query @Query}. If the path or query element has already been URI encoded
* use {@link retrofit.http.EncodedPath @EncodedPath} or
* {@link retrofit.http.EncodedQuery @EncodedQuery} to prevent repeated encoding.
* {@link retrofit.http.Query @Query}.
* <p>
* HTTP requests happen in one of two ways:
* <ul>
Expand Down
72 changes: 11 additions & 61 deletions retrofit/src/main/java/retrofit/RestMethodInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,21 +61,6 @@ private enum ResponseType {
private static final Pattern PARAM_NAME_REGEX = Pattern.compile(PARAM);
private static final Pattern PARAM_URL_REGEX = Pattern.compile("\\{(" + PARAM + ")\\}");

enum ParamUsage {
PATH,
ENCODED_PATH,
QUERY,
ENCODED_QUERY,
QUERY_MAP,
ENCODED_QUERY_MAP,
FIELD,
FIELD_MAP,
PART,
PART_MAP,
BODY,
HEADER
}

enum RequestType {
/** No content-specific logic required. */
SIMPLE,
Expand Down Expand Up @@ -105,9 +90,7 @@ enum RequestType {
boolean isStreaming;

// Parameter-level details
String[] requestParamNames;
ParamUsage[] requestParamUsage;
Annotation[] requestParamAnnotation;
Annotation[] requestParamAnnotations;

RestMethodInfo(Method method) {
this.method = method;
Expand Down Expand Up @@ -321,8 +304,7 @@ private static Type getParameterUpperBound(ParameterizedType type) {
}

/**
* Loads {@link #requestParamNames}, {@link #requestParamUsage}, and
* {@link #requestParamAnnotation}. Must be called after {@link #parseMethodAnnotations()}.
* Loads {@link #requestParamAnnotations}. Must be called after {@link #parseMethodAnnotations()}.
*/
private void parseParameters() {
Class<?>[] methodParameterTypes = method.getParameterTypes();
Expand All @@ -333,9 +315,7 @@ private void parseParameters() {
count -= 1; // Callback is last argument when not a synchronous method.
}

String[] requestParamNames = new String[count];
ParamUsage[] requestParamUsage = new ParamUsage[count];
Annotation[] requestParamAnnotation = new Annotation[count];
Annotation[] requestParamAnnotations = new Annotation[count];

boolean gotField = false;
boolean gotPart = false;
Expand All @@ -352,52 +332,31 @@ private void parseParameters() {
if (methodAnnotationType == Path.class) {
String name = ((Path) methodParameterAnnotation).value();
validatePathName(i, name);

requestParamNames[i] = name;
requestParamUsage[i] = ParamUsage.PATH;
} else if (methodAnnotationType == EncodedPath.class) {
String name = ((EncodedPath) methodParameterAnnotation).value();
validatePathName(i, name);

requestParamNames[i] = name;
requestParamUsage[i] = ParamUsage.ENCODED_PATH;
} else if (methodAnnotationType == Query.class) {
String name = ((Query) methodParameterAnnotation).value();

requestParamNames[i] = name;
requestParamUsage[i] = ParamUsage.QUERY;
// Nothing to do.
} else if (methodAnnotationType == EncodedQuery.class) {
String name = ((EncodedQuery) methodParameterAnnotation).value();

requestParamNames[i] = name;
requestParamUsage[i] = ParamUsage.ENCODED_QUERY;
// Nothing to do.
} else if (methodAnnotationType == QueryMap.class) {
if (!Map.class.isAssignableFrom(methodParameterType)) {
throw parameterError(i, "@QueryMap parameter type must be Map.");
}

requestParamUsage[i] = ParamUsage.QUERY_MAP;
} else if (methodAnnotationType == EncodedQueryMap.class) {
if (!Map.class.isAssignableFrom(methodParameterType)) {
throw parameterError(i, "@EncodedQueryMap parameter type must be Map.");
}

requestParamUsage[i] = ParamUsage.ENCODED_QUERY_MAP;
} else if (methodAnnotationType == Header.class) {
String name = ((Header) methodParameterAnnotation).value();

requestParamNames[i] = name;
requestParamUsage[i] = ParamUsage.HEADER;
// Nothing to do.
} else if (methodAnnotationType == Field.class) {
if (requestType != RequestType.FORM_URL_ENCODED) {
throw parameterError(i, "@Field parameters can only be used with form encoding.");
}

String name = ((Field) methodParameterAnnotation).value();

gotField = true;
requestParamNames[i] = name;
requestParamUsage[i] = ParamUsage.FIELD;
} else if (methodAnnotationType == FieldMap.class) {
if (requestType != RequestType.FORM_URL_ENCODED) {
throw parameterError(i, "@FieldMap parameters can only be used with form encoding.");
Expand All @@ -407,17 +366,12 @@ private void parseParameters() {
}

gotField = true;
requestParamUsage[i] = ParamUsage.FIELD_MAP;
} else if (methodAnnotationType == Part.class) {
if (requestType != RequestType.MULTIPART) {
throw parameterError(i, "@Part parameters can only be used with multipart encoding.");
}

String name = ((Part) methodParameterAnnotation).value();

gotPart = true;
requestParamNames[i] = name;
requestParamUsage[i] = ParamUsage.PART;
} else if (methodAnnotationType == PartMap.class) {
if (requestType != RequestType.MULTIPART) {
throw parameterError(i,
Expand All @@ -428,7 +382,6 @@ private void parseParameters() {
}

gotPart = true;
requestParamUsage[i] = ParamUsage.PART_MAP;
} else if (methodAnnotationType == Body.class) {
if (requestType != RequestType.SIMPLE) {
throw parameterError(i,
Expand All @@ -439,23 +392,22 @@ private void parseParameters() {
}

gotBody = true;
requestParamUsage[i] = ParamUsage.BODY;
} else {
// This is a non-Retrofit annotation. Skip to the next one.
continue;
}

if (requestParamAnnotation[i] != null) {
if (requestParamAnnotations[i] != null) {
throw parameterError(i,
"Multiple Retrofit annotations found, only one allowed: @%s, @%s.",
requestParamAnnotation[i].annotationType().getSimpleName(),
requestParamAnnotations[i].annotationType().getSimpleName(),
methodAnnotationType.getSimpleName());
}
requestParamAnnotation[i] = methodParameterAnnotation;
requestParamAnnotations[i] = methodParameterAnnotation;
}
}

if (requestParamUsage[i] == null) {
if (requestParamAnnotations[i] == null) {
throw parameterError(i, "No Retrofit annotation found.");
}
}
Expand All @@ -470,9 +422,7 @@ private void parseParameters() {
throw methodError("Multipart method must contain at least one @Part.");
}

this.requestParamNames = requestParamNames;
this.requestParamUsage = requestParamUsage;
this.requestParamAnnotation = requestParamAnnotation;
this.requestParamAnnotations = requestParamAnnotations;
}

private void validatePathName(int index, String name) {
Expand Down
4 changes: 4 additions & 0 deletions retrofit/src/main/java/retrofit/http/EncodedPath.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,12 @@
* </pre>
* <p>
* Path parameters may not be {@code null}.
*
* @see Path
* @deprecated Use {@link Path} with {@link Path#encode() encode = false}.
*/
@Documented
@Deprecated
@Retention(RUNTIME)
@Target(PARAMETER)
public @interface EncodedPath {
Expand Down
3 changes: 2 additions & 1 deletion retrofit/src/main/java/retrofit/http/EncodedQuery.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@
*
* @see Query
* @see QueryMap
* @see EncodedQueryMap
* @deprecated Use {@link Query} with {@link Query#encodeValue() encodeValue = false}.
*/
@Documented
@Deprecated
@Target(PARAMETER)
@Retention(RUNTIME)
public @interface EncodedQuery {
Expand Down
3 changes: 2 additions & 1 deletion retrofit/src/main/java/retrofit/http/EncodedQueryMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@
*
* @see Query
* @see QueryMap
* @see EncodedQuery
* @deprecated Use {@link QueryMap} with {@link QueryMap#encodeValues() encodeValues = false}.
*/
@Documented
@Deprecated
@Target(PARAMETER)
@Retention(RUNTIME)
public @interface EncodedQueryMap {
Expand Down
20 changes: 18 additions & 2 deletions retrofit/src/main/java/retrofit/http/Path.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,25 @@

/**
* Named replacement in the URL path. Values are converted to string using
* {@link String#valueOf(Object)}. Replaced values will be URL encoded.
* {@link String#valueOf(Object)} and URL encoded.
* <p>
* Simple example:
* <pre>
* &#64;GET("/image/{id}")
* void example(@Path("id") int id, ..);
* void example(@Path("id") int id);
* </pre>
* Calling with {@code foo.example(1)} yields {@code /image/1}.
* <p>
* Values are URL encoded by default. Disable with {@code encode=false}.
* <pre>
* &#64;GET("/user/{name}")
* void encoded(@Path("name") String name);
*
* &#64;GET("/user/{name}")
* void notEncoded(@Path(value="name", encode=false) String name);
* </pre>
* Calling {@code foo.encoded("John+Doe")} yields {@code /user/John%2BDoe} whereas
* {@code foo.notEncoded("John+Doe")} yields {@code /user/John+Doe}.
* <p>
* Path parameters may not be {@code null}.
*/
Expand All @@ -38,4 +51,7 @@
@Target(PARAMETER)
public @interface Path {
String value();

/** Specifies whether the argument value to the annotated method parameter is URL encoded. */
boolean encode() default true;
}
25 changes: 23 additions & 2 deletions retrofit/src/main/java/retrofit/http/Query.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,35 @@
* </pre>
* Calling with {@code foo.list("bar", "baz")} yields
* {@code /list?category=foo&category=bar}.
* <p>
* Parameter names are not URL encoded. Specify {@link #encodeName() encodeName=true} to change
* this behavior.
* <pre>
* &#64;GET("/search")
* void list(@Query(value="foo+bar", encodeName=true) String foobar);
* </pre>
* Calling with {@code foo.list("baz")} yields {@code /search?foo%2Bbar=foo}.
* <p>
* Parameter values are URL encoded by default. Specify {@link #encodeValue() encodeValue=false} to
* change this behavior.
* <pre>
* &#64;GET("/search")
* void list(@Query(value="foo", encodeValue=false) String foo);
* </pre>
* Calling with {@code foo.list("foo+foo"))} yields {@code /search?foo=foo+bar}.
*
* @see EncodedQuery
* @see QueryMap
* @see EncodedQueryMap
*/
@Documented
@Target(PARAMETER)
@Retention(RUNTIME)
public @interface Query {
/** The query parameter name. */
String value();

/** Specifies whether {@link #value()} is URL encoded. */
boolean encodeName() default false;

/** Specifies whether the argument value to the annotated method parameter is URL encoded. */
boolean encodeValue() default true;
}
28 changes: 25 additions & 3 deletions retrofit/src/main/java/retrofit/http/QueryMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
* Query parameter keys and values appended to the URL.
* <p>
* Both keys and values are converted to strings using {@link String#valueOf(Object)}. Values are
* URL encoded and {@code null} will not include the query parameter in the URL.
* URL encoded and {@code null} will not include the query parameter in the URL. {@code null} keys
* are not allowed.
* <p>
* Simple Example:
* <pre>
Expand All @@ -35,13 +36,34 @@
* </pre>
* Calling with {@code foo.list(ImmutableMap.of("foo", "bar", "kit", "kat"))} yields
* {@code /search?foo=bar&kit=kat}.
* <p>
* Map keys representing the parameter names are not URL encoded. Specify
* {@link #encodeNames() encodeNames=true} to change this behavior.
* <pre>
* &#64;GET("/search")
* void list(@QueryMap(encodeNames=true) Map&lt;String, String&gt; filters);
* </pre>
* Calling with {@code foo.list(ImmutableMap.of("foo+bar", "foo+bar"))} yields
* {@code /search?foo%2Bbar=foo}.
* <p>
* Map values representing parameter values are URL encoded by default. Specify
* {@link #encodeValues() encodeValues=false} to change this behavior.
* <pre>
* &#64;GET("/search")
* void list(@QueryMap(encodeValues=false) Map&lt;String, String&gt; filters);
* </pre>
* Calling with {@code foo.list(ImmutableMap.of("foo", "foo+foo"))} yields
* {@code /search?foo=foo%2Bbar}.
*
* @see Query
* @see QueryMap
* @see EncodedQueryMap
*/
@Documented
@Target(PARAMETER)
@Retention(RUNTIME)
public @interface QueryMap {
/** Specifies whether parameter names (keys in the map) are URL encoded. */
boolean encodeNames() default false;

/** Specifies whether parameter values (values in the map) are URL encoded. */
boolean encodeValues() default true;
}
Loading

0 comments on commit 3436cfb

Please sign in to comment.