content: /*
   * 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 androidx.test.internal.util.Checks.checkNotNull;
  
  import androidx.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;
  import java.util.Locale;
  
  /**
   * 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(
                Locale.ROOT,
                "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(
                    Locale.ROOT,
                    "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);
      }
    }
  }
  