/* * Copyright (C) 2015 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.builder.core; import com.android.annotations.NonNull; import com.android.jack.api.JackProvider; import com.android.jill.api.JillProvider; import com.android.sdklib.BuildToolInfo; import com.android.sdklib.repository.FullRevision; import com.android.utils.ILogger; import com.google.common.base.Joiner; import com.google.common.base.Objects; import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; import java.io.File; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.ServiceLoader; /** * {@link ServiceLoader} helpers for tools located in the SDK's build-tools folders. * * This utility will cache {@link ServiceLoader} instances per build-tools version and per target * service type. */ public enum BuildToolsServiceLoader { /** * Singleton instance to request {@link ServiceLoader} instances from. */ INSTANCE; /** * private cache data for a particular build-tool version. */ private static final class LoadedBuildTool { private final FullRevision version; private final BuildToolServiceLoader serviceLoader; private LoadedBuildTool(FullRevision version, BuildToolServiceLoader serviceLoader) { this.version = version; this.serviceLoader = serviceLoader; } } private final List<LoadedBuildTool> loadedBuildTools = new ArrayList<LoadedBuildTool>(); /** * Load a built-tools version specific {@link ServiceLoader} helper. * @param buildToolInfo the requested build-tools information * @return an initialized {@link BuildToolsServiceLoader.BuildToolServiceLoader} to get * instances of {@link ServiceLoader} from. */ @NonNull public synchronized BuildToolServiceLoader forVersion(BuildToolInfo buildToolInfo) { Optional<LoadedBuildTool> loadedBuildToolOptional = findVersion(buildToolInfo.getRevision()); if (loadedBuildToolOptional.isPresent()) { return loadedBuildToolOptional.get().serviceLoader; } LoadedBuildTool loadedBuildTool = new LoadedBuildTool(buildToolInfo.getRevision(), new BuildToolServiceLoader(buildToolInfo)); loadedBuildTools.add(loadedBuildTool); return loadedBuildTool.serviceLoader; } @NonNull private Optional<LoadedBuildTool> findVersion(FullRevision version) { for (LoadedBuildTool loadedBuildTool : loadedBuildTools) { if (loadedBuildTool.version.equals(version)) { return Optional.of(loadedBuildTool); } } return Optional.absent(); } /** * Abstract notion of what a service is. A service must be declared in one of the classpath * provided jar files. The service declaration must conforms to {@link ServiceLoader} contract. * * @param <T> the type of service. */ public static class Service<T> { private final Collection<String> classpath; private final Class<T> serviceClass; protected Service(Collection<String> classpath, Class<T> serviceClass) { this.classpath = classpath; this.serviceClass = serviceClass; } public Collection<String> getClasspath() { return classpath; } public Class<T> getServiceClass() { return serviceClass; } @Override public String toString() { return Objects.toStringHelper(this) .add("serviceClass", serviceClass) .add("classpath", Joiner.on(",").join(classpath)) .toString(); } } /** * Jack service description. */ public static final Service<JackProvider> JACK = new Service<JackProvider>(ImmutableList.of("jack.jar"), JackProvider.class); /** * Jill service description. */ public static final Service<JillProvider> JILL = new Service<JillProvider>(ImmutableList.of("jill.jar"), JillProvider.class); /** * build-tools version specific {@link ServiceLoader} helper. */ public static final class BuildToolServiceLoader { /** * private cache data for a single {@link ServiceLoader} instance. * @param <T> the service loader type. */ private static final class LoadedServiceLoader<T> { private final Class<T> serviceType; private final ServiceLoader<T> serviceLoader; private LoadedServiceLoader(Class<T> serviceType, ServiceLoader<T> serviceLoader) { this.serviceType = serviceType; this.serviceLoader = serviceLoader; } } private final BuildToolInfo buildToolInfo; private final List<LoadedServiceLoader> loadedServicesLoaders = new ArrayList<LoadedServiceLoader>(); private BuildToolServiceLoader(BuildToolInfo buildToolInfo) { this.buildToolInfo = buildToolInfo; } /** * Returns a newly allocated or existing {@link ServiceLoader} instance for the passed * {@link com.android.builder.core.BuildToolsServiceLoader.Service} type in the context * of the build-tools version this instance was created for. * * @param serviceType the requested service type encapsulation. * @param <T> the type of service * @return a {@link ServiceLoader} instance for the T service type. * @throws ClassNotFoundException */ @NonNull public synchronized <T> ServiceLoader<T> getServiceLoader(Service<T> serviceType) throws ClassNotFoundException { Optional<ServiceLoader<T>> serviceLoaderOptional = getLoadedServiceLoader(serviceType.getServiceClass()); if (serviceLoaderOptional.isPresent()) { return serviceLoaderOptional.get(); } File buildToolLocation = buildToolInfo.getLocation(); if (System.getenv("USE_JACK_LOCATION") != null) { buildToolLocation = new File(System.getenv("USE_JACK_LOCATION")); } URL[] urls = new URL[serviceType.classpath.size()]; int i = 0; for (String classpathItem : serviceType.getClasspath()) { File jarFile = new File(buildToolLocation, classpathItem); try { urls[i++] = jarFile.toURI().toURL(); } catch (MalformedURLException e) { throw new RuntimeException(e); } } ClassLoader cl = new URLClassLoader(urls, serviceType.getServiceClass().getClassLoader()); ServiceLoader<T> serviceLoader = ServiceLoader.load(serviceType.getServiceClass(), cl); loadedServicesLoaders.add(new LoadedServiceLoader<T>( serviceType.getServiceClass(), serviceLoader)); return serviceLoader; } /** * Return the first service instance for the requested service type or * {@link Optional#absent()} if none exist. * @param logger to log resolution. * @param serviceType the requested service type encapsulation. * @param <T> the requested service class type. * @return the instance of T or null of none exist in this context. * @throws ClassNotFoundException */ @NonNull public synchronized <T> Optional<T> getSingleService( ILogger logger, Service<T> serviceType) throws ClassNotFoundException { logger.verbose("Looking for %1$s", serviceType); ServiceLoader<T> serviceLoader = getServiceLoader(serviceType); logger.verbose("Got a serviceLoader %1$d", Integer.toHexString(System.identityHashCode(serviceLoader))); Iterator<T> serviceIterator = serviceLoader.iterator(); logger.verbose("Service Iterator = %1$s ", serviceIterator); if (serviceIterator.hasNext()) { T service = serviceIterator.next(); logger.verbose("Got it from %1$s, loaded service = %2$s, type = %3$s", serviceIterator, service, service.getClass()); return Optional.of(service); } else { logger.info("Cannot find service implementation %1$s" + serviceType); return Optional.absent(); } } @NonNull @SuppressWarnings("unchecked") private <T> Optional<ServiceLoader<T>> getLoadedServiceLoader(Class<T> serviceType) { for (LoadedServiceLoader<?> loadedServiceLoader : loadedServicesLoaders) { if (loadedServiceLoader.serviceType.equals(serviceType)) { return Optional.of((ServiceLoader<T>) loadedServiceLoader.serviceLoader); } } return Optional.absent(); } } }