Twidere-App-Android-Twitter.../twidere.component.common/src/main/java/org/mariotaku/twidere/api/twitter/util/TwitterConverter.java

308 lines
16 KiB
Java

/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2015 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.api.twitter.util;
import com.bluelinelabs.logansquare.LoganSquare;
import com.bluelinelabs.logansquare.typeconverters.TypeConverter;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import org.mariotaku.restfu.Converter;
import org.mariotaku.restfu.Utils;
import org.mariotaku.restfu.http.ContentType;
import org.mariotaku.restfu.http.RestHttpResponse;
import org.mariotaku.restfu.http.mime.TypedData;
import org.mariotaku.twidere.api.twitter.TwitterException;
import org.mariotaku.twidere.api.twitter.auth.OAuthToken;
import org.mariotaku.twidere.api.twitter.model.AccountSettings;
import org.mariotaku.twidere.api.twitter.model.Activity;
import org.mariotaku.twidere.api.twitter.model.CardEntity;
import org.mariotaku.twidere.api.twitter.model.DirectMessage;
import org.mariotaku.twidere.api.twitter.model.ErrorInfo;
import org.mariotaku.twidere.api.twitter.model.ExtendedProfile;
import org.mariotaku.twidere.api.twitter.model.GeoLocation;
import org.mariotaku.twidere.api.twitter.model.HashtagEntity;
import org.mariotaku.twidere.api.twitter.model.IDs;
import org.mariotaku.twidere.api.twitter.model.Language;
import org.mariotaku.twidere.api.twitter.model.Location;
import org.mariotaku.twidere.api.twitter.model.MediaEntity;
import org.mariotaku.twidere.api.twitter.model.MediaUploadResponse;
import org.mariotaku.twidere.api.twitter.model.PageableResponseList;
import org.mariotaku.twidere.api.twitter.model.Place;
import org.mariotaku.twidere.api.twitter.model.QueryResult;
import org.mariotaku.twidere.api.twitter.model.Relationship;
import org.mariotaku.twidere.api.twitter.model.ResponseCode;
import org.mariotaku.twidere.api.twitter.model.ResponseList;
import org.mariotaku.twidere.api.twitter.model.SavedSearch;
import org.mariotaku.twidere.api.twitter.model.ScheduledStatus;
import org.mariotaku.twidere.api.twitter.model.ScheduledStatusesList;
import org.mariotaku.twidere.api.twitter.model.Status;
import org.mariotaku.twidere.api.twitter.model.StatusActivitySummary;
import org.mariotaku.twidere.api.twitter.model.StatusDeletionNotice;
import org.mariotaku.twidere.api.twitter.model.TimeZone;
import org.mariotaku.twidere.api.twitter.model.TranslationResult;
import org.mariotaku.twidere.api.twitter.model.Trend;
import org.mariotaku.twidere.api.twitter.model.Trends;
import org.mariotaku.twidere.api.twitter.model.UrlEntity;
import org.mariotaku.twidere.api.twitter.model.User;
import org.mariotaku.twidere.api.twitter.model.UserList;
import org.mariotaku.twidere.api.twitter.model.UserMentionEntity;
import org.mariotaku.twidere.api.twitter.model.Warning;
import org.mariotaku.twidere.api.twitter.model.impl.AccountSettingsImpl;
import org.mariotaku.twidere.api.twitter.model.impl.ActivityImpl;
import org.mariotaku.twidere.api.twitter.model.impl.CardEntityImpl;
import org.mariotaku.twidere.api.twitter.model.impl.DirectMessageImpl;
import org.mariotaku.twidere.api.twitter.model.impl.ErrorInfoImpl;
import org.mariotaku.twidere.api.twitter.model.impl.ExtendedProfileImpl;
import org.mariotaku.twidere.api.twitter.model.impl.HashtagEntityImpl;
import org.mariotaku.twidere.api.twitter.model.impl.IDsImpl;
import org.mariotaku.twidere.api.twitter.model.impl.Indices;
import org.mariotaku.twidere.api.twitter.model.impl.LanguageImpl;
import org.mariotaku.twidere.api.twitter.model.impl.LocationImpl;
import org.mariotaku.twidere.api.twitter.model.impl.MediaEntityImpl;
import org.mariotaku.twidere.api.twitter.model.impl.MediaUploadResponseImpl;
import org.mariotaku.twidere.api.twitter.model.impl.PageableResponseListWrapper;
import org.mariotaku.twidere.api.twitter.model.impl.PlaceImpl;
import org.mariotaku.twidere.api.twitter.model.impl.QueryResultWrapper;
import org.mariotaku.twidere.api.twitter.model.impl.RelationshipImpl;
import org.mariotaku.twidere.api.twitter.model.impl.RelationshipWrapper;
import org.mariotaku.twidere.api.twitter.model.impl.ResponseListImpl;
import org.mariotaku.twidere.api.twitter.model.impl.SavedSearchImpl;
import org.mariotaku.twidere.api.twitter.model.impl.ScheduledStatusImpl;
import org.mariotaku.twidere.api.twitter.model.impl.ScheduledStatusesListWrapper;
import org.mariotaku.twidere.api.twitter.model.impl.StatusActivitySummaryImpl;
import org.mariotaku.twidere.api.twitter.model.impl.StatusDeletionNoticeImpl;
import org.mariotaku.twidere.api.twitter.model.impl.StatusImpl;
import org.mariotaku.twidere.api.twitter.model.impl.TimeZoneImpl;
import org.mariotaku.twidere.api.twitter.model.impl.TranslationResultImpl;
import org.mariotaku.twidere.api.twitter.model.impl.TrendImpl;
import org.mariotaku.twidere.api.twitter.model.impl.TrendsImpl;
import org.mariotaku.twidere.api.twitter.model.impl.TwitterResponseImpl;
import org.mariotaku.twidere.api.twitter.model.impl.TypeConverterMapper;
import org.mariotaku.twidere.api.twitter.model.impl.UrlEntityImpl;
import org.mariotaku.twidere.api.twitter.model.impl.UserImpl;
import org.mariotaku.twidere.api.twitter.model.impl.UserListImpl;
import org.mariotaku.twidere.api.twitter.model.impl.UserMentionEntityImpl;
import org.mariotaku.twidere.api.twitter.model.impl.WarningImpl;
import org.mariotaku.twidere.api.twitter.model.impl.Wrapper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.text.ParseException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Created by mariotaku on 15/5/5.
*/
public class TwitterConverter implements Converter {
private static final Map<Class<?>, Class<? extends Wrapper<?>>> wrapperMap = new HashMap<>();
static {
TypeConverterMapper.register(Status.class, StatusImpl.class);
TypeConverterMapper.register(User.class, UserImpl.class);
TypeConverterMapper.register(UserList.class, UserListImpl.class);
TypeConverterMapper.register(DirectMessage.class, DirectMessageImpl.class);
TypeConverterMapper.register(SavedSearch.class, SavedSearchImpl.class);
TypeConverterMapper.register(UrlEntity.class, UrlEntityImpl.class);
TypeConverterMapper.register(MediaEntity.class, MediaEntityImpl.class);
TypeConverterMapper.register(MediaEntity.Size.class, MediaEntityImpl.SizeImpl.class);
TypeConverterMapper.register(MediaEntity.Feature.class, MediaEntityImpl.FeatureImpl.class);
TypeConverterMapper.register(MediaEntity.Feature.Face.class, MediaEntityImpl.FeatureImpl.FaceImpl.class);
TypeConverterMapper.register(MediaEntity.VideoInfo.class, MediaEntityImpl.VideoInfoImpl.class);
TypeConverterMapper.register(MediaEntity.VideoInfo.Variant.class, MediaEntityImpl.VideoInfoImpl.VariantImpl.class);
TypeConverterMapper.register(UserMentionEntity.class, UserMentionEntityImpl.class);
TypeConverterMapper.register(HashtagEntity.class, HashtagEntityImpl.class);
TypeConverterMapper.register(CardEntity.class, CardEntityImpl.class);
TypeConverterMapper.register(Place.class, PlaceImpl.class);
TypeConverterMapper.register(Relationship.class, RelationshipImpl.class);
TypeConverterMapper.register(MediaUploadResponse.class, MediaUploadResponseImpl.class);
TypeConverterMapper.register(MediaUploadResponse.Image.class, MediaUploadResponseImpl.ImageImpl.class);
TypeConverterMapper.register(ErrorInfo.class, ErrorInfoImpl.class);
TypeConverterMapper.register(TranslationResult.class, TranslationResultImpl.class);
TypeConverterMapper.register(Language.class, LanguageImpl.class);
TypeConverterMapper.register(Trend.class, TrendImpl.class);
TypeConverterMapper.register(Trends.class, TrendsImpl.class);
TypeConverterMapper.register(Location.class, LocationImpl.class);
TypeConverterMapper.register(Location.PlaceType.class, LocationImpl.PlaceTypeImpl.class);
TypeConverterMapper.register(StatusActivitySummary.class, StatusActivitySummaryImpl.class);
TypeConverterMapper.register(TimeZone.class, TimeZoneImpl.class);
TypeConverterMapper.register(AccountSettings.class, AccountSettingsImpl.class);
TypeConverterMapper.register(IDs.class, IDsImpl.class, IDsImpl.MAPPER);
TypeConverterMapper.register(Activity.class, ActivityImpl.class, ActivityImpl.MAPPER);
TypeConverterMapper.register(Warning.class, WarningImpl.class);
TypeConverterMapper.register(StatusDeletionNotice.class, StatusDeletionNoticeImpl.class);
TypeConverterMapper.register(ScheduledStatus.class, ScheduledStatusImpl.class);
TypeConverterMapper.register(ExtendedProfile.class, ExtendedProfileImpl.class);
LoganSquare.registerTypeConverter(Indices.class, Indices.CONVERTER);
LoganSquare.registerTypeConverter(GeoLocation.class, GeoLocation.CONVERTER);
LoganSquare.registerTypeConverter(CardEntity.BindingValue.class, CardEntityImpl.BindingValueWrapper.CONVERTER);
LoganSquare.registerTypeConverter(IDs.class, IDsImpl.CONVERTER);
LoganSquare.registerTypeConverter(MediaEntity.Type.class, EnumConverter.get(MediaEntity.Type.class));
LoganSquare.registerTypeConverter(UserList.Mode.class, EnumConverter.get(UserList.Mode.class));
LoganSquare.registerTypeConverter(Activity.Action.class, EnumConverter.get(Activity.Action.class));
LoganSquare.registerTypeConverter(ScheduledStatus.State.class, EnumConverter.get(ScheduledStatus.State.class));
registerWrapper(QueryResult.class, QueryResultWrapper.class);
registerWrapper(PageableResponseList.class, PageableResponseListWrapper.class);
registerWrapper(Relationship.class, RelationshipWrapper.class);
registerWrapper(CardEntity.BindingValue.class, CardEntityImpl.BindingValueWrapper.class);
registerWrapper(ScheduledStatusesList.class, ScheduledStatusesListWrapper.class);
}
@Override
public Object convert(RestHttpResponse response, Type type) throws Exception {
final TypedData body = response.getBody();
if (!response.isSuccessful()) {
throw parseOrThrow(response, body.stream(), TwitterException.class);
}
final ContentType contentType = body.contentType();
final InputStream stream = body.stream();
try {
if (type instanceof Class<?>) {
final Class<?> cls = (Class<?>) type;
final Class<?> wrapperCls = wrapperMap.get(cls);
if (wrapperCls != null) {
final Wrapper<?> wrapper = (Wrapper<?>) parseOrThrow(response, stream, wrapperCls);
wrapper.processResponseHeader(response);
return wrapper.getWrapped(null);
} else if (OAuthToken.class.isAssignableFrom(cls)) {
final ByteArrayOutputStream os = new ByteArrayOutputStream();
body.writeTo(os);
Charset charset = contentType != null ? contentType.getCharset() : null;
if (charset == null) {
charset = Charset.defaultCharset();
}
try {
return new OAuthToken(os.toString(charset.name()), charset);
} catch (ParseException e) {
throw new IOException(e);
}
} else if (ResponseCode.class.isAssignableFrom(cls)) {
return new ResponseCode(response);
}
final Object object = parseOrThrow(response, stream, cls);
checkResponse(cls, object, response);
if (object instanceof TwitterResponseImpl) {
((TwitterResponseImpl) object).processResponseHeader(response);
}
return object;
} else if (type instanceof ParameterizedType) {
final Type rawType = ((ParameterizedType) type).getRawType();
if (rawType instanceof Class<?>) {
final Class<?> rawClass = (Class<?>) rawType;
final Class<?> wrapperCls = wrapperMap.get(rawClass);
if (wrapperCls != null) {
final Wrapper<?> wrapper = (Wrapper<?>) parseOrThrow(response, stream, wrapperCls);
wrapper.processResponseHeader(response);
return wrapper.getWrapped(((ParameterizedType) type).getActualTypeArguments());
} else if (ResponseList.class.isAssignableFrom(rawClass)) {
final Type elementType = ((ParameterizedType) type).getActualTypeArguments()[0];
final ResponseListImpl<?> responseList = new ResponseListImpl<>(parseListOrThrow(response, stream, (Class<?>) elementType));
responseList.processResponseHeader(response);
return responseList;
}
}
}
throw new UnsupportedTypeException(type);
} finally {
Utils.closeSilently(stream);
}
}
private static <T> T parseOrThrow(RestHttpResponse resp, InputStream stream, Class<T> cls) throws IOException, TwitterException {
try {
final T parse = LoganSquare.parse(stream, cls);
if (TwitterException.class.isAssignableFrom(cls) && parse == null) {
throw new TwitterException();
}
return parse;
} catch (JsonParseException e) {
throw new TwitterException("Malformed JSON Data", resp);
}
}
private static <T> List<T> parseListOrThrow(RestHttpResponse resp, InputStream stream, Class<T> elementCls) throws IOException, TwitterException {
try {
return LoganSquare.parseList(stream, elementCls);
} catch (JsonParseException e) {
throw new TwitterException("Malformed JSON Data", resp);
}
}
private void checkResponse(Class<?> cls, Object object, RestHttpResponse response) throws TwitterException {
if (User.class.isAssignableFrom(cls)) {
if (object == null) throw new TwitterException("User is null");
}
}
private static <T> void registerWrapper(Class<T> cls, Class<? extends Wrapper<? extends T>> wrapperCls) {
wrapperMap.put(cls, wrapperCls);
}
private static class EnumConverter<T extends Enum<T>> implements TypeConverter<T> {
private final Class<T> cls;
EnumConverter(Class<T> cls) {
this.cls = cls;
}
@SuppressWarnings({"unchecked", "TryWithIdenticalCatches"})
@Override
public T parse(JsonParser jsonParser) throws IOException {
try {
final Method method = cls.getMethod("parse", String.class);
return (T) method.invoke(null, jsonParser.getValueAsString());
} catch (NoSuchMethodException e) {
return Enum.valueOf(cls, jsonParser.getValueAsString());
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
@Override
public void serialize(T object, String fieldName, boolean writeFieldNameForObject, JsonGenerator jsonGenerator) {
throw new UnsupportedOperationException();
}
public static <T extends Enum<T>> EnumConverter<T> get(Class<T> cls) {
return new EnumConverter<>(cls);
}
}
public static class UnsupportedTypeException extends UnsupportedOperationException {
public UnsupportedTypeException(Type type) {
super("Unsupported type " + type);
}
}
}