├── .gitignore ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── README.md ├── src └── main │ ├── java │ └── com │ │ └── spoqa │ │ └── battery │ │ ├── ExceptionHandler.java │ │ ├── OnResponse.java │ │ ├── Config.java │ │ ├── FieldNameTransformer.java │ │ ├── ResponseValidator.java │ │ ├── KeyValuePair.java │ │ ├── RequestPreprocessor.java │ │ ├── exceptions │ │ ├── ContextException.java │ │ ├── ResponseValidationException.java │ │ ├── RpcException.java │ │ ├── MissingFieldException.java │ │ ├── SerializationException.java │ │ ├── DeserializationException.java │ │ └── IncompatibleTypeException.java │ │ ├── annotations │ │ ├── Uri.java │ │ ├── ResponseObject.java │ │ ├── RequestObject.java │ │ ├── UriPath.java │ │ ├── QueryString.java │ │ ├── RequestBody.java │ │ ├── Response.java │ │ └── RpcObject.java │ │ ├── TypeAdapter.java │ │ ├── RequestSerializer.java │ │ ├── android │ │ ├── ResponseDelegate.java │ │ ├── AndroidPlatformUtilsImpl.java │ │ ├── AndroidLogger.java │ │ ├── OkHttpStack.java │ │ ├── VolleyRequest.java │ │ └── AndroidRpcContext.java │ │ ├── transformers │ │ ├── UnderscoreNameTransformer.java │ │ ├── PascalCaseTransformer.java │ │ └── CamelCaseTransformer.java │ │ ├── ResponseDeserializer.java │ │ ├── TypeAdapterCollection.java │ │ ├── PlatformUtils.java │ │ ├── FieldNameTranslator.java │ │ ├── fields │ │ ├── TimestampDateAdapter.java │ │ ├── Rfc1123DateAdapter.java │ │ └── Iso8601DateAdapter.java │ │ ├── Logger.java │ │ ├── StringUtils.java │ │ ├── RpcContext.java │ │ ├── codecs │ │ ├── UrlEncodedFormEncoder.java │ │ ├── MultipartFormDataEncoder.java │ │ └── JsonCodec.java │ │ ├── HttpRequest.java │ │ ├── ReflectionCache.java │ │ ├── RequestFactory.java │ │ ├── ObjectBuilder.java │ │ └── CodecUtils.java │ └── AndroidManifest.xml ├── .travis.yml ├── LICENSE.md ├── gradlew.bat └── gradlew /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | local.properties 3 | .idea 4 | .DS_Store 5 | *.iml 6 | *.ipr 7 | *.iws 8 | build 9 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spoqa/battery/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | battery 2 | ======= 3 | 4 | Barely working. More details are yet to come! 5 | 6 | ## TODO 7 | * Documentation 8 | * Example 9 | * Testing 10 | * README 11 | 12 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Apr 10 15:27:10 PDT 2013 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip 7 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/ExceptionHandler.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery; 6 | 7 | public interface ExceptionHandler { 8 | 9 | public boolean onException(C context, Throwable error); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/OnResponse.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery; 6 | 7 | 8 | public interface OnResponse { 9 | public void onResponse(T object); 10 | public void onFailure(Throwable why); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/Config.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery; 6 | 7 | public final class Config { 8 | 9 | static public boolean DEBUG_DUMP_REQUEST = false; 10 | static public boolean DEBUG_DUMP_RESPONSE = false; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/FieldNameTransformer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery; 6 | 7 | import java.util.List; 8 | 9 | public interface FieldNameTransformer { 10 | 11 | public List decode(String input); 12 | public String encode(List parts); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/ResponseValidator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery; 6 | 7 | import com.spoqa.battery.exceptions.ResponseValidationException; 8 | 9 | public interface ResponseValidator { 10 | 11 | public void validate(Object object) throws ResponseValidationException; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/KeyValuePair.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery; 6 | 7 | public class KeyValuePair { 8 | public KeyType key; 9 | public ValueType value; 10 | 11 | public KeyValuePair(KeyType key, ValueType value) { 12 | this.key = key; 13 | this.value = value; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/RequestPreprocessor.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery; 6 | 7 | import com.spoqa.battery.exceptions.ContextException; 8 | 9 | public interface RequestPreprocessor { 10 | public void validateContext(Object forWhat) throws ContextException; 11 | 12 | public void processHttpRequest(Object object, HttpRequest req); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/exceptions/ContextException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery.exceptions; 6 | 7 | public class ContextException extends Throwable { 8 | private Throwable mWhy; 9 | 10 | public ContextException(Throwable why) { 11 | mWhy = why; 12 | } 13 | 14 | public Throwable why() { 15 | return mWhy; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/exceptions/ResponseValidationException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery.exceptions; 6 | 7 | public class ResponseValidationException extends Throwable { 8 | 9 | public ResponseValidationException() { 10 | super(); 11 | } 12 | 13 | public ResponseValidationException(String message) { 14 | super(message); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/annotations/Uri.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery.annotations; 6 | 7 | import java.lang.annotation.ElementType; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.RetentionPolicy; 10 | import java.lang.annotation.Target; 11 | 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @Target(ElementType.FIELD) 14 | public @interface Uri { 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/annotations/ResponseObject.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery.annotations; 6 | 7 | import java.lang.annotation.ElementType; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.RetentionPolicy; 10 | import java.lang.annotation.Target; 11 | 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @Target(ElementType.FIELD) 14 | public @interface ResponseObject { 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/annotations/RequestObject.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery.annotations; 6 | 7 | import java.lang.annotation.ElementType; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.RetentionPolicy; 10 | import java.lang.annotation.Target; 11 | 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @Target(ElementType.FIELD) 14 | public @interface RequestObject { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/TypeAdapter.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery; 6 | 7 | import com.spoqa.battery.exceptions.DeserializationException; 8 | import com.spoqa.battery.exceptions.SerializationException; 9 | 10 | public interface TypeAdapter { 11 | 12 | public Class getType(); 13 | public T decode(String s) throws DeserializationException; 14 | public String encode(T object) throws SerializationException; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/annotations/UriPath.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery.annotations; 6 | 7 | import java.lang.annotation.ElementType; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.RetentionPolicy; 10 | import java.lang.annotation.Target; 11 | 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @Target(ElementType.FIELD) 14 | public @interface UriPath { 15 | public int value(); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: 2 | - android 3 | 4 | jdk: 5 | - oraclejdk7 6 | 7 | env: 8 | matrix: 9 | - ANDROID_TARGET=android-19 ANDROID_ABI=armeabi-v7a 10 | 11 | android: 12 | components: 13 | - build-tools-21.1.2 14 | - android-21 15 | - sysimg-21 16 | 17 | before_script: 18 | # Create and start emulator 19 | - echo no | android create avd --force -n test -t $ANDROID_TARGET --abi $ANDROID_ABI 20 | - emulator -avd test -no-skin -no-audio -no-window & 21 | - adb wait-for-device 22 | - adb shell input keyevent 82 & 23 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/annotations/QueryString.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery.annotations; 6 | 7 | import java.lang.annotation.ElementType; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.RetentionPolicy; 10 | import java.lang.annotation.Target; 11 | 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @Target(ElementType.FIELD) 14 | public @interface QueryString { 15 | 16 | public String value() default ""; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/annotations/RequestBody.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery.annotations; 6 | 7 | import java.lang.annotation.ElementType; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.RetentionPolicy; 10 | import java.lang.annotation.Target; 11 | 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @Target(ElementType.FIELD) 14 | public @interface RequestBody { 15 | 16 | public String value() default ""; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/annotations/Response.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery.annotations; 6 | 7 | import java.lang.annotation.ElementType; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.RetentionPolicy; 10 | import java.lang.annotation.Target; 11 | 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @Target({ElementType.METHOD, ElementType.FIELD}) 14 | public @interface Response { 15 | 16 | public boolean required() default false; 17 | public String value() default ""; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/exceptions/RpcException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery.exceptions; 6 | 7 | public class RpcException extends Throwable { 8 | 9 | private String mMessage; 10 | 11 | public RpcException(String message) { 12 | mMessage = message; 13 | } 14 | 15 | public String getMessage() { 16 | return mMessage; 17 | } 18 | 19 | @Override 20 | public String toString() { 21 | return String.format("RpcException: %1$s", mMessage); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/RequestSerializer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery; 6 | 7 | import com.spoqa.battery.exceptions.SerializationException; 8 | 9 | public interface RequestSerializer { 10 | 11 | public byte[] serializeObject(Object o, FieldNameTranslator translator, 12 | TypeAdapterCollection typeAdapters) 13 | throws SerializationException; 14 | 15 | public String serializationContentType(); 16 | 17 | public boolean supportsCompositeType(); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/android/ResponseDelegate.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery.android; 6 | 7 | public class ResponseDelegate { 8 | private String mData; 9 | private String mContentType; 10 | 11 | public ResponseDelegate(String data, String contentType) { 12 | mData = data; 13 | mContentType = contentType; 14 | } 15 | 16 | public String data() { 17 | return mData; 18 | } 19 | 20 | public String contentType() { 21 | return mContentType; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/exceptions/MissingFieldException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery.exceptions; 6 | 7 | public class MissingFieldException extends Throwable { 8 | 9 | private String mFieldName; 10 | 11 | public MissingFieldException(String fieldName) { 12 | mFieldName = fieldName; 13 | } 14 | 15 | public String getFieldName() { 16 | return mFieldName; 17 | } 18 | 19 | public String toString() { 20 | return String.format("Field %1$s: missing", mFieldName); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/android/AndroidPlatformUtilsImpl.java: -------------------------------------------------------------------------------- 1 | package com.spoqa.battery.android; 2 | 3 | import android.webkit.MimeTypeMap; 4 | 5 | import com.spoqa.battery.PlatformUtils; 6 | 7 | import java.io.File; 8 | 9 | public class AndroidPlatformUtilsImpl implements PlatformUtils.PlatformUtilsImpl { 10 | @Override 11 | public String getMimeType(File path) { 12 | String type = null; 13 | String extension = MimeTypeMap.getFileExtensionFromUrl(path.getAbsolutePath()); 14 | if (extension != null) { 15 | MimeTypeMap mime = MimeTypeMap.getSingleton(); 16 | type = mime.getMimeTypeFromExtension(extension); 17 | } 18 | return type; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/exceptions/SerializationException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery.exceptions; 6 | 7 | public class SerializationException extends Throwable { 8 | 9 | private Throwable mForWhat; 10 | 11 | public SerializationException() { 12 | 13 | } 14 | 15 | public SerializationException(Throwable forWhat) { 16 | mForWhat = forWhat; 17 | } 18 | 19 | public Throwable forWhat() { 20 | return mForWhat; 21 | } 22 | 23 | @Override 24 | public String toString() { 25 | return String.format("Error while serializing: %1$s", mForWhat.toString()); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/exceptions/DeserializationException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery.exceptions; 6 | 7 | public class DeserializationException extends Throwable { 8 | 9 | private Throwable mForWhat; 10 | 11 | public DeserializationException() { 12 | 13 | } 14 | 15 | public DeserializationException(Throwable forWhat) { 16 | mForWhat = forWhat; 17 | } 18 | 19 | public Throwable forWhat() { 20 | return mForWhat; 21 | } 22 | 23 | @Override 24 | public String toString() { 25 | return String.format("Error while deserializing: %1$s", mForWhat.toString()); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/transformers/UnderscoreNameTransformer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery.transformers; 6 | 7 | import com.spoqa.battery.FieldNameTransformer; 8 | import com.spoqa.battery.StringUtils; 9 | 10 | import java.util.Arrays; 11 | import java.util.List; 12 | 13 | public class UnderscoreNameTransformer implements FieldNameTransformer { 14 | 15 | @Override 16 | public List decode(String key) { 17 | return Arrays.asList(key.split("_")); 18 | } 19 | 20 | @Override 21 | public String encode(List parts) { 22 | return StringUtils.join(parts, "_", StringUtils.toLowerTransformer); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/exceptions/IncompatibleTypeException.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery.exceptions; 6 | 7 | public class IncompatibleTypeException extends Throwable { 8 | private String mFieldName; 9 | private String mExpectedType; 10 | private String mValue; 11 | 12 | public IncompatibleTypeException(String fieldName, String expectedType, String value) { 13 | mFieldName = fieldName; 14 | mExpectedType = expectedType; 15 | mValue = value; 16 | } 17 | 18 | @Override 19 | public String toString() { 20 | return String.format("field %1$s: %2$s expected, value is %3$s.", mFieldName, mExpectedType, 21 | mValue); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/ResponseDeserializer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery; 6 | 7 | import com.spoqa.battery.exceptions.DeserializationException; 8 | 9 | public interface ResponseDeserializer { 10 | 11 | public Object parseInput(String input) throws DeserializationException; 12 | 13 | public boolean containsChild(Object internalObject, String key); 14 | 15 | public Object queryObjectChild(Object internalObject, String key); 16 | 17 | public Iterable queryArrayChildren(Object internalArray); 18 | 19 | public boolean isObject(Class internalClass); 20 | 21 | public boolean isArray(Class internalClass); 22 | 23 | public String deserializationContentType(); 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/TypeAdapterCollection.java: -------------------------------------------------------------------------------- 1 | package com.spoqa.battery; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public class TypeAdapterCollection { 7 | 8 | private Map, TypeAdapter> mTypeAdapters; 9 | 10 | public TypeAdapterCollection() { 11 | mTypeAdapters = new HashMap, TypeAdapter>(); 12 | } 13 | 14 | public void register(TypeAdapter adapter) { 15 | mTypeAdapters.put(adapter.getType(), adapter); 16 | } 17 | 18 | public boolean contains(Class clazz) { 19 | return mTypeAdapters.containsKey(clazz); 20 | } 21 | 22 | public TypeAdapter query(Class clazz) { 23 | if (mTypeAdapters.containsKey(clazz)) 24 | return mTypeAdapters.get(clazz); 25 | 26 | return null; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/PlatformUtils.java: -------------------------------------------------------------------------------- 1 | package com.spoqa.battery; 2 | 3 | import java.io.File; 4 | 5 | public final class PlatformUtils { 6 | 7 | private static final String TAG = "PlatformUtils"; 8 | 9 | public static interface PlatformUtilsImpl { 10 | abstract String getMimeType(File path); 11 | } 12 | 13 | private static PlatformUtilsImpl sCurrentImpl; 14 | 15 | public static void registerPlatformUtils(PlatformUtilsImpl impl) { 16 | sCurrentImpl = impl; 17 | } 18 | 19 | static public void unregisterPlatformUtils() { 20 | sCurrentImpl = null; 21 | } 22 | 23 | static public String getMimeType(File path) { 24 | if (sCurrentImpl != null) { 25 | return sCurrentImpl.getMimeType(path); 26 | } else { 27 | Logger.warn(TAG, "No PlaformUtilsImpl installed"); 28 | return null; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/android/AndroidLogger.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery.android; 6 | 7 | import android.util.Log; 8 | 9 | import com.spoqa.battery.Logger; 10 | 11 | public class AndroidLogger implements Logger.LoggerImpl { 12 | @Override 13 | public void debug(String tag, String msg) { 14 | Log.d(tag, msg); 15 | } 16 | 17 | @Override 18 | public void error(String tag, String msg) { 19 | Log.e(tag, msg); 20 | } 21 | 22 | @Override 23 | public void info(String tag, String msg) { 24 | Log.i(tag, msg); 25 | } 26 | 27 | @Override 28 | public void verbose(String tag, String msg) { 29 | Log.v(tag, msg); 30 | } 31 | 32 | @Override 33 | public void warn(String tag, String msg) { 34 | Log.w(tag, msg); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/transformers/PascalCaseTransformer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery.transformers; 6 | 7 | import com.spoqa.battery.FieldNameTransformer; 8 | import com.spoqa.battery.StringUtils; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | public class PascalCaseTransformer implements FieldNameTransformer { 14 | 15 | @Override 16 | public List decode(String input) { 17 | return StringUtils.splitByCase(input); 18 | } 19 | 20 | @Override 21 | public String encode(List parts) { 22 | if (parts.size() == 0) 23 | return ""; 24 | 25 | List output = new ArrayList(); 26 | 27 | for (int i = 0; i < parts.size(); ++i) { 28 | output.add(StringUtils.uppercaseFirst(parts.get(i))); 29 | } 30 | 31 | return StringUtils.join(output, ""); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/FieldNameTranslator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery; 6 | 7 | public class FieldNameTranslator { 8 | 9 | private FieldNameTransformer mRemoteTransformer; 10 | private FieldNameTransformer mLocalTransformer; 11 | 12 | public FieldNameTranslator(FieldNameTransformer remote, FieldNameTransformer local) { 13 | mRemoteTransformer = remote; 14 | mLocalTransformer = local; 15 | } 16 | 17 | public String remoteToLocal(String name) { 18 | if (mRemoteTransformer == null || mLocalTransformer == null) 19 | return name; 20 | 21 | return mLocalTransformer.encode(mRemoteTransformer.decode(name)); 22 | } 23 | 24 | public String localToRemote(String name) { 25 | if (mRemoteTransformer == null || mLocalTransformer == null) 26 | return name; 27 | 28 | return mRemoteTransformer.encode(mLocalTransformer.decode(name)); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/annotations/RpcObject.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery.annotations; 6 | 7 | import com.spoqa.battery.HttpRequest; 8 | 9 | import java.lang.annotation.ElementType; 10 | import java.lang.annotation.Retention; 11 | import java.lang.annotation.RetentionPolicy; 12 | import java.lang.annotation.Target; 13 | 14 | /** 15 | * Annotation to specify callee's HTTP request metadata 16 | */ 17 | @Retention(RetentionPolicy.RUNTIME) 18 | @Target(ElementType.TYPE) 19 | public @interface RpcObject { 20 | static final class NULL {} 21 | 22 | public int method() default HttpRequest.Methods.GET; 23 | public String uri() default ""; 24 | public Class requestSerializer() default NULL.class; 25 | public Class localName() default NULL.class; 26 | public Class remoteName() default NULL.class; 27 | public Class context() default NULL.class; 28 | public String expectedContentType() default ""; 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/transformers/CamelCaseTransformer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery.transformers; 6 | 7 | import com.spoqa.battery.FieldNameTransformer; 8 | import com.spoqa.battery.StringUtils; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | public class CamelCaseTransformer implements FieldNameTransformer { 14 | 15 | @Override 16 | public List decode(String input) { 17 | return StringUtils.splitByCase(input); 18 | } 19 | 20 | @Override 21 | public String encode(List parts) { 22 | if (parts.size() == 0) 23 | return ""; 24 | 25 | List output = new ArrayList(); 26 | 27 | for (int i = 0; i < parts.size(); ++i) { 28 | if (i == 0) 29 | output.add(parts.get(i)); 30 | else 31 | output.add(StringUtils.uppercaseFirst(parts.get(i))); 32 | } 33 | 34 | return StringUtils.join(output, ""); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License 2 | =============== 3 | 4 | Copyright (c) 2014 Spoqa, All Right Reserved. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | 24 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/fields/TimestampDateAdapter.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery.fields; 6 | 7 | import com.spoqa.battery.TypeAdapter; 8 | import com.spoqa.battery.exceptions.DeserializationException; 9 | 10 | import java.sql.Timestamp; 11 | import java.util.Calendar; 12 | import java.util.Date; 13 | 14 | public class TimestampDateAdapter implements TypeAdapter { 15 | 16 | private boolean mMsec; 17 | 18 | public TimestampDateAdapter(boolean msec) { 19 | mMsec = msec; 20 | } 21 | 22 | @Override 23 | public Class getType() { 24 | return Date.class; 25 | } 26 | 27 | @Override 28 | public Date decode(String s) throws DeserializationException { 29 | long l = Long.parseLong(s); 30 | if (!mMsec) 31 | l *= 1000; 32 | 33 | Timestamp timestamp = new Timestamp(l); 34 | return new Date(timestamp.getTime()); 35 | } 36 | 37 | @Override 38 | public String encode(Date object) { 39 | Calendar calendar = Calendar.getInstance(); 40 | calendar.setTime(object); 41 | 42 | if (mMsec) 43 | return Long.toString(calendar.getTimeInMillis()); 44 | else 45 | return Long.toString(calendar.getTimeInMillis() / 1000); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/android/OkHttpStack.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery.android; 6 | 7 | import com.android.volley.toolbox.HurlStack; 8 | import com.squareup.okhttp.OkHttpClient; 9 | import com.squareup.okhttp.OkUrlFactory; 10 | 11 | import java.io.IOException; 12 | import java.net.HttpURLConnection; 13 | import java.net.URL; 14 | 15 | import javax.net.ssl.SSLContext; 16 | 17 | public class OkHttpStack extends HurlStack { 18 | private final OkUrlFactory mFactory; 19 | 20 | public OkHttpStack() { 21 | this(new OkHttpClient()); 22 | } 23 | 24 | public OkHttpStack(OkHttpClient client) { 25 | if (client == null) { 26 | throw new NullPointerException("Client must not be null."); 27 | } 28 | 29 | try { 30 | SSLContext sslContext = SSLContext.getInstance("TLS"); 31 | sslContext.init(null, null, null); 32 | client.setSslSocketFactory(sslContext.getSocketFactory()); 33 | } catch (Exception e) { 34 | throw new AssertionError(); // The system has no TLS. Just give up. 35 | } 36 | 37 | mFactory = new OkUrlFactory(client); 38 | } 39 | 40 | @Override 41 | protected HttpURLConnection createConnection(URL url) throws IOException { 42 | return mFactory.open(url); 43 | } 44 | } -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/Logger.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery; 6 | 7 | public final class Logger { 8 | 9 | static public interface LoggerImpl { 10 | abstract void debug(String tag, String msg); 11 | abstract void error(String tag, String msg); 12 | abstract void info(String tag, String msg); 13 | abstract void verbose(String tag, String msg); 14 | abstract void warn(String tag, String msg); 15 | } 16 | 17 | static private LoggerImpl sCurrentLogger; 18 | 19 | static public void registerLogger(LoggerImpl logger) { 20 | sCurrentLogger = logger; 21 | } 22 | 23 | static public void unregisterLogger() { 24 | sCurrentLogger = null; 25 | } 26 | 27 | static public void debug(String tag, String msg) { 28 | if (sCurrentLogger != null) 29 | sCurrentLogger.debug(tag, msg); 30 | } 31 | 32 | static public void error(String tag, String msg) { 33 | if (sCurrentLogger != null) 34 | sCurrentLogger.error(tag, msg); 35 | } 36 | 37 | static public void info(String tag, String msg) { 38 | if (sCurrentLogger != null) 39 | sCurrentLogger.info(tag, msg); 40 | } 41 | 42 | static public void verbose(String tag, String msg) { 43 | if (sCurrentLogger != null) 44 | sCurrentLogger.verbose(tag, msg); 45 | } 46 | 47 | static public void warn(String tag, String msg) { 48 | if (sCurrentLogger != null) 49 | sCurrentLogger.warn(tag, msg); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/fields/Rfc1123DateAdapter.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery.fields; 6 | 7 | import com.spoqa.battery.TypeAdapter; 8 | import com.spoqa.battery.exceptions.DeserializationException; 9 | 10 | import java.text.ParseException; 11 | import java.text.SimpleDateFormat; 12 | import java.util.Date; 13 | import java.util.Locale; 14 | import java.util.TimeZone; 15 | 16 | public class Rfc1123DateAdapter implements TypeAdapter { 17 | 18 | private SimpleDateFormat mDateFormat; 19 | private SimpleDateFormat mDateWithMicrosecFormat; 20 | 21 | public Rfc1123DateAdapter() { 22 | mDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US); 23 | mDateWithMicrosecFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss.SSS z", Locale.US); 24 | } 25 | 26 | public Rfc1123DateAdapter(TimeZone timezone) { 27 | this(); 28 | mDateFormat.setTimeZone(timezone); 29 | mDateWithMicrosecFormat.setTimeZone(timezone); 30 | } 31 | 32 | @Override 33 | public Class getType() { 34 | return Date.class; 35 | } 36 | 37 | @Override 38 | public Date decode(String s) throws DeserializationException { 39 | try { 40 | return mDateFormat.parse(s); 41 | } catch (ParseException e) { 42 | try { 43 | return mDateWithMicrosecFormat.parse(s); 44 | } catch (ParseException e2) { 45 | //return new Date(); 46 | throw new DeserializationException(e2); 47 | } 48 | } 49 | } 50 | 51 | @Override 52 | public String encode(Date object) { 53 | return mDateFormat.format(object); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/fields/Iso8601DateAdapter.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery.fields; 6 | 7 | import com.spoqa.battery.TypeAdapter; 8 | import com.spoqa.battery.exceptions.DeserializationException; 9 | 10 | import java.text.ParseException; 11 | import java.text.SimpleDateFormat; 12 | import java.util.Date; 13 | import java.util.Locale; 14 | 15 | public class Iso8601DateAdapter implements TypeAdapter { 16 | 17 | private SimpleDateFormat mDateTimeFormatWithMsec; 18 | private SimpleDateFormat mDateTimeFormat; 19 | private SimpleDateFormat mDateFormat; 20 | 21 | public Iso8601DateAdapter() { 22 | mDateTimeFormatWithMsec = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSSZ", Locale.getDefault()); 23 | mDateTimeFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.getDefault()); 24 | mDateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()); 25 | } 26 | 27 | @Override 28 | public Class getType() { 29 | return Date.class; 30 | } 31 | 32 | @Override 33 | public Date decode(String s) throws DeserializationException { 34 | s = s.replaceAll(":(\\d\\d)$", "$1"); 35 | 36 | try { 37 | return mDateTimeFormatWithMsec.parse(s); 38 | } catch (ParseException e) { 39 | try { 40 | return mDateTimeFormat.parse(s); 41 | } catch (ParseException e1) { 42 | try { 43 | return mDateFormat.parse(s); 44 | } catch (ParseException e2) { 45 | throw new DeserializationException(e); 46 | } 47 | } 48 | } 49 | } 50 | 51 | @Override 52 | public String encode(Date object) { 53 | if (object.getHours() == 0 && object.getMinutes() == 0 && object.getSeconds() == 0) 54 | return mDateFormat.format(object); 55 | else 56 | return mDateTimeFormatWithMsec.format(object); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/StringUtils.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | public final class StringUtils { 11 | 12 | public static interface StringTransformer { 13 | public String transform(String input); 14 | } 15 | 16 | public static StringTransformer toUpperTransformer = new StringTransformer() { 17 | @Override 18 | public String transform(String input) { 19 | return input.toUpperCase(); 20 | } 21 | }; 22 | 23 | public static StringTransformer toLowerTransformer = new StringTransformer() { 24 | @Override 25 | public String transform(String input) { 26 | return input.toLowerCase(); 27 | } 28 | }; 29 | 30 | public static String join(List array, String delimiter, StringTransformer transformer) { 31 | StringBuilder sb = new StringBuilder(); 32 | for (int i = 0; i < array.size(); ++i) { 33 | if (i > 0) 34 | sb.append(delimiter); 35 | 36 | String elem = array.get(i); 37 | if (transformer != null) 38 | elem = transformer.transform(elem); 39 | sb.append(elem); 40 | } 41 | return sb.toString(); 42 | } 43 | 44 | public static String join(List array, String delimiter) { 45 | return join(array, delimiter, null); 46 | } 47 | 48 | public static String uppercaseFirst(String input) { 49 | return input.substring(0, 1).toUpperCase() + input.substring(1).toLowerCase(); 50 | } 51 | 52 | public static List splitByCase(String input) { 53 | List output = new ArrayList(); 54 | 55 | boolean isUppercase = false; 56 | boolean isDigit = false; 57 | boolean continuousUppercase = false; 58 | int startIndex = 0; 59 | 60 | for (int i = 0; i < input.length(); ++i) { 61 | char c = input.charAt(i); 62 | boolean currentUppercase = Character.isUpperCase(c); 63 | if (i == 0) 64 | isUppercase = currentUppercase; 65 | 66 | if (Character.isDigit(c)) { 67 | isDigit = true; 68 | } else if (isDigit & !Character.isDigit(c)) { 69 | output.add(input.substring(startIndex, i - 1)); 70 | startIndex = i - 1; 71 | isDigit = false; 72 | } else if (currentUppercase && !isUppercase) { 73 | output.add(input.substring(startIndex, i)); 74 | startIndex = i; 75 | } else if (currentUppercase) { 76 | continuousUppercase = true; 77 | } else if (continuousUppercase) { 78 | output.add(input.substring(startIndex, i - 1)); 79 | startIndex = i - 1; 80 | continuousUppercase = false; 81 | } 82 | 83 | isUppercase = currentUppercase; 84 | } 85 | 86 | output.add(input.substring(startIndex)); 87 | 88 | return output; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/android/VolleyRequest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery.android; 6 | 7 | import com.android.volley.DefaultRetryPolicy; 8 | import com.android.volley.NetworkResponse; 9 | import com.android.volley.Request; 10 | import com.android.volley.Response; 11 | import com.android.volley.RetryPolicy; 12 | import com.android.volley.VolleyError; 13 | import com.android.volley.toolbox.HttpHeaderParser; 14 | import com.spoqa.battery.HttpRequest; 15 | import com.spoqa.battery.Logger; 16 | 17 | import java.io.UnsupportedEncodingException; 18 | import java.util.Map; 19 | 20 | public class VolleyRequest extends Request { 21 | private static final String TAG = "VolleyRequest"; 22 | 23 | private Response.Listener mListener; 24 | private Map mHeaders; 25 | private byte[] mRequestBody; 26 | private String mContentType; 27 | 28 | public VolleyRequest(HttpRequest request, Response.Listener listener, 29 | Response.ErrorListener errorListener) { 30 | super(translateVolleyHttpMethod(request.getMethod()), request.getUri(), errorListener); 31 | mListener = listener; 32 | mHeaders = request.getHeaders(); 33 | mRequestBody = request.getRequestBody(); 34 | mContentType = request.getContentType(); 35 | 36 | // forbid retry if not GET 37 | if (request.getMethod() != HttpRequest.Methods.GET) { 38 | setRetryPolicy(new DefaultRetryPolicy( 39 | 10000, 40 | 0, 41 | DefaultRetryPolicy.DEFAULT_BACKOFF_MULT)); 42 | } 43 | } 44 | 45 | static private int translateVolleyHttpMethod(int method) { 46 | switch (method) { 47 | case HttpRequest.Methods.GET: 48 | return Request.Method.GET; 49 | case HttpRequest.Methods.DELETE: 50 | return Request.Method.DELETE; 51 | case HttpRequest.Methods.POST: 52 | return Request.Method.POST; 53 | case HttpRequest.Methods.PUT: 54 | return Request.Method.PUT; 55 | default: 56 | Logger.warn(TAG, String.format("Invalid HTTP method %1$d. Defaulting to GET...")); 57 | return Request.Method.GET; 58 | } 59 | } 60 | 61 | @Override 62 | public Map getHeaders() { 63 | return mHeaders; 64 | } 65 | 66 | @Override 67 | protected Response parseNetworkResponse(NetworkResponse networkResponse) { 68 | try { 69 | return Response.success( 70 | new ResponseDelegate( 71 | new String(networkResponse.data, HttpHeaderParser.parseCharset(networkResponse.headers)), 72 | networkResponse.headers.get(HttpRequest.HEADER_CONTENT_TYPE)), 73 | HttpHeaderParser.parseCacheHeaders(networkResponse)); 74 | } catch (UnsupportedEncodingException e) { 75 | e.printStackTrace(); 76 | return null; 77 | } 78 | } 79 | 80 | @Override 81 | protected void deliverResponse(ResponseDelegate response) { 82 | mListener.onResponse(response); 83 | } 84 | 85 | @Override 86 | public String getBodyContentType() { 87 | return mContentType; 88 | } 89 | 90 | @Override 91 | public byte[] getBody() { 92 | return mRequestBody; 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/RpcContext.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | public class RpcContext { 11 | 12 | static final private String TAG = "ExecutionContext"; 13 | 14 | private String mDefaultUriPrefix; 15 | private RequestPreprocessor mRequestPreprocessor; 16 | private ResponseValidator mResponseValidator; 17 | private RequestSerializer mRequestSerializer; 18 | private FieldNameTransformer mLocalFieldName; 19 | private FieldNameTransformer mRemoteFieldName; 20 | private Map, ExceptionHandler> mExceptionHandlers; 21 | private TypeAdapterCollection mTypeAdapters; 22 | 23 | public RpcContext() { 24 | mExceptionHandlers = new HashMap, 25 | ExceptionHandler>(); 26 | mTypeAdapters = new TypeAdapterCollection(); 27 | } 28 | 29 | public String getDefaultUriPrefix() { 30 | return mDefaultUriPrefix; 31 | } 32 | 33 | public RequestPreprocessor getRequestPreprocessor() { 34 | return mRequestPreprocessor; 35 | } 36 | 37 | public ResponseValidator getResponseValidator() { 38 | return mResponseValidator; 39 | } 40 | 41 | public RequestSerializer getRequestSerializer() { 42 | return mRequestSerializer; 43 | } 44 | 45 | public FieldNameTransformer getLocalFieldNameTransformer() { 46 | return mLocalFieldName; 47 | } 48 | 49 | public FieldNameTransformer getRemoteFieldNameTransformer() { 50 | return mRemoteFieldName; 51 | } 52 | 53 | public void setDefaultUriPrefix(String prefix) { 54 | if (prefix.startsWith("http://") || prefix.startsWith("https://")) 55 | mDefaultUriPrefix = prefix; 56 | 57 | // strip out trailing slash 58 | if (mDefaultUriPrefix.endsWith("/")) 59 | mDefaultUriPrefix = mDefaultUriPrefix.substring(0, mDefaultUriPrefix.length() - 1); 60 | } 61 | 62 | public void setRequestPreprocessor(RequestPreprocessor preprocessor) { 63 | mRequestPreprocessor = preprocessor; 64 | } 65 | 66 | public void setResponseValidator(ResponseValidator validator) { 67 | mResponseValidator = validator; 68 | } 69 | 70 | public void setRequestSerializer(RequestSerializer serializer) { 71 | mRequestSerializer = serializer; 72 | } 73 | 74 | public void setFieldNameTransformer(FieldNameTransformer local, FieldNameTransformer remote) { 75 | mLocalFieldName = local; 76 | mRemoteFieldName = remote; 77 | } 78 | 79 | public void registerExceptionHandler(Class clazz, ExceptionHandler handler) { 80 | mExceptionHandlers.put(clazz, handler); 81 | } 82 | 83 | public boolean dispatchErrorHandler(C frontendContext, T ex) { 84 | Class clazz = (Class) ex.getClass(); 85 | 86 | if (Config.DEBUG_DUMP_RESPONSE) { 87 | Logger.debug(TAG, "got exception: " + clazz.getName()); 88 | } 89 | 90 | while (clazz != null) { 91 | if (mExceptionHandlers.containsKey(clazz)) { 92 | if (Config.DEBUG_DUMP_RESPONSE) { 93 | Logger.debug(TAG, " handling: " + clazz.getName()); 94 | } 95 | 96 | ExceptionHandler callback = (ExceptionHandler) mExceptionHandlers.get(clazz); 97 | boolean ret = callback.onException(frontendContext, ex); 98 | 99 | if (ret) 100 | return true; 101 | } 102 | 103 | /* find for superclass */ 104 | Class super_ = clazz.getSuperclass(); 105 | if (!Throwable.class.isAssignableFrom(super_)) 106 | break; 107 | 108 | clazz = (Class) super_; 109 | } 110 | 111 | return false; 112 | } 113 | 114 | public TypeAdapterCollection getTypeAdapters() { 115 | return mTypeAdapters; 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/codecs/UrlEncodedFormEncoder.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery.codecs; 6 | 7 | import com.spoqa.battery.CodecUtils; 8 | import com.spoqa.battery.FieldNameTranslator; 9 | import com.spoqa.battery.Logger; 10 | import com.spoqa.battery.RequestSerializer; 11 | import com.spoqa.battery.TypeAdapterCollection; 12 | import com.spoqa.battery.annotations.RequestBody; 13 | import com.spoqa.battery.exceptions.SerializationException; 14 | 15 | import java.io.IOException; 16 | import java.io.InputStream; 17 | import java.io.UnsupportedEncodingException; 18 | import java.lang.reflect.Field; 19 | import java.net.URLEncoder; 20 | import java.util.List; 21 | 22 | public class UrlEncodedFormEncoder implements RequestSerializer { 23 | private static final String TAG = "UrlEncodedFormEncoder"; 24 | 25 | private static final String MIME_TYPE = "application/x-www-form-urlencoded; charset=utf-8"; 26 | 27 | public UrlEncodedFormEncoder() { 28 | 29 | } 30 | 31 | @Override 32 | public byte[] serializeObject(Object o, FieldNameTranslator translator, 33 | TypeAdapterCollection typeAdapters) throws SerializationException { 34 | StringBuilder sb = new StringBuilder(); 35 | 36 | List fields = CodecUtils.getAnnotatedFields(null, RequestBody.class, o.getClass()); 37 | for (Field f : fields) { 38 | RequestBody annotation = f.getAnnotation(RequestBody.class); 39 | Class type = f.getType(); 40 | String localName = f.getName(); 41 | String foreignName; 42 | if (annotation.value().length() > 0) 43 | foreignName = annotation.value(); 44 | else 45 | foreignName = translator.localToRemote(localName); 46 | String value = ""; 47 | 48 | try { 49 | Object element = f.get(o); 50 | 51 | if (element == null) { 52 | continue; 53 | } else if (CodecUtils.isString(type)) { 54 | append(sb, foreignName, (String) element); 55 | } else if (CodecUtils.isFloat(type)) { 56 | append(sb, foreignName, Float.toString((Float) element)); 57 | } else if (CodecUtils.isDouble(type)) { 58 | append(sb, foreignName, Double.toString((Double) element)); 59 | } else if (CodecUtils.isBoolean(type)) { 60 | append(sb, foreignName, Boolean.toString((Boolean) element)); 61 | } else if (CodecUtils.isInteger(type)) { 62 | append(sb, foreignName, Integer.toString((Integer) element)); 63 | } else if (CodecUtils.isLong(type)) { 64 | append(sb, foreignName, Long.toString((Long) element)); 65 | } else if (type.isEnum()) { 66 | append(sb, foreignName, element.toString()); 67 | } else if (CodecUtils.isList(type)) { 68 | for (Object innerElement : (List) element) 69 | append(sb, foreignName, innerElement.toString()); 70 | } else if (element instanceof InputStream) { 71 | Logger.warn(TAG, "Could not attach byte stream"); 72 | } else if (typeAdapters.contains(type)) { 73 | append(sb, foreignName, typeAdapters.query(type).encode(element)); 74 | } else { 75 | Logger.warn(TAG, String.format("Field %1$s is not serializable", type.getName())); 76 | } 77 | } catch (IllegalAccessException e) { 78 | e.printStackTrace(); 79 | continue; 80 | } 81 | } 82 | 83 | try { 84 | return sb.toString().getBytes("utf-8"); 85 | } catch (UnsupportedEncodingException e) { 86 | e.printStackTrace(); 87 | return new byte[0]; 88 | } 89 | } 90 | 91 | private void append(StringBuilder sb, String key, String value) { 92 | if (sb.length() != 0) 93 | sb.append('&'); 94 | 95 | try { 96 | sb.append(URLEncoder.encode(key, "utf-8")); 97 | sb.append('='); 98 | sb.append(URLEncoder.encode(value, "utf-8")); 99 | } catch (UnsupportedEncodingException e) { 100 | e.printStackTrace(); 101 | } 102 | } 103 | 104 | @Override 105 | public String serializationContentType() { 106 | return MIME_TYPE; 107 | } 108 | 109 | @Override 110 | public boolean supportsCompositeType() { 111 | return false; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/HttpRequest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery; 6 | 7 | import java.io.ByteArrayOutputStream; 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | import java.io.UnsupportedEncodingException; 11 | import java.net.URLEncoder; 12 | import java.util.HashMap; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | public class HttpRequest { 17 | static final private String TAG = "HttpRequest"; 18 | 19 | public static final class Methods { 20 | public static final int GET = 1; 21 | public static final int POST = 2; 22 | public static final int PUT = 3; 23 | public static final int DELETE = 4; 24 | } 25 | 26 | public static final String HEADER_ACCEPT = "Accept"; 27 | public static final String HEADER_CONTENT_TYPE = "Content-Type"; 28 | 29 | private int mMethod; 30 | private String mUri; 31 | private Map mHeaders; 32 | private Map mParams; 33 | private byte[] mRequestBody; 34 | private FieldNameTranslator mFieldNameTranslator; 35 | private Object mRequestObject; 36 | private String mContentType; 37 | 38 | public HttpRequest(String uri) { 39 | mMethod = Methods.GET; 40 | mUri = uri; 41 | mHeaders = new HashMap(); 42 | mParams = new HashMap(); 43 | } 44 | 45 | public HttpRequest(int method, String uri) { 46 | mMethod = method; 47 | mUri = uri; 48 | mHeaders = new HashMap(); 49 | mParams = new HashMap(); 50 | } 51 | 52 | public void setRequestBody(byte[] body) { 53 | mRequestBody = body; 54 | } 55 | 56 | public void setRequestBody(InputStream inputStream) { 57 | ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 58 | 59 | int nRead; 60 | byte[] data = new byte[8192]; 61 | 62 | try { 63 | while ((nRead = inputStream.read(data, 0, data.length)) != 1) 64 | buffer.write(data, 0, nRead); 65 | buffer.flush(); 66 | } catch (IOException e) { 67 | e.printStackTrace(); 68 | } 69 | 70 | mRequestBody = buffer.toByteArray(); 71 | } 72 | 73 | public void setRequestObject(Object requestObject) { 74 | mRequestObject = requestObject; 75 | } 76 | 77 | public void setContentType(String contentType) { 78 | mContentType = contentType; 79 | } 80 | 81 | public void putHeader(String key, String value) { 82 | mHeaders.put(key, value); 83 | } 84 | 85 | public void putParameter(String key, Object value) { 86 | mParams.put(key, value); 87 | } 88 | 89 | public void putParameters(Map params) { 90 | for (String key : params.keySet()) 91 | mParams.put(key, params.get(key)); 92 | } 93 | 94 | public void removeHeader(String key) { 95 | mHeaders.remove(key); 96 | } 97 | 98 | public void removeParameter(String key) { 99 | if (mParams.containsKey(key)) 100 | mParams.remove(key); 101 | } 102 | 103 | public void setNameTranslator(FieldNameTranslator fieldNameTranslator) { 104 | mFieldNameTranslator = fieldNameTranslator; 105 | } 106 | 107 | public byte[] getRequestBody() { 108 | return mRequestBody; 109 | } 110 | 111 | public Map getHeaders() { 112 | return mHeaders; 113 | } 114 | 115 | public int getMethod() { 116 | return mMethod; 117 | } 118 | 119 | public String getContentType() { 120 | return mContentType; 121 | } 122 | 123 | public FieldNameTranslator getFieldNameTranslator() { 124 | return mFieldNameTranslator; 125 | } 126 | 127 | public String getUri() { 128 | StringBuilder sb = new StringBuilder(); 129 | sb.append(mUri); 130 | 131 | char delimiter; 132 | if (mUri.contains("?")) 133 | delimiter = '&'; 134 | else 135 | delimiter = '?'; 136 | 137 | for (String key : mParams.keySet()) { 138 | Object value = mParams.get(key); 139 | 140 | if (value instanceof List && value != null) { 141 | for (Object innerValue : (List) value) { 142 | if (appendQueryString(sb, delimiter, key, innerValue)) 143 | delimiter = '&'; 144 | } 145 | } else { 146 | if (appendQueryString(sb, delimiter, key, value)) 147 | delimiter = '&'; 148 | } 149 | } 150 | 151 | String output = sb.toString(); 152 | Logger.debug(TAG, "built uri: " + output); 153 | 154 | return output; 155 | } 156 | 157 | private boolean appendQueryString(StringBuilder sb, char delimiter, String key, Object value) { 158 | if (value == null) 159 | return false; 160 | 161 | try { 162 | sb.append(String.format("%1$c%2$s=%3$s", delimiter, key, 163 | URLEncoder.encode(value.toString(), "utf-8"))); 164 | } catch (UnsupportedEncodingException e) { 165 | e.printStackTrace(); 166 | return false; 167 | } 168 | 169 | return true; 170 | } 171 | 172 | public Object getRequestObject() { 173 | return mRequestObject; 174 | } 175 | 176 | } 177 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/codecs/MultipartFormDataEncoder.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery.codecs; 6 | 7 | import com.spoqa.battery.CodecUtils; 8 | import com.spoqa.battery.FieldNameTranslator; 9 | import com.spoqa.battery.Logger; 10 | import com.spoqa.battery.PlatformUtils; 11 | import com.spoqa.battery.RequestSerializer; 12 | import com.spoqa.battery.TypeAdapterCollection; 13 | import com.spoqa.battery.annotations.RequestBody; 14 | import com.spoqa.battery.exceptions.SerializationException; 15 | 16 | import java.io.ByteArrayOutputStream; 17 | import java.io.File; 18 | import java.io.FileInputStream; 19 | import java.io.FileNotFoundException; 20 | import java.io.IOException; 21 | import java.io.InputStream; 22 | import java.io.UnsupportedEncodingException; 23 | import java.lang.reflect.Field; 24 | import java.util.List; 25 | import java.util.UUID; 26 | 27 | public class MultipartFormDataEncoder implements RequestSerializer { 28 | 29 | private static final String MIME_TYPE = "multipart/form-data"; 30 | private static final String TAG = "MultipartFormDataEncoder"; 31 | 32 | private String mBoundary; 33 | private ByteArrayOutputStream mOutputStream; 34 | 35 | public MultipartFormDataEncoder() { 36 | mBoundary = String.format("----BatteryMultipart%1$s", UUID.randomUUID().toString()); 37 | } 38 | 39 | @Override 40 | public byte[] serializeObject(Object o, FieldNameTranslator translator, 41 | TypeAdapterCollection typeAdapters) throws SerializationException { 42 | mOutputStream = new ByteArrayOutputStream(); 43 | 44 | List fields = CodecUtils.getAnnotatedFields(null, RequestBody.class, o.getClass()); 45 | for (Field f : fields) { 46 | RequestBody annotation = f.getAnnotation(RequestBody.class); 47 | Class type = f.getType(); 48 | String localName = f.getName(); 49 | String foreignName; 50 | if (annotation.value().length() > 0) 51 | foreignName = annotation.value(); 52 | else 53 | foreignName = translator.localToRemote(localName); 54 | String value = ""; 55 | 56 | try { 57 | Object element = f.get(o); 58 | 59 | if (element == null) { 60 | continue; 61 | } else if (CodecUtils.isString(type)) { 62 | addPart(foreignName, (String) element); 63 | } else if (CodecUtils.isFloat(type)) { 64 | addPart(foreignName, Float.toString((Float) element)); 65 | } else if (CodecUtils.isDouble(type)) { 66 | addPart(foreignName, Double.toString((Double) element)); 67 | } else if (CodecUtils.isBoolean(type)) { 68 | addPart(foreignName, Boolean.toString((Boolean) element)); 69 | } else if (CodecUtils.isInteger(type)) { 70 | addPart(foreignName, Integer.toString((Integer) element)); 71 | } else if (CodecUtils.isLong(type)) { 72 | addPart(foreignName, Long.toString((Long) element)); 73 | } else if (type.isEnum()) { 74 | addPart(foreignName, element.toString()); 75 | } else if (CodecUtils.isList(type)) { 76 | int i = 0; 77 | for (Object innerElement : (List) element) { 78 | String nameWithIndex = String.format("%1$s[%2$d]", foreignName, i++); 79 | 80 | if (innerElement instanceof File) { 81 | File file = (File) innerElement; 82 | try { 83 | addPart(nameWithIndex, new FileInputStream(file), file.getAbsolutePath()); 84 | } catch (FileNotFoundException e) { 85 | Logger.warn(TAG, String.format("Could not find file %1$s", file.getAbsolutePath())); 86 | } 87 | } else if (innerElement instanceof InputStream) { 88 | addPart(nameWithIndex, (InputStream) innerElement, null); 89 | } else { 90 | addPart(nameWithIndex, innerElement.toString()); 91 | } 92 | } 93 | } else if (element instanceof InputStream) { 94 | addPart(foreignName, (InputStream) element, null); 95 | } else if (element instanceof File) { 96 | try { 97 | File file = (File) element; 98 | addPart(foreignName, new FileInputStream(file), file.getName()); 99 | } catch (FileNotFoundException e) { 100 | e.printStackTrace(); 101 | Logger.warn(TAG, String.format("Field %1$s is not serializable: %2$s", type.getName(), e.toString())); 102 | } 103 | } else if (typeAdapters.contains(type)) { 104 | addPart(foreignName, typeAdapters.query(type).encode(element)); 105 | } else { 106 | Logger.warn(TAG, String.format("Field %1$s is not serializable", type.getName())); 107 | } 108 | } catch (IllegalAccessException e) { 109 | e.printStackTrace(); 110 | continue; 111 | } 112 | } 113 | 114 | try { 115 | String end = String.format("--%1$s--\r\n", mBoundary); 116 | mOutputStream.write(end.getBytes("utf-8")); 117 | } catch (IOException e) { 118 | e.printStackTrace(); 119 | } 120 | 121 | return mOutputStream.toByteArray(); 122 | } 123 | 124 | private void addPart(String fieldName, String formData) { 125 | String header = "--%1$s\r\nContent-Disposition: form-data; name=\"%2$s\"\r\n\r\n"; 126 | 127 | try { 128 | mOutputStream.write(String.format(header, mBoundary, fieldName).getBytes("utf-8")); 129 | mOutputStream.write(formData.getBytes("utf-8")); 130 | mOutputStream.write("\r\n".getBytes("utf-8")); 131 | } catch (IOException e) { 132 | e.printStackTrace(); 133 | } 134 | } 135 | 136 | private void addPart(String fieldName, InputStream stream, String fileName) { 137 | byte[] buffer = new byte[16384]; 138 | 139 | if (fileName == null) 140 | fileName = ""; 141 | 142 | String mimeType = null; 143 | if (fileName.length() > 0) 144 | mimeType = PlatformUtils.getMimeType(new File(fileName)); 145 | 146 | if (mimeType == null) 147 | mimeType = "application/octet-stream"; 148 | 149 | String header; 150 | if (fileName.length() > 0) { 151 | header = String.format("--%1$s\r\nContent-Disposition: form-data; name=\"%2$s\"; filename=\"%3$s\"\r\n" + 152 | "Content-Type: %4$s\r\n\r\n", mBoundary, fieldName, fileName, mimeType); 153 | } else { 154 | header = String.format("--%1$s\r\nContent-Disposition: form-data; name=\"%2$s\"\r\n" + 155 | "Content-Type: %3$s\r\n\r\n", mBoundary, fieldName, mimeType); 156 | } 157 | 158 | try { 159 | mOutputStream.write(header.getBytes("utf-8")); 160 | int read; 161 | while ((read = stream.read(buffer)) > 0) 162 | mOutputStream.write(buffer, 0, read); 163 | mOutputStream.write("\r\n".getBytes("utf-8")); 164 | } catch (IOException e) { 165 | e.printStackTrace(); 166 | } 167 | } 168 | 169 | @Override 170 | public String serializationContentType() { 171 | return String.format("%1$s; boundary=%2$s", MIME_TYPE, mBoundary); 172 | } 173 | 174 | @Override 175 | public boolean supportsCompositeType() { 176 | return false; 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/android/AndroidRpcContext.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery.android; 6 | 7 | import android.content.Context; 8 | 9 | import com.android.volley.RequestQueue; 10 | import com.android.volley.Response; 11 | import com.android.volley.ServerError; 12 | import com.android.volley.VolleyError; 13 | import com.android.volley.toolbox.Volley; 14 | 15 | import com.spoqa.battery.CodecUtils; 16 | import com.spoqa.battery.Config; 17 | import com.spoqa.battery.PlatformUtils; 18 | import com.spoqa.battery.RpcContext; 19 | import com.spoqa.battery.FieldNameTranslator; 20 | import com.spoqa.battery.HttpRequest; 21 | import com.spoqa.battery.Logger; 22 | import com.spoqa.battery.ObjectBuilder; 23 | import com.spoqa.battery.OnResponse; 24 | import com.spoqa.battery.RequestFactory; 25 | import com.spoqa.battery.annotations.RpcObject; 26 | import com.spoqa.battery.exceptions.ContextException; 27 | import com.spoqa.battery.exceptions.DeserializationException; 28 | import com.spoqa.battery.exceptions.ResponseValidationException; 29 | import com.spoqa.battery.exceptions.RpcException; 30 | import com.spoqa.battery.exceptions.SerializationException; 31 | 32 | import java.io.UnsupportedEncodingException; 33 | 34 | import rx.Observable; 35 | import rx.subjects.PublishSubject; 36 | 37 | public class AndroidRpcContext extends RpcContext { 38 | 39 | static { 40 | /* register up */ 41 | Logger.registerLogger(new AndroidLogger()); 42 | PlatformUtils.registerPlatformUtils(new AndroidPlatformUtilsImpl()); 43 | } 44 | 45 | private static final String TAG = "AndroidExecutionContext"; 46 | 47 | private RequestQueue mRequestQueue; 48 | private Context mAndroidContext; 49 | 50 | public AndroidRpcContext(Context androidApplicationContext, RequestQueue requestQueue) { 51 | super(); 52 | mAndroidContext = androidApplicationContext; 53 | mRequestQueue = requestQueue; 54 | } 55 | 56 | public AndroidRpcContext(Context androidApplicationContext) { 57 | this(androidApplicationContext, 58 | Volley.newRequestQueue(androidApplicationContext, new OkHttpStack())); 59 | } 60 | 61 | public RequestQueue getRequestQueue() { 62 | return mRequestQueue; 63 | } 64 | 65 | public Context androidApplicationContext() { 66 | return mAndroidContext; 67 | } 68 | 69 | public void invokeAsync(final T rpcObject, final OnResponse onResponse) { 70 | invokeAsync(rpcObject, onResponse, mAndroidContext); 71 | } 72 | 73 | public void invokeAsync(final T rpcObject, final OnResponse onResponse, final Context currentContext) { 74 | HttpRequest request = null; 75 | try { 76 | request = RequestFactory.createRequest(this, rpcObject); 77 | } catch (SerializationException e) { 78 | onResponse.onFailure(e); 79 | return; 80 | } catch (ContextException e) { 81 | onResponse.onFailure(e.why()); 82 | return; 83 | } 84 | 85 | if (request == null) { 86 | Logger.error(TAG, "Could not make call due to error(s) while creating request object."); 87 | return; 88 | } 89 | 90 | final RpcObject rpcObjectDecl = rpcObject.getClass().getAnnotation(RpcObject.class); 91 | if (rpcObjectDecl.context() != RpcObject.NULL.class) { 92 | Class contextSpec = rpcObjectDecl.context(); 93 | if (!CodecUtils.isSubclassOf(contextSpec, RpcContext.class)) { 94 | Logger.error(TAG, String.format("Context attribute of RpcObject %1$s is not a " + 95 | "subclass of ExecutionContext", rpcObject.getClass().getName())); 96 | return; 97 | } 98 | if (getClass() != contextSpec) { 99 | Logger.error(TAG, String.format("RpcObject context mismatch. context: %1$s, " + 100 | "expected: %2$s", getClass().getName(), contextSpec.getName())); 101 | return; 102 | } 103 | } 104 | 105 | final FieldNameTranslator nameTranslator = request.getFieldNameTranslator(); 106 | Response.Listener onVolleyResponse = new Response.Listener() { 107 | @Override 108 | public void onResponse(ResponseDelegate s) { 109 | try { 110 | /* force content type if declared by RpcObject */ 111 | String contentType = rpcObjectDecl.expectedContentType(); 112 | if (contentType == null || contentType.length() == 0) 113 | contentType = s.contentType(); 114 | ObjectBuilder.build(contentType, s.data(), 115 | rpcObject, nameTranslator, getTypeAdapters()); 116 | 117 | if (getResponseValidator() != null) { 118 | try { 119 | Object responseObject = null; 120 | try { 121 | responseObject = CodecUtils.getResponseObject(null, rpcObject, false); 122 | } catch (RpcException e) { 123 | e.printStackTrace(); 124 | } 125 | if (responseObject == null) 126 | responseObject = rpcObject; 127 | getResponseValidator().validate(responseObject); 128 | } catch (ResponseValidationException e) { 129 | if (!dispatchErrorHandler(currentContext, e)) { 130 | onResponse.onFailure(e); 131 | return; 132 | } 133 | } 134 | } 135 | 136 | onResponse.onResponse(rpcObject); 137 | } catch (DeserializationException e) { 138 | if (!dispatchErrorHandler(currentContext, e)) 139 | onResponse.onFailure(e); 140 | } 141 | } 142 | }; 143 | 144 | Response.ErrorListener onVolleyErrorResponse = new Response.ErrorListener() { 145 | @Override 146 | public void onErrorResponse(VolleyError volleyError) { 147 | Logger.error(TAG, "Error while RPC call: " + volleyError.getMessage()); 148 | if (Config.DEBUG_DUMP_RESPONSE) { 149 | try { 150 | if (volleyError.networkResponse != null) 151 | Logger.error(TAG, "Error response: " + 152 | new String(volleyError.networkResponse.data, "utf-8")); 153 | } catch (UnsupportedEncodingException e) {} 154 | } 155 | 156 | Throwable e = volleyError.getCause(); 157 | if (e == null) { 158 | if (volleyError instanceof ServerError) { 159 | e = new RpcException("Server Error"); 160 | } else { 161 | e = new RpcException(volleyError.toString()); 162 | } 163 | } 164 | if (!dispatchErrorHandler(currentContext, volleyError) && 165 | !dispatchErrorHandler(currentContext, e)) 166 | onResponse.onFailure(volleyError); 167 | } 168 | }; 169 | 170 | VolleyRequest req = new VolleyRequest(request, onVolleyResponse, onVolleyErrorResponse); 171 | mRequestQueue.add(req); 172 | } 173 | 174 | public Observable invokeObservable(final T rpcObject) { 175 | return invokeObservable(rpcObject, mAndroidContext); 176 | } 177 | 178 | public Observable invokeObservable(final T rpcObject, Context currentContext) { 179 | final PublishSubject subject = PublishSubject.create(); 180 | 181 | invokeAsync(rpcObject, new OnResponse() { 182 | @Override 183 | public void onResponse(T responseBody) { 184 | subject.onNext(responseBody); 185 | subject.onCompleted(); 186 | } 187 | 188 | @Override 189 | public void onFailure(Throwable why) { 190 | subject.onError(why); 191 | } 192 | }, currentContext); 193 | 194 | return subject.asObservable(); 195 | } 196 | 197 | } 198 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/ReflectionCache.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery; 6 | 7 | import java.lang.annotation.Annotation; 8 | import java.lang.reflect.Field; 9 | import java.lang.reflect.Method; 10 | import java.util.HashMap; 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | public class ReflectionCache { 15 | 16 | private static class AnnotatedPropertyKey { 17 | public static AnnotatedPropertyKey instance = null; 18 | 19 | static { 20 | instance = new AnnotatedPropertyKey(); 21 | } 22 | 23 | public Class annotation; 24 | public Class baseClass; 25 | 26 | public AnnotatedPropertyKey() { 27 | } 28 | 29 | public AnnotatedPropertyKey(Class annotation, Class baseClass) { 30 | this.annotation = annotation; 31 | this.baseClass = baseClass; 32 | } 33 | 34 | @Override 35 | public int hashCode() { 36 | return annotation.hashCode() ^ baseClass.hashCode(); 37 | } 38 | 39 | @Override 40 | public boolean equals(Object other_) { 41 | if (!(other_ instanceof AnnotatedPropertyKey)) 42 | return false; 43 | 44 | AnnotatedPropertyKey other = (AnnotatedPropertyKey) other_; 45 | return other.annotation == annotation && other.baseClass == baseClass; 46 | } 47 | } 48 | 49 | private static class MemberAnnotationKey { 50 | public T member; 51 | public Class annotation; 52 | 53 | public MemberAnnotationKey(T member, Class annotation) { 54 | this.member = member; 55 | this.annotation = annotation; 56 | } 57 | 58 | @Override 59 | public int hashCode() { 60 | return member.hashCode() ^ annotation.hashCode(); 61 | } 62 | 63 | @Override 64 | public boolean equals(Object other_) { 65 | if (!(other_ instanceof MemberAnnotationKey)) 66 | return false; 67 | 68 | MemberAnnotationKey other = (MemberAnnotationKey) other_; 69 | return other.member == member && other.annotation == annotation; 70 | } 71 | } 72 | 73 | private static class FieldAnnotationKey extends MemberAnnotationKey { 74 | static public FieldAnnotationKey instance = null; 75 | 76 | static { 77 | instance = new FieldAnnotationKey(null, null); 78 | } 79 | 80 | public FieldAnnotationKey(Field field, Class annotation) { 81 | super(field, annotation); 82 | } 83 | } 84 | 85 | private static class MethodAnnotationKey extends MemberAnnotationKey { 86 | static public MethodAnnotationKey instance = null; 87 | 88 | static { 89 | instance = new MethodAnnotationKey(null, null); 90 | } 91 | 92 | public MethodAnnotationKey(Method method, Class annotation) { 93 | super(method, annotation); 94 | } 95 | } 96 | 97 | private Map> mAnnotatedFieldsCache; 98 | private Map> mAnnotatedGetterMethodsCache; 99 | private Map> mAnnotatedSetterMethodsCache; 100 | private Map> mFieldsCache; 101 | private Map> mGetterMethodsCache; 102 | private Map> mSetterMethodsCache; 103 | private Map mFieldAnnotationCache; 104 | private Map mMethodAnnotationCache; 105 | 106 | public ReflectionCache() { 107 | mAnnotatedFieldsCache = new HashMap>(); 108 | mAnnotatedGetterMethodsCache = new HashMap>(); 109 | mAnnotatedSetterMethodsCache = new HashMap>(); 110 | mFieldsCache = new HashMap>(); 111 | mGetterMethodsCache = new HashMap>(); 112 | mSetterMethodsCache = new HashMap>(); 113 | mFieldAnnotationCache = new HashMap(); 114 | mMethodAnnotationCache = new HashMap(); 115 | } 116 | 117 | public List queryCachedAnnotatedFields(Class annotation, 118 | Class baseClass) { 119 | AnnotatedPropertyKey key = AnnotatedPropertyKey.instance; 120 | key.annotation = annotation; 121 | key.baseClass = baseClass; 122 | 123 | if (mAnnotatedFieldsCache.containsKey(key)) 124 | return mAnnotatedFieldsCache.get(key); 125 | 126 | return null; 127 | } 128 | 129 | public List queryCachedAnnotatedGetterMethods(Class annotation, 130 | Class baseClass) { 131 | AnnotatedPropertyKey key = AnnotatedPropertyKey.instance; 132 | key.annotation = annotation; 133 | key.baseClass = baseClass; 134 | 135 | if (mAnnotatedGetterMethodsCache.containsKey(key)) 136 | return mAnnotatedGetterMethodsCache.get(key); 137 | 138 | return null; 139 | } 140 | 141 | public List queryCachedAnnotatedSetterMethods(Class annotation, 142 | Class baseClass) { 143 | AnnotatedPropertyKey key = AnnotatedPropertyKey.instance; 144 | key.annotation = annotation; 145 | key.baseClass = baseClass; 146 | 147 | if (mAnnotatedSetterMethodsCache.containsKey(key)) 148 | return mAnnotatedSetterMethodsCache.get(key); 149 | 150 | return null; 151 | } 152 | 153 | public List queryCachedFields(Class baseClass) { 154 | if (mFieldsCache.containsKey(baseClass)) 155 | return mFieldsCache.get(baseClass); 156 | 157 | return null; 158 | } 159 | 160 | public List queryCachedGetterMethods(Class baseClass) { 161 | if (mGetterMethodsCache.containsKey(baseClass)) 162 | return mGetterMethodsCache.get(baseClass); 163 | 164 | return null; 165 | } 166 | 167 | public List queryCachedSetterMethods(Class baseClass) { 168 | if (mSetterMethodsCache.containsKey(baseClass)) 169 | return mSetterMethodsCache.get(baseClass); 170 | 171 | return null; 172 | } 173 | 174 | public Annotation queryFieldAnnotation(Field field, Class annotationClass) { 175 | FieldAnnotationKey key = FieldAnnotationKey.instance; 176 | key.member = field; 177 | key.annotation = annotationClass; 178 | 179 | if (mFieldAnnotationCache.containsKey(key)) { 180 | return mFieldAnnotationCache.get(key); 181 | } 182 | 183 | return null; 184 | } 185 | 186 | public Annotation queryMethodAnnotation(Method method, Class annotationClass) { 187 | MethodAnnotationKey key = MethodAnnotationKey.instance; 188 | key.member = method; 189 | key.annotation = annotationClass; 190 | 191 | if (mFieldAnnotationCache.containsKey(key)) { 192 | return mFieldAnnotationCache.get(key); 193 | } 194 | 195 | return null; 196 | } 197 | 198 | public void cacheAnnotatedFields(Class annotation, Class baseClass, 199 | List fields) { 200 | mAnnotatedFieldsCache.put(new AnnotatedPropertyKey(annotation, baseClass), fields); 201 | } 202 | 203 | public void cacheAnnotatedGetterMethods(Class annotation, Class baseClass, 204 | List methods) { 205 | mAnnotatedGetterMethodsCache.put(new AnnotatedPropertyKey(annotation, baseClass), methods); 206 | } 207 | 208 | public void cacheAnnotatedSetterMethods(Class annotation, Class baseClass, 209 | List methods) { 210 | mAnnotatedSetterMethodsCache.put(new AnnotatedPropertyKey(annotation, baseClass), methods); 211 | } 212 | 213 | public void cacheFields(Class baseClass, List fields) { 214 | mFieldsCache.put(baseClass, fields); 215 | } 216 | 217 | public void cacheGetterMethods(Class baseClass, List methods) { 218 | mGetterMethodsCache.put(baseClass, methods); 219 | } 220 | 221 | public void cacheSetterMethods(Class baseClass, List methods) { 222 | mSetterMethodsCache.put(baseClass, methods); 223 | } 224 | 225 | public void cacheFieldAnnotation(Field field, Class annotationClass, Annotation a) { 226 | mFieldAnnotationCache.put(new FieldAnnotationKey(field, annotationClass), a); 227 | } 228 | 229 | public void cacheMethodAnnotation(Method method, Class annotationClass, Annotation a) { 230 | mMethodAnnotationCache.put(new MethodAnnotationKey(method, annotationClass), a); 231 | } 232 | 233 | public boolean containsFieldAnnotation(Field field, Class annotationClass) { 234 | FieldAnnotationKey key = FieldAnnotationKey.instance; 235 | key.member = field; 236 | key.annotation = annotationClass; 237 | 238 | return mFieldAnnotationCache.containsKey(key); 239 | } 240 | 241 | public boolean containsMethodAnnotation(Method method, Class annotationClass) { 242 | MethodAnnotationKey key = MethodAnnotationKey.instance; 243 | key.member = method; 244 | key.annotation = annotationClass; 245 | 246 | return mFieldAnnotationCache.containsKey(key); 247 | } 248 | 249 | } 250 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/RequestFactory.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery; 6 | 7 | import com.spoqa.battery.annotations.QueryString; 8 | import com.spoqa.battery.annotations.RpcObject; 9 | import com.spoqa.battery.annotations.Uri; 10 | import com.spoqa.battery.annotations.UriPath; 11 | import com.spoqa.battery.codecs.UrlEncodedFormEncoder; 12 | import com.spoqa.battery.exceptions.ContextException; 13 | import com.spoqa.battery.exceptions.SerializationException; 14 | 15 | import java.io.UnsupportedEncodingException; 16 | import java.lang.reflect.Field; 17 | import java.net.URLEncoder; 18 | import java.util.ArrayList; 19 | import java.util.HashMap; 20 | import java.util.List; 21 | import java.util.Map; 22 | 23 | public final class RequestFactory { 24 | private static final String TAG = "RequestFactory"; 25 | 26 | public static HttpRequest createRequest(RpcContext context, Object object) 27 | throws SerializationException, ContextException { 28 | /* validate current preprocessor context (if exists) */ 29 | if (context.getRequestPreprocessor() != null) 30 | context.getRequestPreprocessor().validateContext(object); 31 | 32 | RpcObject annotation = object.getClass().getAnnotation(RpcObject.class); 33 | if (annotation == null) { 34 | Logger.error(TAG, String.format("Attempted to create a request from non-RpcObject")); 35 | return null; 36 | } 37 | 38 | FieldNameTransformer remote, local; 39 | local = context.getLocalFieldNameTransformer(); 40 | remote = context.getRemoteFieldNameTransformer(); 41 | try { 42 | if (annotation.remoteName() != RpcObject.NULL.class) 43 | remote = (FieldNameTransformer) annotation.remoteName().newInstance(); 44 | if (annotation.localName() != RpcObject.NULL.class) 45 | local = (FieldNameTransformer) annotation.localName().newInstance(); 46 | } catch (InstantiationException e) { 47 | e.printStackTrace(); 48 | Logger.error(TAG, "Failed to create request."); 49 | return null; 50 | } catch (IllegalAccessException e) { 51 | e.printStackTrace(); 52 | Logger.error(TAG, "Failed to create request."); 53 | return null; 54 | } 55 | 56 | FieldNameTranslator nameTranslator = new FieldNameTranslator(remote, local); 57 | TypeAdapterCollection typeAdapters = context.getTypeAdapters(); 58 | 59 | Map parameters = new HashMap(); 60 | int method = annotation.method(); 61 | String uri = buildUri(context, object, annotation, parameters, nameTranslator); 62 | if (uri == null) { 63 | Logger.error(TAG, "Failed to create request."); 64 | return null; 65 | } 66 | 67 | HttpRequest request = new HttpRequest(method, uri); 68 | request.setNameTranslator(nameTranslator); 69 | request.putParameters(parameters); 70 | request.setRequestObject(object); 71 | 72 | /* set request body */ 73 | Class serializerCls = annotation.requestSerializer(); 74 | if (method == HttpRequest.Methods.POST || method == HttpRequest.Methods.PUT) { 75 | if (serializerCls == RpcObject.NULL.class && context.getRequestSerializer() == null) 76 | serializerCls = UrlEncodedFormEncoder.class; 77 | 78 | if (serializerCls != RpcObject.NULL.class) { 79 | try { 80 | RequestSerializer serializer = (RequestSerializer) serializerCls.newInstance(); 81 | request.putHeader(HttpRequest.HEADER_CONTENT_TYPE, serializer.serializationContentType()); 82 | request.setRequestBody(serializer.serializeObject(object, nameTranslator, typeAdapters)); 83 | } catch (InstantiationException e) { 84 | e.printStackTrace(); 85 | } catch (IllegalAccessException e) { 86 | e.printStackTrace(); 87 | } 88 | } else if (context.getRequestSerializer() != null) { 89 | RequestSerializer serializer = context.getRequestSerializer(); 90 | request.setContentType(serializer.serializationContentType()); 91 | request.setRequestBody(serializer.serializeObject(object, nameTranslator, typeAdapters)); 92 | } else { 93 | Logger.warn(TAG, String.format("Current RpcObject %1$s does not have " + 94 | "RequestSerializer specified.", object.getClass().getName())); 95 | } 96 | } 97 | 98 | /* apply custom request processing middleware */ 99 | if (context.getRequestPreprocessor() != null) 100 | context.getRequestPreprocessor().processHttpRequest(object, request); 101 | 102 | return request; 103 | } 104 | 105 | private static String buildUri(RpcContext context, Object object, RpcObject rpcObjectDecl, 106 | Map params, FieldNameTranslator translator) { 107 | String uri = null; 108 | 109 | /* Search for @Uri field */ 110 | List uriFields = CodecUtils.getAnnotatedFields(null, Uri.class, object.getClass()); 111 | if (uriFields != null && uriFields.size() > 0) { 112 | if (uriFields.size() > 1) { 113 | Logger.error(TAG, String.format("More than one Uri fields in object %1$s", 114 | object.getClass().getName())); 115 | return null; 116 | } 117 | Field f = uriFields.get(0); 118 | if (f.getType() != String.class) { 119 | Logger.error(TAG, String.format("Field %1$s must be String", f.getType().getName())); 120 | return null; 121 | } 122 | try { 123 | uri = f.get(object).toString(); 124 | } catch (IllegalAccessException e) { 125 | e.printStackTrace(); 126 | return null; 127 | } 128 | } 129 | 130 | if (uri == null) 131 | uri = rpcObjectDecl.uri(); 132 | 133 | if (uri == null || uri.length() == 0) { 134 | Logger.error(TAG, String.format("No URI supplied for object %1$s", object.getClass().getName())); 135 | return null; 136 | } 137 | 138 | if (!uri.startsWith("http://") && !uri.startsWith("https://")) { 139 | if (context.getDefaultUriPrefix() == null) { 140 | Logger.error(TAG, String.format("No URI prefix given.")); 141 | return null; 142 | } 143 | if (uri.startsWith("/")) 144 | uri = uri.substring(1); 145 | uri = context.getDefaultUriPrefix() + "/" + uri; 146 | } 147 | 148 | TypeAdapterCollection typeAdapters = context.getTypeAdapters(); 149 | 150 | /* Build REST URI fragment */ 151 | Class self = object.getClass(); 152 | List uriFragments = CodecUtils.getAnnotatedFields(null, UriPath.class, self); 153 | if (uriFragments != null && uriFragments.size() > 0) { 154 | Map fieldMap = new HashMap(); 155 | for (Field f : uriFragments) { 156 | UriPath uf = (UriPath) f.getAnnotation(UriPath.class); 157 | fieldMap.put(uf.value(), f); 158 | } 159 | 160 | List fieldList = new ArrayList(); 161 | for (int i = 1; i <= fieldMap.size(); ++i) { 162 | if (!fieldMap.containsKey(i)) { 163 | Logger.error(TAG, String.format("Positional argument %1$d not found in %2$s", i, 164 | self.getName())); 165 | return null; 166 | } else { 167 | fieldList.add(fieldMap.get(i)); 168 | } 169 | } 170 | 171 | Object[] parameters = new Object[fieldList.size()]; 172 | int i = 0; 173 | for (Field field : fieldList) { 174 | String paramName = field.getName(); 175 | try { 176 | Object fieldObject = field.get(object); 177 | Class fieldType = field.getType(); 178 | if (fieldObject == null || 179 | (!CodecUtils.isPrimitive(fieldType) && 180 | !CodecUtils.isString(fieldObject)) && 181 | !typeAdapters.contains(fieldType)) { 182 | Logger.error(TAG, String.format("Type '%1$s' of field '%2$s' could not be built into URI.", 183 | fieldType.getName(), paramName)); 184 | return null; 185 | } 186 | 187 | if (typeAdapters.contains(fieldType)) { 188 | try { 189 | parameters[i] = URLEncoder.encode(typeAdapters.query(fieldType).encode(fieldObject), "utf-8"); 190 | } catch (UnsupportedEncodingException e) {} 191 | } else if (fieldObject instanceof String) { 192 | try { 193 | parameters[i] = URLEncoder.encode((String) fieldObject, "utf-8"); 194 | } catch (UnsupportedEncodingException e) {} 195 | } else { 196 | parameters[i] = fieldObject; 197 | } 198 | } catch (IllegalAccessException e) { 199 | e.printStackTrace(); 200 | ++i; 201 | continue; 202 | } catch (SerializationException e) { 203 | e.printStackTrace(); 204 | ++i; 205 | continue; 206 | } 207 | ++i; 208 | } 209 | 210 | uri = String.format(uri, parameters); 211 | } 212 | 213 | /* append query string */ 214 | List queryStringParams = CodecUtils.getAnnotatedFields(null, QueryString.class, object.getClass()); 215 | for (Field field : queryStringParams) { 216 | String fieldName = field.getName(); 217 | Class fieldType = field.getType(); 218 | 219 | if (!CodecUtils.isString(fieldType) && 220 | !CodecUtils.isInteger(fieldType) && 221 | !CodecUtils.isBoolean(fieldType) && 222 | !CodecUtils.isFloat(fieldType) && 223 | !CodecUtils.isDouble(fieldType) && 224 | !CodecUtils.isLong(fieldType) && 225 | !CodecUtils.isList(fieldType) && 226 | !typeAdapters.contains(fieldType)) { 227 | Logger.error(TAG, String.format("Type '%1$s' of field '%2$s' could not be built into URI.", 228 | fieldType.getName(), field.getName())); 229 | continue; 230 | } 231 | 232 | /* override field name if optional value is supplied */ 233 | QueryString annotation = field.getAnnotation(QueryString.class); 234 | if (annotation.value().length() > 0) 235 | fieldName = annotation.value(); 236 | else 237 | fieldName = translator.localToRemote(fieldName); 238 | try { 239 | if (typeAdapters.contains(fieldType)) { 240 | Object obj = field.get(object); 241 | if (obj != null) 242 | params.put(fieldName, typeAdapters.query(fieldType).encode(field.get(object))); 243 | } else { 244 | params.put(fieldName, field.get(object)); 245 | } 246 | } catch (IllegalAccessException e) { 247 | e.printStackTrace(); 248 | } catch (SerializationException e) { 249 | e.printStackTrace(); 250 | } 251 | } 252 | 253 | return uri; 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/codecs/JsonCodec.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery.codecs; 6 | 7 | import com.spoqa.battery.CodecUtils; 8 | import com.spoqa.battery.FieldNameTranslator; 9 | import com.spoqa.battery.Logger; 10 | import com.spoqa.battery.RequestSerializer; 11 | import com.spoqa.battery.ResponseDeserializer; 12 | import com.spoqa.battery.TypeAdapterCollection; 13 | import com.spoqa.battery.annotations.RequestBody; 14 | import com.spoqa.battery.annotations.RequestObject; 15 | import com.spoqa.battery.exceptions.DeserializationException; 16 | import com.spoqa.battery.exceptions.SerializationException; 17 | 18 | import org.json.JSONArray; 19 | import org.json.JSONException; 20 | import org.json.JSONObject; 21 | 22 | import java.io.UnsupportedEncodingException; 23 | import java.lang.reflect.Field; 24 | import java.lang.reflect.InvocationTargetException; 25 | import java.lang.reflect.Method; 26 | import java.util.Iterator; 27 | import java.util.List; 28 | 29 | public class JsonCodec implements RequestSerializer, ResponseDeserializer { 30 | private static final String TAG = "JsonCodec"; 31 | 32 | public static final String MIME_TYPE = "application/json"; 33 | 34 | public JsonCodec() { 35 | 36 | } 37 | 38 | /* serializer */ 39 | 40 | @Override 41 | public byte[] serializeObject(Object o, FieldNameTranslator translator, 42 | TypeAdapterCollection typeAdapters) 43 | throws SerializationException { 44 | List fields = CodecUtils.getAnnotatedFields(null, RequestObject.class, o.getClass()); 45 | List getters = CodecUtils.getAnnotatedGetterMethods(null, RequestObject.class, o.getClass()); 46 | int count = fields.size() + getters.size(); 47 | boolean filterAnnotated = true; 48 | 49 | if (count > 1) { 50 | Logger.error(TAG, String.format("Object %1$s has more than one @RequestObject fields.", o.getClass().getName())); 51 | } else if (count == 1) { 52 | try { 53 | if (fields.size() == 1) 54 | o = fields.get(0).get(o); 55 | else if (getters.size() == 1) 56 | o = getters.get(0).invoke(o); 57 | filterAnnotated = false; 58 | } catch (IllegalAccessException e) { 59 | e.printStackTrace(); 60 | } catch (InvocationTargetException e) { 61 | e.printStackTrace(); 62 | } 63 | } 64 | 65 | JSONObject body = visitObject(o, translator, typeAdapters, filterAnnotated); 66 | 67 | if (body != null) { 68 | try { 69 | return body.toString().getBytes("utf-8"); 70 | } catch (UnsupportedEncodingException e) { 71 | e.printStackTrace(); 72 | } 73 | } 74 | 75 | return "null".getBytes(); 76 | } 77 | 78 | @Override 79 | public String serializationContentType() { 80 | return MIME_TYPE; 81 | } 82 | 83 | @Override 84 | public boolean supportsCompositeType() { 85 | return true; 86 | } 87 | 88 | private JSONObject visitObject(Object o, FieldNameTranslator translator, 89 | TypeAdapterCollection typeAdapters, boolean filterAnnotated) throws SerializationException { 90 | Iterable fields; 91 | Iterable getters; 92 | if (filterAnnotated) { 93 | fields = CodecUtils.getAnnotatedFields(null, RequestBody.class, o.getClass()); 94 | getters = CodecUtils.getAnnotatedGetterMethods(null, RequestBody.class, o.getClass()); 95 | } else { 96 | fields = CodecUtils.getAllFields(null, o.getClass()); 97 | getters = CodecUtils.getAllGetterMethods(null, o.getClass()); 98 | } 99 | 100 | JSONObject body = new JSONObject(); 101 | 102 | for (Field f : fields) { 103 | RequestBody annotation = f.getAnnotation(RequestBody.class); 104 | Class type = f.getType(); 105 | String localName = f.getName(); 106 | String foreignName; 107 | 108 | if (localName.equals("serialVersionUID")) { 109 | continue; 110 | } 111 | 112 | if (annotation != null && annotation.value().length() > 0) { 113 | foreignName = annotation.value(); 114 | } else { 115 | if (translator != null) 116 | foreignName = translator.localToRemote(localName); 117 | else 118 | foreignName = localName; 119 | } 120 | 121 | try { 122 | Object element = f.get(o); 123 | 124 | if (element == null) 125 | body.put(foreignName, null); 126 | else if (CodecUtils.isString(type)) 127 | body.put(foreignName, (String) element); 128 | else if (CodecUtils.isFloat(type)) 129 | body.put(foreignName, (Float) element); 130 | else if (CodecUtils.isDouble(type)) 131 | body.put(foreignName, (Double) element); 132 | else if (CodecUtils.isBoolean(type)) 133 | body.put(foreignName, (Boolean) element); 134 | else if (CodecUtils.isInteger(type)) 135 | body.put(foreignName, (Integer) element); 136 | else if (CodecUtils.isLong(type)) 137 | body.put(foreignName, (Long) element); 138 | else if (CodecUtils.isList(type)) 139 | body.put(foreignName, visitArray((List) element, translator, typeAdapters)); 140 | else if (type.isEnum()) 141 | body.put(foreignName, element.toString()); 142 | else if (typeAdapters.contains(element.getClass())) 143 | body.put(foreignName, typeAdapters.query(element.getClass()).encode(element)); 144 | else 145 | body.put(foreignName, visitObject(element, translator, typeAdapters, false)); 146 | } catch (IllegalAccessException e) { 147 | e.printStackTrace(); 148 | continue; 149 | } catch (JSONException e) { 150 | e.printStackTrace(); 151 | continue; 152 | } 153 | } 154 | 155 | for (Method m : getters) { 156 | RequestBody annotation = m.getAnnotation(RequestBody.class); 157 | Class type = m.getReturnType(); 158 | String localName = CodecUtils.normalizeGetterName(m.getName()); 159 | String foreignName; 160 | 161 | if (annotation != null && annotation.value().length() > 0) { 162 | foreignName = annotation.value(); 163 | } else { 164 | if (translator != null) 165 | foreignName = translator.localToRemote(localName); 166 | else 167 | foreignName = localName; 168 | } 169 | 170 | try { 171 | Object element = m.invoke(o); 172 | 173 | if (element == null) 174 | body.put(foreignName, null); 175 | else if (CodecUtils.isString(type)) 176 | body.put(foreignName, (String) element); 177 | else if (CodecUtils.isFloat(type)) 178 | body.put(foreignName, (Float) element); 179 | else if (CodecUtils.isDouble(type)) 180 | body.put(foreignName, (Double) element); 181 | else if (CodecUtils.isBoolean(type)) 182 | body.put(foreignName, (Boolean) element); 183 | else if (CodecUtils.isInteger(type)) 184 | body.put(foreignName, (Integer) element); 185 | else if (CodecUtils.isLong(type)) 186 | body.put(foreignName, (Long) element); 187 | else if (CodecUtils.isList(type)) 188 | body.put(foreignName, visitArray((List) element, translator, typeAdapters)); 189 | else if (type.isEnum()) 190 | body.put(foreignName, element.toString()); 191 | else if (typeAdapters.contains(element.getClass())) 192 | body.put(foreignName, typeAdapters.query(element.getClass()).encode(element)); 193 | else 194 | body.put(foreignName, visitObject(element, translator, typeAdapters, false)); 195 | } catch (IllegalAccessException e) { 196 | e.printStackTrace(); 197 | continue; 198 | } catch (JSONException e) { 199 | e.printStackTrace(); 200 | continue; 201 | } catch (InvocationTargetException e) { 202 | e.printStackTrace(); 203 | continue; 204 | } 205 | } 206 | 207 | if (body.length() > 0) 208 | return body; 209 | else 210 | return null; 211 | } 212 | 213 | private JSONArray visitArray(Iterable a, FieldNameTranslator translator, 214 | TypeAdapterCollection typeAdapters) throws SerializationException { 215 | JSONArray array = new JSONArray(); 216 | 217 | for (Object element : a) { 218 | Class type = element.getClass(); 219 | 220 | if (CodecUtils.isString(type)) 221 | array.put((String) element); 222 | else if (CodecUtils.isFloat(type)) 223 | array.put((Float) element); 224 | else if (CodecUtils.isDouble(type)) 225 | array.put((Double) element); 226 | else if (CodecUtils.isBoolean(type)) 227 | array.put((Boolean) element); 228 | else if (CodecUtils.isInteger(type)) 229 | array.put((Integer) element); 230 | else if (CodecUtils.isLong(type)) 231 | array.put((Long) element); 232 | else if (CodecUtils.isList(type)) 233 | array.put(visitArray((List) element, translator, typeAdapters)); 234 | else if (type.isEnum()) 235 | array.put(element.toString()); 236 | else if (typeAdapters.contains(element.getClass())) 237 | array.put(typeAdapters.query(element.getClass()).encode(element)); 238 | else 239 | array.put(visitObject(element, translator, typeAdapters, false)); 240 | } 241 | 242 | return array; 243 | } 244 | 245 | /* deserializer */ 246 | 247 | @Override 248 | public Object parseInput(String input) throws DeserializationException { 249 | try { 250 | return new JSONObject(input); 251 | } catch (JSONException e) { 252 | throw new DeserializationException(e); 253 | } 254 | } 255 | 256 | @Override 257 | public boolean containsChild(Object internalObject, String key) { 258 | assert internalObject instanceof JSONObject; 259 | 260 | final JSONObject jsonObject = (JSONObject) internalObject; 261 | return jsonObject.has(key); 262 | } 263 | 264 | @Override 265 | public Object queryObjectChild(Object internalObject, String key) { 266 | assert internalObject instanceof JSONObject; 267 | 268 | final JSONObject jsonObject = (JSONObject) internalObject; 269 | 270 | Object o = jsonObject.opt(key); 271 | if (o == JSONObject.NULL) 272 | return null; 273 | 274 | return o; 275 | } 276 | 277 | @Override 278 | public Iterable queryArrayChildren(Object internalArray) { 279 | assert internalArray instanceof JSONArray; 280 | 281 | final JSONArray jsonArray = (JSONArray) internalArray; 282 | 283 | return new Iterable() { 284 | private int index = 0; 285 | 286 | @Override 287 | public Iterator iterator() { 288 | return new Iterator() { 289 | @Override 290 | public boolean hasNext() { 291 | return index < jsonArray.length(); 292 | } 293 | 294 | @Override 295 | public Object next() { 296 | Object o = jsonArray.opt(index++); 297 | 298 | if (o == JSONObject.NULL) 299 | return null; 300 | 301 | return o; 302 | } 303 | 304 | @Override 305 | public void remove() { 306 | /* do not implement */ 307 | } 308 | }; 309 | } 310 | }; 311 | } 312 | 313 | @Override 314 | public boolean isObject(Class internalClass) { 315 | return internalClass == JSONObject.class; 316 | } 317 | 318 | @Override 319 | public boolean isArray(Class internalClass) { 320 | return internalClass == JSONArray.class; 321 | } 322 | 323 | @Override 324 | public String deserializationContentType() { 325 | return MIME_TYPE; 326 | } 327 | 328 | } 329 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/ObjectBuilder.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery; 6 | 7 | import com.spoqa.battery.annotations.Response; 8 | import com.spoqa.battery.codecs.JsonCodec; 9 | import com.spoqa.battery.exceptions.DeserializationException; 10 | import com.spoqa.battery.exceptions.IncompatibleTypeException; 11 | import com.spoqa.battery.exceptions.MissingFieldException; 12 | import com.spoqa.battery.exceptions.RpcException; 13 | 14 | import java.lang.reflect.Field; 15 | import java.lang.reflect.InvocationTargetException; 16 | import java.lang.reflect.Method; 17 | import java.lang.reflect.ParameterizedType; 18 | import java.lang.reflect.Type; 19 | import java.lang.reflect.TypeVariable; 20 | import java.util.ArrayList; 21 | import java.util.HashMap; 22 | import java.util.List; 23 | import java.util.Map; 24 | import java.util.NoSuchElementException; 25 | 26 | public final class ObjectBuilder { 27 | private static final String TAG = "ObjectBuilder"; 28 | 29 | private static Map sDeserializerMap; 30 | 31 | static { 32 | sDeserializerMap = new HashMap(); 33 | registerDeserializer(new JsonCodec()); 34 | } 35 | 36 | public static void registerDeserializer(ResponseDeserializer deserializer) { 37 | try { 38 | sDeserializerMap.put(deserializer.deserializationContentType(), deserializer); 39 | } catch (Exception e) { 40 | Logger.error(TAG, "Could not register deserializer class"); 41 | e.printStackTrace(); 42 | } 43 | } 44 | 45 | public static void build(String contentType, String input, Object object, 46 | FieldNameTranslator translator, TypeAdapterCollection typeAdapters) 47 | throws DeserializationException { 48 | String mime = extractMime(contentType); 49 | 50 | if (Config.DEBUG_DUMP_RESPONSE) { 51 | Logger.debug(TAG, "Mime: " + mime); 52 | Logger.debug(TAG, "Response: " + input); 53 | } 54 | 55 | if (!sDeserializerMap.containsKey(mime)) { 56 | RpcException e = new RpcException(String.format("No deserializer associated with MIME type %1$s", mime)); 57 | throw new DeserializationException(e); 58 | } 59 | 60 | ReflectionCache cache = new ReflectionCache(); 61 | 62 | try { 63 | CodecUtils.ResponseWithTypeParameters rt = CodecUtils.getResponseObject(cache, object, true); 64 | if (rt == null) { 65 | deserializeObject(cache, sDeserializerMap.get(mime), input, object, 66 | translator, typeAdapters, true, null); 67 | } else { 68 | deserializeObject(cache, sDeserializerMap.get(mime), input, rt.object, 69 | translator, typeAdapters, false, rt.typeVariables); 70 | } 71 | } catch (RpcException e) { 72 | throw new DeserializationException(e); 73 | } 74 | } 75 | 76 | private static String extractMime(String contentType) { 77 | String[] parts = contentType.split(";"); 78 | if (parts.length == 0) 79 | return null; 80 | return parts[0].trim(); 81 | } 82 | 83 | private static void deserializeObject(ReflectionCache cache, ResponseDeserializer deserializer, 84 | String input, Object object, FieldNameTranslator translator, 85 | TypeAdapterCollection typeAdapters, boolean filterByAnnotation, 86 | Type[] genericTypes) 87 | throws DeserializationException { 88 | /* Let's assume the root element is always an object */ 89 | Object internalObject = deserializer.parseInput(input); 90 | 91 | visitObject(cache, deserializer, internalObject, object, 92 | translator, typeAdapters, filterByAnnotation, genericTypes); 93 | } 94 | 95 | private static void visitObject(ReflectionCache cache, ResponseDeserializer deserializer, 96 | Object internalObject, Object dest, FieldNameTranslator translator, 97 | TypeAdapterCollection typeAdapters, 98 | boolean filterByAnnotation, Type[] genericTypes) 99 | throws DeserializationException { 100 | List fields; 101 | List setters; 102 | Class clazz = dest.getClass(); 103 | 104 | if (filterByAnnotation) { 105 | fields = CodecUtils.getAnnotatedFields(cache, Response.class, clazz); 106 | setters = CodecUtils.getAnnotatedSetterMethods(cache, Response.class, clazz); 107 | } else { 108 | fields = CodecUtils.getAllFields(cache, clazz); 109 | setters = CodecUtils.getAllSetterMethods(cache, clazz); 110 | } 111 | 112 | TypeVariable tvs[] = clazz.getTypeParameters(); 113 | try { 114 | /* search fields */ 115 | for (Field f : fields) { 116 | String fieldName = f.getName(); 117 | String docName = null; 118 | boolean explicit = false; 119 | boolean hasValue = false; 120 | Class fieldType = f.getType(); 121 | 122 | Type genericType = f.getGenericType(); 123 | if (genericType instanceof TypeVariable && genericTypes != null && 124 | genericTypes.length > 0 && tvs.length > 0) { 125 | for (int i = 0; i < tvs.length; ++i) { 126 | if (genericType.equals(tvs[i])) { 127 | if (genericTypes[i] instanceof ParameterizedType) { 128 | ParameterizedType inner = (ParameterizedType) genericTypes[i]; 129 | Type[] innerTypeArgs = inner.getActualTypeArguments(); 130 | Type[] ts = new Type[tvs.length + innerTypeArgs.length]; 131 | for (int j = 0; j < genericTypes.length; ++j) 132 | ts[j] = genericTypes[j]; 133 | for (int j = 0; j < innerTypeArgs.length; ++j) 134 | ts[j + genericTypes.length] = innerTypeArgs[j]; 135 | genericTypes = ts; 136 | fieldType = (Class) inner.getRawType(); 137 | } else { 138 | fieldType = (Class) genericTypes[i]; 139 | } 140 | break; 141 | } 142 | } 143 | } 144 | 145 | if (Config.DEBUG_DUMP_RESPONSE) { 146 | Logger.debug(TAG, "read field " + fieldName); 147 | } 148 | 149 | Response annotation; 150 | if (cache.containsFieldAnnotation(f, Response.class)) { 151 | annotation = (Response) cache.queryFieldAnnotation(f, Response.class); 152 | } else { 153 | annotation = f.getAnnotation(Response.class); 154 | cache.cacheFieldAnnotation(f, Response.class, annotation); 155 | } 156 | 157 | if (annotation != null) { 158 | if (annotation.value().length() > 0) { 159 | docName = annotation.value(); 160 | explicit = true; 161 | } 162 | } 163 | 164 | if (docName == null) 165 | docName = translator.localToRemote(fieldName); 166 | 167 | /* check for field names */ 168 | Object value = null; 169 | if (explicit && docName.contains(".")) { 170 | try { 171 | value = findChild(deserializer, internalObject, docName); 172 | hasValue = true; 173 | } catch (NoSuchElementException e) { 174 | value = null; 175 | } 176 | } else if (internalObject != null) { 177 | value = deserializer.queryObjectChild(internalObject, docName); 178 | hasValue = deserializer.containsChild(internalObject, docName); 179 | } 180 | 181 | if (!explicit && !hasValue) { 182 | /* fall back to the untransformed name */ 183 | if (deserializer.containsChild(internalObject, fieldName)) { 184 | value = deserializer.queryObjectChild(internalObject, fieldName); 185 | hasValue = true; 186 | } 187 | } 188 | 189 | if (annotation != null && annotation.required() && !hasValue) { 190 | /* check for mandatory field */ 191 | throw new DeserializationException(new MissingFieldException(f.getName())); 192 | } 193 | 194 | if (hasValue) { 195 | if (internalObject == null || value == null) { 196 | f.set(dest, null); 197 | } else if (typeAdapters.contains(fieldType) && 198 | CodecUtils.isBuiltIn(value.getClass())) { 199 | TypeAdapter codec = typeAdapters.query(fieldType); 200 | f.set(dest, codec.decode(value.toString())); 201 | } else if (CodecUtils.isString(fieldType)) { 202 | f.set(dest, value.toString()); 203 | } else if (CodecUtils.isIntegerPrimitive(fieldType)) { 204 | f.setInt(dest, CodecUtils.parseInteger(fieldName, value)); 205 | } else if (CodecUtils.isIntegerBoxed(fieldType)) { 206 | f.set(dest, CodecUtils.parseInteger(fieldName, value)); 207 | } else if (CodecUtils.isLongPrimitive(fieldType)) { 208 | f.setLong(dest, CodecUtils.parseLong(fieldName, value)); 209 | } else if (CodecUtils.isLongBoxed(fieldType)) { 210 | f.set(dest, CodecUtils.parseLong(fieldName, value)); 211 | } else if (CodecUtils.isList(fieldType)) { 212 | if (fieldType != List.class && fieldType != ArrayList.class) { 213 | Logger.error(TAG, String.format("field '%1$s' is not ArrayList or its superclass.", 214 | fieldName)); 215 | continue; 216 | } 217 | if (!deserializer.isArray(value.getClass())) { 218 | Logger.error(TAG, String.format("internal class of '%1$s' is not an array", 219 | fieldName)); 220 | continue; 221 | } 222 | List newList = ArrayList.class.newInstance(); 223 | visitArray(cache, deserializer, value, newList, 224 | CodecUtils.getGenericTypeOfField(dest.getClass(), f.getName(), genericTypes), 225 | translator, typeAdapters); 226 | f.set(dest, newList); 227 | } else if (CodecUtils.isMap(fieldType)) { 228 | Map newMap = (Map) fieldType.newInstance(); 229 | visitMap(cache, deserializer, value, newMap); 230 | f.set(dest, newMap); 231 | } else if (CodecUtils.isBooleanPrimitive(fieldType)) { 232 | f.setBoolean(dest, CodecUtils.parseBoolean(fieldName, value)); 233 | } else if (CodecUtils.isBooleanBoxed(fieldType)) { 234 | f.set(dest, CodecUtils.parseBoolean(fieldName, value)); 235 | } else if (CodecUtils.isFloatPrimitive(fieldType)) { 236 | f.setFloat(dest, CodecUtils.parseFloat(fieldName, value)); 237 | } else if (CodecUtils.isFloatBoxed(fieldType)) { 238 | f.set(dest, CodecUtils.parseFloat(fieldName, value)); 239 | } else if (CodecUtils.isDoublePrimitive(fieldType)) { 240 | f.setDouble(dest, CodecUtils.parseDouble(fieldName, value)); 241 | } else if (CodecUtils.isDoubleBoxed(fieldType)) { 242 | f.set(dest, CodecUtils.parseDouble(fieldName, value)); 243 | } else if (fieldType.isEnum()) { 244 | f.set(dest, CodecUtils.parseEnum(fieldType, value.toString())); 245 | } else { 246 | if (!CodecUtils.shouldBeExcluded(fieldType)) { 247 | /* or it should be a POJO... */ 248 | Type[] typeArguments = null; 249 | if (genericType instanceof ParameterizedType) { 250 | ParameterizedType pt = (ParameterizedType) genericType; 251 | typeArguments = pt.getActualTypeArguments(); 252 | } 253 | 254 | Object newObject = fieldType.newInstance(); 255 | visitObject(cache, deserializer, value, newObject, translator, 256 | typeAdapters, false, typeArguments); 257 | f.set(dest, newObject); 258 | } 259 | } 260 | } else { 261 | if (!CodecUtils.isPrimitive(fieldType)) 262 | f.set(dest, null); 263 | } 264 | } 265 | 266 | /* search methods */ 267 | for (Method m : setters) { 268 | String fieldName = CodecUtils.normalizeSetterName(m.getName()); 269 | String docName = null; 270 | boolean explicit = false; 271 | boolean hasValue = false; 272 | Class fieldType = m.getParameterTypes()[0]; 273 | 274 | Type genericType = m.getGenericParameterTypes()[0]; 275 | if (genericType instanceof TypeVariable && genericTypes != null && 276 | genericTypes.length > 0 && tvs.length > 0) { 277 | for (int i = 0; i < tvs.length; ++i) { 278 | if (genericType.equals(tvs[i])) { 279 | fieldType = (Class) genericTypes[i]; 280 | break; 281 | } 282 | } 283 | } 284 | 285 | if (Config.DEBUG_DUMP_RESPONSE) { 286 | Logger.debug(TAG, "read method " + fieldName); 287 | } 288 | 289 | Response annotation; 290 | if (cache.containsMethodAnnotation(m, Response.class)) { 291 | annotation = (Response) cache.queryMethodAnnotation(m, Response.class); 292 | } else { 293 | annotation = m.getAnnotation(Response.class); 294 | cache.cacheMethodAnnotation(m, Response.class, annotation); 295 | } 296 | 297 | if (annotation != null) { 298 | if (annotation.value().length() > 0) { 299 | docName = annotation.value(); 300 | explicit = true; 301 | } 302 | } 303 | 304 | if (docName == null) 305 | docName = translator.localToRemote(fieldName); 306 | 307 | /* check for field names */ 308 | Object value = null; 309 | if (explicit && docName.contains(".")) { 310 | try { 311 | value = findChild(deserializer, internalObject, docName); 312 | hasValue = true; 313 | } catch (NoSuchElementException e) { 314 | value = null; 315 | } 316 | } else if (internalObject != null) { 317 | value = deserializer.queryObjectChild(internalObject, docName); 318 | hasValue = deserializer.containsChild(internalObject, docName); 319 | } 320 | 321 | if (!explicit && !hasValue) { 322 | /* fall back to the untransformed name */ 323 | if (deserializer.containsChild(internalObject, fieldName)) { 324 | value = deserializer.queryObjectChild(internalObject, fieldName); 325 | hasValue = true; 326 | } 327 | } 328 | 329 | if (annotation != null && annotation.required() && !hasValue) { 330 | /* check for mandatory field */ 331 | throw new DeserializationException(new MissingFieldException(fieldName)); 332 | } 333 | 334 | if (hasValue) { 335 | if (internalObject == null || value == null) { 336 | //m.invoke(dest, null); 337 | } else if (typeAdapters.contains(fieldType) && 338 | CodecUtils.isBuiltIn(value.getClass())) { 339 | TypeAdapter codec = typeAdapters.query(fieldType); 340 | m.invoke(dest, codec.decode(value.toString())); 341 | } else if (CodecUtils.isString(fieldType)) { 342 | m.invoke(dest, value.toString()); 343 | } else if (CodecUtils.isInteger(fieldType)) { 344 | m.invoke(dest, CodecUtils.parseInteger(fieldName, value)); 345 | } else if (CodecUtils.isLong(fieldType)) { 346 | m.invoke(dest, CodecUtils.parseLong(fieldName, value)); 347 | } else if (CodecUtils.isList(fieldType)) { 348 | if (fieldType != List.class && fieldType != ArrayList.class) { 349 | Logger.error(TAG, String.format("argument of method '%1$s' is not " + 350 | "ArrayList or its superclass.", 351 | fieldName)); 352 | continue; 353 | } 354 | if (!deserializer.isArray(value.getClass())) { 355 | Logger.error(TAG, String.format("internal class of '%1$s' is not an array", 356 | fieldName)); 357 | continue; 358 | } 359 | List newList = ArrayList.class.newInstance(); 360 | visitArray(cache, deserializer, value, newList, 361 | CodecUtils.getGenericTypeOfMethod(dest.getClass(), m.getName(), List.class), 362 | translator, typeAdapters); 363 | m.invoke(dest, newList); 364 | } else if (CodecUtils.isMap(fieldType)) { 365 | Map newMap = (Map) fieldType.newInstance(); 366 | visitMap(cache, deserializer, value, newMap); 367 | m.invoke(dest, newMap); 368 | } else if (CodecUtils.isBoolean(fieldType)) { 369 | m.invoke(dest, CodecUtils.parseBoolean(fieldName, value)); 370 | } else if (CodecUtils.isFloat(fieldType)) { 371 | m.invoke(dest, CodecUtils.parseFloat(fieldName, value)); 372 | } else if (CodecUtils.isDouble(fieldType)) { 373 | m.invoke(dest, CodecUtils.parseDouble(fieldName, value)); 374 | } else if (fieldType.isEnum()) { 375 | m.invoke(dest, CodecUtils.parseEnum(fieldType, value.toString())); 376 | } else { 377 | if (!CodecUtils.shouldBeExcluded(fieldType)) { 378 | /* or it should be a POJO... */ 379 | Type[] typeArguments = null; 380 | if (genericType instanceof ParameterizedType) { 381 | ParameterizedType pt = (ParameterizedType) genericType; 382 | typeArguments = pt.getActualTypeArguments(); 383 | } 384 | 385 | Object newObject = fieldType.newInstance(); 386 | visitObject(cache, deserializer, value, newObject, translator, 387 | typeAdapters, false, typeArguments); 388 | m.invoke(dest, newObject); 389 | } 390 | } 391 | } 392 | } 393 | } catch (Exception e) { 394 | if (BuildConfig.DEBUG) 395 | e.printStackTrace(); 396 | throw new DeserializationException(e); 397 | } catch (IncompatibleTypeException e) { 398 | if (BuildConfig.DEBUG) 399 | e.printStackTrace(); 400 | throw new DeserializationException(e); 401 | } 402 | } 403 | 404 | private static void visitArray(ReflectionCache cache, 405 | ResponseDeserializer deserializer, Object internalArray, 406 | List output, Class innerType, 407 | FieldNameTranslator translator, 408 | TypeAdapterCollection typeAdapters) throws DeserializationException { 409 | try { 410 | Method add = List.class.getDeclaredMethod("add", Object.class); 411 | Integer index = 0; 412 | 413 | for (Object element : deserializer.queryArrayChildren(internalArray)) { 414 | if (element == null) { 415 | add.invoke(output, (Object) null); 416 | } else if (CodecUtils.isList(innerType)) { 417 | /* TODO implement nested list */ 418 | } else if (CodecUtils.isMap(innerType)) { 419 | /* TODO implement nested map */ 420 | } else if (deserializer.isObject(element.getClass())) { 421 | Object o = innerType.newInstance(); 422 | visitObject(cache, deserializer, element, o, translator, typeAdapters, false, null); 423 | add.invoke(output, o); 424 | } else { 425 | Object newElem = element; 426 | try { 427 | if (typeAdapters.contains(innerType)) 428 | newElem = typeAdapters.query(innerType).decode(element.toString()); 429 | else if (CodecUtils.isString(innerType)) 430 | newElem = CodecUtils.parseString(element); 431 | else if (CodecUtils.isInteger(innerType)) 432 | newElem = CodecUtils.parseInteger(index.toString(), element); 433 | else if (CodecUtils.isBoolean(innerType)) 434 | newElem = CodecUtils.parseBoolean(index.toString(), element); 435 | else if (CodecUtils.isDouble(innerType)) 436 | newElem = CodecUtils.parseDouble(index.toString(), element); 437 | else if (CodecUtils.isFloat(innerType)) 438 | newElem = CodecUtils.parseFloat(index.toString(), element); 439 | else if (CodecUtils.isLong(innerType)) 440 | newElem = CodecUtils.parseLong(index.toString(), element); 441 | } catch (IncompatibleTypeException e) { 442 | throw new DeserializationException(e); 443 | } 444 | 445 | add.invoke(output, newElem); 446 | 447 | ++index; 448 | } 449 | } 450 | } catch (NoSuchMethodException e) { 451 | // there's no List without add() 452 | } catch (InvocationTargetException e) { 453 | throw new DeserializationException(e); 454 | } catch (IllegalAccessException e) { 455 | throw new DeserializationException(e); 456 | } catch (InstantiationException e) { 457 | throw new DeserializationException(e); 458 | } 459 | } 460 | 461 | private static void visitMap(ReflectionCache cache, ResponseDeserializer deserializer, 462 | Object internalObject, Map m) throws DeserializationException { 463 | /* TODO implement deserialization into Map */ 464 | } 465 | 466 | private static Object findChild(ResponseDeserializer deserializer, Object internalObject, 467 | String path) throws NoSuchElementException { 468 | String[] frags = path.split("\\."); 469 | 470 | // Return one child if the key's not meant to be a path 471 | if (deserializer.containsChild(internalObject, path)) 472 | return deserializer.queryObjectChild(internalObject, path); 473 | 474 | int i = 0; 475 | for (String frag : frags) { 476 | if (++i == frags.length) { 477 | // if the last object 478 | if (!deserializer.containsChild(internalObject, frag)) 479 | throw new NoSuchElementException(); 480 | return deserializer.queryObjectChild(internalObject, frag); 481 | } else { 482 | internalObject = deserializer.queryObjectChild(internalObject, frag); 483 | if (internalObject == null) 484 | break; 485 | } 486 | } 487 | 488 | throw new NoSuchElementException(); 489 | } 490 | 491 | } 492 | -------------------------------------------------------------------------------- /src/main/java/com/spoqa/battery/CodecUtils.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-2015 Spoqa, All Rights Reserved. 3 | */ 4 | 5 | package com.spoqa.battery; 6 | 7 | import com.spoqa.battery.annotations.Response; 8 | import com.spoqa.battery.annotations.ResponseObject; 9 | import com.spoqa.battery.exceptions.IncompatibleTypeException; 10 | import com.spoqa.battery.exceptions.RpcException; 11 | 12 | import java.lang.annotation.Annotation; 13 | import java.lang.reflect.Field; 14 | import java.lang.reflect.GenericDeclaration; 15 | import java.lang.reflect.Method; 16 | import java.lang.reflect.ParameterizedType; 17 | import java.lang.reflect.Type; 18 | import java.lang.reflect.TypeVariable; 19 | import java.util.ArrayList; 20 | import java.util.HashMap; 21 | import java.util.LinkedList; 22 | import java.util.List; 23 | import java.util.Map; 24 | 25 | public final class CodecUtils { 26 | public static final String TAG = "CodecUtils"; 27 | 28 | private static final Class PRIMITIVE_TYPE_STRING = String.class; 29 | private static final Class PRIMITIVE_TYPE_INTEGER = int.class; 30 | private static final Class PRIMITIVE_TYPE_INTEGER_BOXED = Integer.class; 31 | private static final Class PRIMITIVE_TYPE_LONG = long.class; 32 | private static final Class PRIMITIVE_TYPE_LONG_BOXED = Long.class; 33 | private static final Class PRIMITIVE_TYPE_FLOAT = float.class; 34 | private static final Class PRIMITIVE_TYPE_FLOAT_BOXED = Float.class; 35 | private static final Class PRIMITIVE_TYPE_DOUBLE = double.class; 36 | private static final Class PRIMITIVE_TYPE_DOUBLE_BOXED = Double.class; 37 | private static final Class PRIMITIVE_TYPE_BOOLEAN = boolean.class; 38 | private static final Class PRIMITIVE_TYPE_BOOLEAN_BOXED = Boolean.class; 39 | private static final Class PRIMITIVE_TYPE_LIST = List.class; 40 | private static final Class PRIMITIVE_TYPE_MAP = Map.class; 41 | 42 | private static String[] EXCLUDED_OBJECT_PACKAGE_PREFIXES = { 43 | "java.", 44 | "android." 45 | }; 46 | 47 | public static Iterable> traverseObject( 48 | ReflectionCache cache, Class annotationFilter, Object o) { 49 | List> list = new ArrayList>(); 50 | 51 | for (Field field : getAllFields(cache, o.getClass())) { 52 | if (annotationFilter != null && field.getAnnotation(annotationFilter) == null) { 53 | /* if annotationFilter is given, filter by annotations */ 54 | continue; 55 | } 56 | 57 | try { 58 | list.add(new KeyValuePair(field.getName(), field.get(o))); 59 | } catch (IllegalAccessException e) { 60 | Logger.error(TAG, e.toString()); 61 | } 62 | } 63 | 64 | return list; 65 | } 66 | 67 | public static boolean implements_(Class clazz, Class what) { 68 | for (Class interface_ : clazz.getInterfaces()) { 69 | if (interface_ == what) 70 | return true; 71 | } 72 | 73 | return false; 74 | } 75 | 76 | public static boolean isSubclassOf(Class clazz, Class what) { 77 | while (clazz != null) { 78 | if (clazz == what) 79 | return true; 80 | 81 | clazz = clazz.getSuperclass(); 82 | } 83 | 84 | return false; 85 | } 86 | 87 | public static boolean isString(Object object) { 88 | return isSubclassOf(object.getClass(), PRIMITIVE_TYPE_STRING); 89 | } 90 | 91 | public static boolean isString(Class clazz) { 92 | return isSubclassOf(clazz, PRIMITIVE_TYPE_STRING); 93 | } 94 | 95 | public static boolean isInteger(Class clazz) { 96 | return clazz == PRIMITIVE_TYPE_INTEGER || 97 | clazz == PRIMITIVE_TYPE_INTEGER_BOXED; 98 | } 99 | 100 | public static boolean isIntegerPrimitive(Class clazz) { 101 | return clazz == PRIMITIVE_TYPE_INTEGER; 102 | } 103 | 104 | public static boolean isIntegerBoxed(Class clazz) { 105 | return clazz == PRIMITIVE_TYPE_INTEGER_BOXED; 106 | } 107 | 108 | public static boolean isLong(Class clazz) { 109 | return clazz == PRIMITIVE_TYPE_LONG || 110 | clazz == PRIMITIVE_TYPE_LONG_BOXED; 111 | } 112 | 113 | public static boolean isLongPrimitive(Class clazz) { 114 | return clazz == PRIMITIVE_TYPE_LONG; 115 | } 116 | 117 | public static boolean isLongBoxed(Class clazz) { 118 | return clazz == PRIMITIVE_TYPE_LONG_BOXED; 119 | } 120 | 121 | public static boolean isFloat(Class clazz) { 122 | return clazz == PRIMITIVE_TYPE_FLOAT || 123 | clazz == PRIMITIVE_TYPE_FLOAT_BOXED; 124 | } 125 | 126 | public static boolean isFloatPrimitive(Class clazz) { 127 | return clazz == PRIMITIVE_TYPE_FLOAT; 128 | } 129 | 130 | public static boolean isFloatBoxed(Class clazz) { 131 | return clazz == PRIMITIVE_TYPE_FLOAT_BOXED; 132 | } 133 | 134 | public static boolean isDouble(Class clazz) { 135 | return clazz == PRIMITIVE_TYPE_DOUBLE || 136 | clazz == PRIMITIVE_TYPE_DOUBLE_BOXED; 137 | } 138 | 139 | public static boolean isDoublePrimitive(Class clazz) { 140 | return clazz == PRIMITIVE_TYPE_DOUBLE; 141 | } 142 | 143 | public static boolean isDoubleBoxed(Class clazz) { 144 | return clazz == PRIMITIVE_TYPE_DOUBLE_BOXED; 145 | } 146 | 147 | public static boolean isBoolean(Class clazz) { 148 | return clazz == PRIMITIVE_TYPE_BOOLEAN || 149 | clazz == PRIMITIVE_TYPE_BOOLEAN_BOXED; 150 | } 151 | 152 | public static boolean isBooleanPrimitive(Class clazz) { 153 | return clazz == PRIMITIVE_TYPE_BOOLEAN; 154 | } 155 | 156 | public static boolean isBooleanBoxed(Class clazz) { 157 | return clazz == PRIMITIVE_TYPE_BOOLEAN_BOXED; 158 | } 159 | 160 | public static boolean isList(Class clazz) { 161 | return implements_(clazz, PRIMITIVE_TYPE_LIST) || clazz == PRIMITIVE_TYPE_LIST || clazz == ArrayList.class; 162 | } 163 | 164 | public static boolean isMap(Class clazz) { 165 | return implements_(clazz, PRIMITIVE_TYPE_MAP) || clazz == PRIMITIVE_TYPE_MAP || clazz == HashMap.class; 166 | } 167 | 168 | public static boolean isPrimitive(Class clazz) { 169 | if (isInteger(clazz) || isLong(clazz) || isBoolean(clazz) || 170 | isFloat(clazz) || isDouble(clazz)) 171 | return true; 172 | 173 | return false; 174 | } 175 | 176 | public static boolean isBuiltIn(Class clazz) { 177 | if (isString(clazz) || isPrimitive(clazz)) 178 | return true; 179 | 180 | return false; 181 | } 182 | 183 | public static Class getGenericTypeOfField(Class clazz, String fieldName, Type[] genericTypes) { 184 | try { 185 | Field field = clazz.getField(fieldName); 186 | Type genericType = field.getGenericType(); 187 | Class declaringType = field.getType(); 188 | TypeVariable[] tvs = clazz.getTypeParameters(); 189 | ParameterizedType type = null; 190 | if (genericType instanceof TypeVariable && genericTypes != null && genericTypes.length > 0 && 191 | tvs.length > 0) { 192 | for (int i = 0; i < tvs.length; ++i) { 193 | if (genericType.equals(tvs[i])) { 194 | if (genericTypes[i] instanceof ParameterizedType) { 195 | type = (ParameterizedType) genericTypes[i]; 196 | declaringType = (Class) type.getRawType(); 197 | } else { 198 | type = (ParameterizedType) genericType; 199 | declaringType = (Class) genericTypes[i]; 200 | } 201 | break; 202 | } 203 | } 204 | if (declaringType == null) { 205 | Logger.error(TAG, "No generic type available for " + clazz.getName()); 206 | return null; 207 | } 208 | } else { 209 | type = (ParameterizedType) genericType; 210 | } 211 | int genericTypePosition; 212 | if (isList(declaringType)) { 213 | genericTypePosition = 0; 214 | } else if (isMap(declaringType)) { 215 | genericTypePosition = 1; 216 | } else { 217 | Logger.error(TAG, String.format("Field %1$s is neither list nor map.", declaringType.getName())); 218 | return null; 219 | } 220 | Object o = type.getActualTypeArguments()[genericTypePosition]; 221 | if (o instanceof TypeVariable) { 222 | return resolveActualTypeArgs(clazz, (TypeVariable) o); 223 | } else { 224 | return (Class) type.getActualTypeArguments()[genericTypePosition]; 225 | } 226 | } catch (NoSuchFieldException e) { 227 | Logger.error(TAG, String.format("No such field %1$s in %2$s", fieldName, clazz.getName())); 228 | e.printStackTrace(); 229 | return null; 230 | } 231 | } 232 | 233 | public static Class resolveActualTypeArgs(Class offspring, TypeVariable tv, Type... actualArgs) { 234 | assert offspring != null; 235 | assert actualArgs.length == 0 || actualArgs.length == offspring.getTypeParameters().length; 236 | 237 | Class base = (Class) tv.getGenericDeclaration(); 238 | 239 | if (actualArgs.length == 0) { 240 | actualArgs = offspring.getTypeParameters(); 241 | } 242 | 243 | Map typeVariables = new HashMap(); 244 | for (int i = 0; i < actualArgs.length; i++) { 245 | TypeVariable typeVariable = (TypeVariable) offspring.getTypeParameters()[i]; 246 | if (typeVariable.getName().equals(tv.getName())) 247 | return (Class) actualArgs[i]; 248 | typeVariables.put(typeVariable.getName(), actualArgs[i]); 249 | } 250 | 251 | List ancestors = new LinkedList(); 252 | if (offspring.getGenericSuperclass() != null) { 253 | ancestors.add(offspring.getGenericSuperclass()); 254 | } 255 | for (Type t : offspring.getGenericInterfaces()) { 256 | ancestors.add(t); 257 | } 258 | 259 | for (Type type : ancestors) { 260 | if (type instanceof Class) { 261 | Class ancestorClass = (Class) type; 262 | if (base.isAssignableFrom(ancestorClass)) { 263 | Class result = resolveActualTypeArgs((Class) ancestorClass, tv); 264 | if (result != null) { 265 | return result; 266 | } 267 | } 268 | } 269 | if (type instanceof ParameterizedType) { 270 | ParameterizedType parameterizedType = (ParameterizedType) type; 271 | Type rawType = parameterizedType.getRawType(); 272 | if (rawType instanceof Class) { 273 | Class rawTypeClass = (Class) rawType; 274 | if (base.isAssignableFrom(rawTypeClass)) { 275 | 276 | List resolvedTypes = new LinkedList(); 277 | for (Type t : parameterizedType.getActualTypeArguments()) { 278 | if (t instanceof TypeVariable) { 279 | Type resolvedType = typeVariables.get(((TypeVariable) t).getName()); 280 | resolvedTypes.add(resolvedType != null ? resolvedType : t); 281 | } else { 282 | resolvedTypes.add(t); 283 | } 284 | } 285 | 286 | Class result = resolveActualTypeArgs((Class) rawTypeClass, tv, resolvedTypes.toArray(new Type[] {})); 287 | if (result != null) { 288 | return result; 289 | } 290 | } 291 | } 292 | } 293 | } 294 | 295 | return null; 296 | } 297 | 298 | public static Class getGenericTypeOfMethod(Class clazz, String methodName, Class paramType) { 299 | try { 300 | Method method = clazz.getMethod(methodName, paramType); 301 | int genericTypePosition; 302 | if (isList(paramType)) { 303 | genericTypePosition = 0; 304 | } else if (isMap(paramType)) { 305 | genericTypePosition = 1; 306 | } else { 307 | Logger.error(TAG, String.format("Method %1$s is neither list nor map.", paramType.getName())); 308 | return null; 309 | } 310 | ParameterizedType type = (ParameterizedType) method.getGenericParameterTypes()[0]; 311 | return (Class) type.getActualTypeArguments()[genericTypePosition]; 312 | } catch (NoSuchMethodException e) { 313 | Logger.error(TAG, String.format("No such method %1$s in %2$s", methodName, clazz.getName())); 314 | e.printStackTrace(); 315 | return null; 316 | } 317 | } 318 | 319 | public static List getAnnotatedFields(ReflectionCache cache, 320 | Class annotationType, 321 | Class baseClass) { 322 | List fields; 323 | 324 | if (cache != null) { 325 | fields = cache.queryCachedAnnotatedFields(annotationType, baseClass); 326 | if (fields != null) 327 | return fields; 328 | } 329 | 330 | fields = new ArrayList(); 331 | 332 | for (Field f : baseClass.getFields()) { 333 | if (f.isAnnotationPresent(annotationType)) { 334 | fields.add(f); 335 | } 336 | } 337 | 338 | if (cache != null) 339 | cache.cacheAnnotatedFields(annotationType, baseClass, fields); 340 | 341 | return fields; 342 | } 343 | 344 | public static List getAllFields(ReflectionCache cache, Class baseClass) { 345 | List fields; 346 | 347 | if (cache != null) { 348 | fields = cache.queryCachedFields(baseClass); 349 | if (fields != null) 350 | return fields; 351 | } 352 | 353 | fields = new ArrayList(); 354 | 355 | for (Field f : baseClass.getFields()) { 356 | fields.add(f); 357 | } 358 | 359 | if (cache != null) 360 | cache.cacheFields(baseClass, fields); 361 | 362 | return fields; 363 | } 364 | 365 | public static List getAnnotatedGetterMethods(ReflectionCache cache, 366 | Class annotationType, 367 | Class baseClass) { 368 | List methods; 369 | 370 | if (cache != null) { 371 | methods = cache.queryCachedAnnotatedGetterMethods(annotationType, baseClass); 372 | if (methods != null) 373 | return methods; 374 | } 375 | 376 | methods = new ArrayList(); 377 | 378 | for (Method m : baseClass.getMethods()) { 379 | if (m.isAnnotationPresent(annotationType)) { 380 | String methodName = m.getName().toLowerCase(); 381 | if (m.getReturnType() == void.class || m.getParameterTypes().length != 0 || 382 | methodName.equals("getclass")) { 383 | Logger.warn(TAG, String.format("%1$s.%2$s() is not a getter", 384 | baseClass.getName(), m.getName())); 385 | continue; 386 | } 387 | methods.add(m); 388 | } 389 | } 390 | 391 | if (cache != null) 392 | cache.cacheAnnotatedGetterMethods(annotationType, baseClass, methods); 393 | 394 | return methods; 395 | } 396 | 397 | public static List getAnnotatedSetterMethods(ReflectionCache cache, 398 | Class annotationType, 399 | Class baseClass) { 400 | List methods; 401 | 402 | if (cache != null) { 403 | methods = cache.queryCachedAnnotatedSetterMethods(annotationType, baseClass); 404 | if (methods != null) 405 | return methods; 406 | } 407 | 408 | methods = new ArrayList(); 409 | 410 | for (Method m : baseClass.getMethods()) { 411 | if (m.isAnnotationPresent(annotationType)) { 412 | if (m.getReturnType() != void.class || m.getParameterTypes().length != 1) { 413 | Logger.warn(TAG, String.format("%1$s.%2$s() is not a setter", 414 | baseClass.getName(), m.getName())); 415 | continue; 416 | } 417 | methods.add(m); 418 | } 419 | } 420 | 421 | if (cache != null) 422 | cache.cacheAnnotatedSetterMethods(annotationType, baseClass, methods); 423 | 424 | return methods; 425 | } 426 | 427 | public static List getAllGetterMethods(ReflectionCache cache, Class baseClass) { 428 | List methods; 429 | 430 | if (cache != null) { 431 | methods = cache.queryCachedGetterMethods(baseClass); 432 | if (methods != null) 433 | return methods; 434 | } 435 | 436 | methods = new ArrayList(); 437 | 438 | for (Method m : baseClass.getMethods()) { 439 | /* this method automatically filter out setter methods only starting with "set-" prefix */ 440 | String methodName = m.getName().toLowerCase(); 441 | if (m.getReturnType() != void.class && m.getParameterTypes().length == 0 && 442 | methodName.startsWith("get") && !methodName.equals("getclass")) 443 | methods.add(m); 444 | } 445 | 446 | if (cache != null) 447 | cache.cacheGetterMethods(baseClass, methods); 448 | 449 | return methods; 450 | } 451 | 452 | public static List getAllSetterMethods(ReflectionCache cache, Class baseClass) { 453 | List methods; 454 | 455 | if (cache != null) { 456 | methods = cache.queryCachedSetterMethods(baseClass); 457 | if (methods != null) 458 | return methods; 459 | } 460 | 461 | methods = new ArrayList(); 462 | 463 | for (Method m : baseClass.getMethods()) { 464 | /* this method automatically filter out setter methods only starting with "set-" prefix */ 465 | String methodName = m.getName().toLowerCase(); 466 | if (m.getReturnType() == void.class && m.getParameterTypes().length == 1 && 467 | methodName.startsWith("set")) 468 | methods.add(m); 469 | } 470 | 471 | if (cache != null) 472 | cache.cacheSetterMethods(baseClass, methods); 473 | 474 | return methods; 475 | } 476 | 477 | public static String parseString(Object o) { 478 | return o.toString(); 479 | } 480 | 481 | public static int parseInteger(String fieldName, Object o) throws IncompatibleTypeException { 482 | if (o instanceof Integer) { 483 | return (Integer) o; 484 | } else if (o instanceof Long) { 485 | return ((Long) o).intValue(); 486 | } else if (o instanceof Float) { 487 | return ((Float) o).intValue(); 488 | } else if (o instanceof Double) { 489 | return ((Double) o).intValue(); 490 | } else if (o instanceof String) { 491 | try { 492 | return Integer.parseInt((String) o); 493 | } catch (NumberFormatException e) { 494 | throw new IncompatibleTypeException(fieldName, Integer.class.getName(), (String) o); 495 | } 496 | } else { 497 | throw new IncompatibleTypeException(fieldName, Integer.class.getName(), o.toString()); 498 | } 499 | } 500 | 501 | public static long parseLong(String fieldName, Object o) throws IncompatibleTypeException { 502 | if (o instanceof Long) { 503 | return (Long) o; 504 | } else if (o instanceof Integer) { 505 | return ((Integer) o).longValue(); 506 | } else if (o instanceof Float) { 507 | return ((Float) o).longValue(); 508 | } else if (o instanceof Double) { 509 | return ((Double) o).longValue(); 510 | } else if (o instanceof String) { 511 | try { 512 | return Long.parseLong((String) o); 513 | } catch (NumberFormatException e) { 514 | throw new IncompatibleTypeException(fieldName, Long.class.getName(), (String) o); 515 | } 516 | } else { 517 | throw new IncompatibleTypeException(fieldName, Long.class.getName(), (String) o); 518 | } 519 | } 520 | 521 | public static float parseFloat(String fieldName, Object o) throws IncompatibleTypeException { 522 | if (o instanceof Float) { 523 | return (Float) o; 524 | } else if (o instanceof Integer) { 525 | return ((Integer) o).floatValue(); 526 | } else if (o instanceof Double) { 527 | return ((Double) o).floatValue(); 528 | } else if (o instanceof Long) { 529 | return ((Long) o).floatValue(); 530 | } else if (o instanceof String) { 531 | try { 532 | return Float.parseFloat((String) o); 533 | } catch (NumberFormatException e) { 534 | throw new IncompatibleTypeException(fieldName, Float.class.getName(), (String) o); 535 | } 536 | } else { 537 | throw new IncompatibleTypeException(fieldName, Float.class.getName(), o.toString()); 538 | } 539 | } 540 | 541 | public static double parseDouble(String fieldName, Object o) throws IncompatibleTypeException { 542 | if (o instanceof Float) { 543 | return ((Float) o).doubleValue(); 544 | } else if (o instanceof Integer) { 545 | return ((Integer) o).doubleValue(); 546 | } else if (o instanceof Double) { 547 | return (Double) o; 548 | } else if (o instanceof Long) { 549 | return ((Long) o).doubleValue(); 550 | } else if (o instanceof String) { 551 | try { 552 | return Double.parseDouble((String) o); 553 | } catch (NumberFormatException e) { 554 | throw new IncompatibleTypeException(fieldName, Double.class.getName(), (String) o); 555 | } 556 | } else { 557 | throw new IncompatibleTypeException(fieldName, Double.class.getName(), o.toString()); 558 | } 559 | } 560 | 561 | public static boolean parseBoolean(String fieldName, Object o) throws IncompatibleTypeException { 562 | if (o instanceof Boolean) { 563 | return (Boolean) o; 564 | } else if (o instanceof String) { 565 | return Boolean.parseBoolean((String) o); 566 | } else { 567 | throw new IncompatibleTypeException(fieldName, Boolean.class.getName(), o.toString()); 568 | } 569 | } 570 | 571 | public static String normalizeGetterName(String name) { 572 | if (name.startsWith("get_")) { 573 | return name.substring(4); 574 | } else if (name.startsWith("get")) { 575 | name = name.substring(3); 576 | return name.substring(0, 1).toLowerCase() + name.substring(1); 577 | } else if (name.startsWith("Get")) { 578 | return name.substring(3); 579 | } else { 580 | return name; 581 | } 582 | } 583 | 584 | public static String normalizeSetterName(String name) { 585 | if (name.startsWith("set_")) { 586 | return name.substring(4); 587 | } else if (name.startsWith("set")) { 588 | name = name.substring(3); 589 | return name.substring(0, 1).toLowerCase() + name.substring(1); 590 | } else if (name.startsWith("Set")) { 591 | return name.substring(3); 592 | } else { 593 | return name; 594 | } 595 | } 596 | 597 | public static boolean shouldBeExcluded(Class clazz) { 598 | String package_ = clazz.getPackage().getName(); 599 | for (String i : EXCLUDED_OBJECT_PACKAGE_PREFIXES) 600 | if (package_.startsWith(i)) 601 | return true; 602 | 603 | return false; 604 | } 605 | 606 | public static Object parseEnum(Class enumType, String value) { 607 | if (!enumType.isEnum()) { 608 | Logger.error(TAG, String.format("type %1$s is not enum", enumType.getName())); 609 | return null; 610 | } 611 | 612 | String valueLowercase = value.toLowerCase(); 613 | 614 | for (Object o : enumType.getEnumConstants()) { 615 | if (o.toString().toLowerCase().equals(valueLowercase)) { 616 | return o; 617 | } 618 | } 619 | 620 | Logger.warn(TAG, String.format("Could not found value '%1$s' for enum %2$s", 621 | value, enumType.getName())); 622 | 623 | return null; 624 | } 625 | 626 | public static class ResponseWithTypeParameters { 627 | public Object object; 628 | public Type typeVariables[]; 629 | } 630 | 631 | public static ResponseWithTypeParameters getResponseObject(ReflectionCache cache, Object object, boolean overwrite) throws RpcException { 632 | List responseObjects = CodecUtils.getAnnotatedFields(cache, ResponseObject.class, 633 | object.getClass()); 634 | 635 | if (responseObjects == null || responseObjects.size() == 0) { 636 | return null; 637 | } else if (responseObjects.size() > 1) { 638 | RpcException e = new RpcException(String.format("Object '%1$s' has more than one ResponseObject declarations", 639 | object.getClass().getName())); 640 | throw e; 641 | } else { 642 | ResponseWithTypeParameters ret = new ResponseWithTypeParameters(); 643 | Field destField = responseObjects.get(0); 644 | try { 645 | List responseFields = CodecUtils.getAnnotatedFields(cache, Response.class, object.getClass()); 646 | if (responseFields != null && responseFields.size() > 0) { 647 | RpcException e = new RpcException( 648 | String.format("Object '%1$s' has both ResponseObject and Response declarations", 649 | object.getClass().getName())); 650 | throw e; 651 | } 652 | 653 | Type genericType = destField.getGenericType(); 654 | if (genericType != null && genericType instanceof ParameterizedType) { 655 | ParameterizedType pt = (ParameterizedType) genericType; 656 | ret.typeVariables = pt.getActualTypeArguments(); 657 | } 658 | 659 | ret.object = destField.get(object); 660 | if (ret.object == null || overwrite) { 661 | ret.object = destField.getType().newInstance(); 662 | destField.set(object, ret.object); 663 | } 664 | return ret; 665 | } catch (InstantiationException e) { 666 | throw new RpcException(String.format("Could not instantiate ResponseObject %1$s", 667 | destField.getName())); 668 | } catch (IllegalAccessException e) { 669 | throw new RpcException(String.format("Could not instantiate ResponseObject %1$s", 670 | destField.getName())); 671 | } 672 | } 673 | } 674 | 675 | } 676 | --------------------------------------------------------------------------------