content: /*
   * Copyright (C) 2017 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 com.android.layoutlib.bridge.remote.server.adapters;
  
  import com.android.ide.common.rendering.api.ActionBarCallback;
  import com.android.ide.common.rendering.api.AdapterBinding;
  import com.android.ide.common.rendering.api.ILayoutPullParser;
  import com.android.ide.common.rendering.api.LayoutlibCallback;
  import com.android.ide.common.rendering.api.ResourceReference;
  import com.android.ide.common.rendering.api.ResourceValue;
  import com.android.ide.common.rendering.api.SessionParams.Key;
  import com.android.layout.remote.api.RemoteLayoutlibCallback;
  import com.android.layoutlib.bridge.MockView;
  import com.android.tools.layoutlib.annotations.NotNull;
  import com.android.tools.layoutlib.annotations.Nullable;
  
  import org.xmlpull.v1.XmlPullParser;
  
  import java.io.IOException;
  import java.lang.reflect.Constructor;
  import java.lang.reflect.InvocationTargetException;
  import java.nio.file.Files;
  import java.nio.file.Path;
  import java.rmi.RemoteException;
  import java.util.HashMap;
  import java.util.function.Function;
  
  public class RemoteLayoutlibCallbackAdapter extends LayoutlibCallback {
      private final RemoteLayoutlibCallback mDelegate;
      private final PathClassLoader mPathClassLoader;
  
      public RemoteLayoutlibCallbackAdapter(@NotNull RemoteLayoutlibCallback remote) {
          mDelegate = remote;
  
          // Views requested to this callback need to be brought from the "client" side.
          // We transform any loadView into two operations:
          //  - First we ask to where the class is located on disk via findClassPath
          //  - Second, we instantiate the class in the "server" side
          HashMap<String, Path> nameToPathCache = new HashMap<>();
          Function<String, Path> getPathFromName = cacheName -> nameToPathCache.computeIfAbsent(
                  cacheName,
                  name -> {
                      try {
                          return mDelegate.findClassPath(name);
                      } catch (RemoteException e) {
                          throw new RuntimeException(e);
                      }
                  });
  
          mPathClassLoader = new PathClassLoader(getPathFromName);
      }
  
      @NotNull
      private Object createNewInstance(@NotNull Class<?> clazz,
              @Nullable Class<?>[] constructorSignature, @Nullable Object[] constructorParameters,
              boolean isView)
              throws NoSuchMethodException, ClassNotFoundException, InvocationTargetException,
              IllegalAccessException, InstantiationException {
          Constructor<?> constructor = null;
  
          try {
              constructor = clazz.getConstructor(constructorSignature);
          } catch (NoSuchMethodException e) {
              if (!isView) {
                  throw e;
              }
  
              // View class has 1-parameter, 2-parameter and 3-parameter constructors
  
              final int paramsCount = constructorSignature != null ? constructorSignature.length : 0;
              if (paramsCount == 0) {
                  throw e;
              }
              assert constructorParameters != null;
  
              for (int i = 3; i >= 1; i--) {
                  if (i == paramsCount) {
                      continue;
                  }
  
                  final int k = paramsCount < i ? paramsCount : i;
  
                  final Class[] sig = new Class[i];
                  System.arraycopy(constructorSignature, 0, sig, 0, k);
  
                  final Object[] params = new Object[i];
                  System.arraycopy(constructorParameters, 0, params, 0, k);
  
                  for (int j = k + 1; j <= i; j++) {
                      if (j == 2) {
                          sig[j - 1] = findClass("android.util.AttributeSet");
                          params[j - 1] = null;
                      } else if (j == 3) {
                          // parameter 3: int defstyle
                          sig[j - 1] = int.class;
                          params[j - 1] = 0;
                      }
                  }
  
                  constructorSignature = sig;
                  constructorParameters = params;
  
                  try {
                      constructor = clazz.getConstructor(constructorSignature);
                      if (constructor != null) {
                          if (constructorSignature.length < 2) {
                              // TODO: Convert this to remote
  //                            LOG.info("wrong_constructor: Custom view " +
  //                                    clazz.getSimpleName() +
  //                                    " is not using the 2- or 3-argument " +
  //                                    "View constructors; XML attributes will not work");
  //                            mDelegate.warning("wrongconstructor", //$NON-NLS-1$
  //                                    String.format(
  //                                            "Custom view %1$s is not using the 2- or 3-argument
  // View constructors; XML attributes will not work",
  //                                            clazz.getSimpleName()), null, null);
                          }
                          break;
                      }
                  } catch (NoSuchMethodException ignored) {
                  }
              }
  
              if (constructor == null) {
                  throw e;
              }
          }
  
          constructor.setAccessible(true);
          return constructor.newInstance(constructorParameters);
      }
  
      @Override
      public Object loadView(String name, Class[] constructorSignature, Object[] constructorArgs)
              throws Exception {
          Class<?> viewClass = MockView.class;
          try {
              viewClass = findClass(name);
          } catch (ClassNotFoundException ignore) {
              // MockView will be used instead
          }
          return createNewInstance(viewClass, constructorSignature, constructorArgs, true);
      }
  
      @Override
      public String getNamespace() {
          try {
              return mDelegate.getNamespace();
          } catch (RemoteException e) {
              throw new RuntimeException(e);
          }
      }
  
      @Override
      public ResourceReference resolveResourceId(int id) {
          try {
              return mDelegate.resolveResourceId(id);
          } catch (RemoteException e) {
              throw new RuntimeException(e);
          }
      }
  
      @Override
      public int getOrGenerateResourceId(ResourceReference resource) {
          try {
              return mDelegate.getOrGenerateResourceId(resource);
          } catch (RemoteException e) {
              throw new RuntimeException(e);
          }
      }
  
      @Override
      public ILayoutPullParser getParser(ResourceValue layoutResource) {
          try {
              return new RemoteILayoutPullParserAdapter(mDelegate.getParser(layoutResource));
          } catch (RemoteException e) {
              throw new RuntimeException(e);
          }
      }
  
      @Override
      public Object getAdapterItemValue(ResourceReference adapterView, Object adapterCookie,
              ResourceReference itemRef, int fullPosition, int positionPerType,
              int fullParentPosition, int parentPositionPerType, ResourceReference viewRef,
              ViewAttribute viewAttribute, Object defaultValue) {
          return null;
      }
  
      @Override
      public AdapterBinding getAdapterBinding(ResourceReference adapterViewRef, Object adapterCookie,
              Object viewObject) {
          return null;
      }
  
      @Override
      public ActionBarCallback getActionBarCallback() {
          try {
              return new RemoteActionBarCallbackAdapter(mDelegate.getActionBarCallback());
          } catch (RemoteException e) {
              throw new RuntimeException(e);
          }
      }
  
      @Override
      public Object loadClass(String name, Class[] constructorSignature, Object[] constructorArgs)
              throws ClassNotFoundException {
          return super.loadClass(name, constructorSignature, constructorArgs);
      }
  
      @Override
      public boolean supports(int ideFeature) {
          try {
              return mDelegate.supports(ideFeature);
          } catch (RemoteException e) {
              throw new RuntimeException(e);
          }
      }
  
      @Override
      public <T> T getFlag(Key<T> key) {
          return super.getFlag(key);
      }
  
      @Override
      public Class<?> findClass(String name) throws ClassNotFoundException {
          return mPathClassLoader.loadClass(name);
      }
  
      @Override
      public XmlPullParser createXmlParserForPsiFile(String fileName) {
          try {
              return new RemoteXmlPullParserAdapter(mDelegate.createXmlParserForPsiFile(fileName));
          } catch (RemoteException e) {
              throw new RuntimeException(e);
          }
      }
  
      @Override
      public XmlPullParser createXmlParserForFile(String fileName) {
          try {
              return new RemoteXmlPullParserAdapter(mDelegate.createXmlParserForFile(fileName));
          } catch (RemoteException e) {
              throw new RuntimeException(e);
          }
      }
  
      @Override
      public XmlPullParser createXmlParser() {
          try {
              return new RemoteXmlPullParserAdapter(mDelegate.createXmlParser());
          } catch (RemoteException e) {
              throw new RuntimeException(e);
          }
      }
  
      /**
       * Simple class loaders that loads classes from Paths
       */
      private static class PathClassLoader extends ClassLoader {
          private final Function<String, Path> mGetPath;
  
          private PathClassLoader(@NotNull Function<String, Path> getUrl) {
              mGetPath = getUrl;
          }
  
          @Override
          protected Class<?> findClass(@NotNull String name) throws ClassNotFoundException {
              Path path = mGetPath.apply(name);
  
              if (path != null) {
                  try {
                      byte[] content = Files.readAllBytes(path);
                      return defineClass(name, content, 0, content.length);
                  } catch (IOException ignore) {
                  }
              }
  
              throw new ClassNotFoundException(name);
          }
      }
  }
  