blob: 51772e7b02e9d58478279c7cfc8def1749a58a8e [file] [log] [blame]
/*
* 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 <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);
}
}
}