AnyToTypeConverter.java
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package androidx.test.espresso.remote;
import static com.google.common.base.Preconditions.checkNotNull;
import android.support.annotation.NonNull;
import androidx.test.espresso.remote.EspressoRemoteMessage.From;
import com.google.protobuf.Any;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.MessageLite;
import com.google.protobuf.Parser;
import java.lang.reflect.Field;
/**
* Unwraps an {@link Any} proto message to it's target type T, where T must implement the {@link
* EspressoRemoteMessage.From} interface.
*
* @param <T> the target type T
*/
final class AnyToTypeConverter<T> implements Converter<Any, T> {
private final RemoteDescriptorRegistry remoteDescriptorRegistry;
AnyToTypeConverter(@NonNull RemoteDescriptorRegistry remoteDescriptorRegistry) {
this.remoteDescriptorRegistry =
checkNotNull(remoteDescriptorRegistry, "remoteDescriptorRegistry cannot be null!");
}
@Override
public T convert(@NonNull Any any) {
checkNotNull(any, "any cannot be null!");
T targetType;
try {
Class<?> targetClass = createRemoteTargetClassFromAny(any);
Field fromField = accessFromField(targetClass);
From<T, MessageLite> remoteMessageFromField = createRemoteMessageFromField(fromField);
targetType = createTargetTypeFromRemoteMessage(remoteMessageFromField, any);
} catch (IllegalAccessException iae) {
throw new RemoteProtocolException("Cannot unwrap target type instance from any proto: ", iae);
} catch (RuntimeException re) {
throw new RemoteProtocolException(
String.format(
"Something went wrong during any conversion " + "for type url: %s", any.getTypeUrl()),
re);
}
return targetType;
}
private T createTargetTypeFromRemoteMessage(
From<T, MessageLite> remoteMessageFromField, Any anyMessage) {
MessageLite remoteMessage = anyToProto(anyMessage);
// Create an instance of our remote view Matcher from the remoteMatcherMessage
return remoteMessageFromField.fromProto(remoteMessage);
}
@SuppressWarnings("unchecked") // safe covariant cast
private EspressoRemoteMessage.From<T, MessageLite> createRemoteMessageFromField(Field fromField)
throws IllegalAccessException {
try {
return EspressoRemoteMessage.From.class.cast(fromField.get(null));
} catch (ClassCastException cce) {
throw new RemoteProtocolException(
"Cannot unwrap target type from any proto: Cannot cast"
+ "'private static FROM' field to the EspressoRemoteMessage.From interface.");
}
}
private Field accessFromField(Class<?> remoteClass) throws IllegalAccessException {
Field fromField;
try {
fromField = remoteClass.getDeclaredField("FROM");
} catch (NoSuchFieldException nsfe) {
throw new RemoteProtocolException(
"Cannot unwrap target type from any proto: "
+ remoteClass.getSimpleName()
+ " does not declare a "
+ "'private static FROM' fromViewMatcherField implementing the"
+ "EspressoRemoteMessage.From interface. Please ensure that such a"
+ "fromViewMatcherField exists!",
nsfe);
}
fromField.setAccessible(true);
if (null == fromField /* should never happen */ || !fromField.isAccessible()) {
throw new IllegalAccessException(
"Failed to make EspressoRemoteMessage.From FROM field accessible");
}
return fromField;
}
private Class<?> createRemoteTargetClassFromAny(Any any) {
return remoteDescriptorRegistry.argForRemoteTypeUrl(any.getTypeUrl()).getRemoteType();
}
private <T extends MessageLite> T anyToProto(Any any, Class<T> expectedType, Parser<?> parser)
throws InvalidProtocolBufferException {
checkNotNull(expectedType, "expectedType cannot be null!");
checkNotNull(parser, "parser cannot be null!");
Object proto = parser.parseFrom(any.getValue());
if (!expectedType.isInstance(proto)) {
RemoteProtocolException rpe =
new RemoteProtocolException(
String.format(
"Got type: %s, but expected type: %s instead",
proto.getClass().getName(), expectedType.getName()));
throw rpe;
}
return expectedType.cast(proto);
}
@SuppressWarnings("TypeParameterUnusedInFormals") // addressed in anyToProto(Any, Class,// Parser)
private <T extends MessageLite> T anyToProto(Any any) {
RemoteDescriptor remoteDescriptor =
remoteDescriptorRegistry.argForRemoteTypeUrl(any.getTypeUrl());
try {
@SuppressWarnings("unchecked") // safe covariant cast
Class<T> messageType = (Class<T>) remoteDescriptor.getProtoType();
return anyToProto(any, messageType, remoteDescriptor.getProtoParser());
} catch (ClassCastException cce) {
throw new RemoteProtocolException("Message cannot be casted to type T", cce);
} catch (InvalidProtocolBufferException ipbe) {
throw new RemoteProtocolException(
"Invalid Protocol buffer for any type url: " + any.getTypeUrl(), ipbe);
}
}
}