blob: 4c1881ca686202b99b4675c997fcb95faf70e8d1 [file] [log] [blame]
// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.graph;
import com.android.tools.r8.DataDirectoryResource;
import com.android.tools.r8.DataEntryResource;
import com.android.tools.r8.DataResourceProvider;
import com.android.tools.r8.DataResourceProvider.Visitor;
import com.android.tools.r8.ProgramResourceProvider;
import com.android.tools.r8.ResourceException;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.StringDiagnostic;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.ByteStreams;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/** A description of the services and their implementations found in META-INF/services/. */
public class AppServices {
// Mapping from service types to service implementation types.
private final Map<DexType, Set<DexType>> services;
private AppServices(Map<DexType, Set<DexType>> services) {
this.services = services;
}
public Set<DexType> allServiceTypes() {
return services.keySet();
}
public Set<DexType> serviceImplementationsFor(DexType serviceType) {
assert services.containsKey(serviceType);
Set<DexType> serviceImplementationTypes = services.get(serviceType);
if (serviceImplementationTypes == null) {
assert false
: "Unexpected attempt to get service implementations for non-service type `"
+ serviceType.toSourceString()
+ "`";
return ImmutableSet.of();
}
return serviceImplementationTypes;
}
public static Builder builder(AppView<? extends AppInfo> appView) {
return new Builder(appView);
}
public static class Builder {
private static final String SERVICE_DIRECTORY_NAME = "META-INF/services/";
private final AppView<? extends AppInfo> appView;
private final Map<DexType, Set<DexType>> services = new IdentityHashMap<>();
private Builder(AppView<? extends AppInfo> appView) {
this.appView = appView;
}
public AppServices build() {
Iterable<ProgramResourceProvider> programResourceProviders =
appView.appInfo().app.programResourceProviders;
for (ProgramResourceProvider programResourceProvider : programResourceProviders) {
DataResourceProvider dataResourceProvider =
programResourceProvider.getDataResourceProvider();
if (dataResourceProvider != null) {
readServices(dataResourceProvider);
}
}
return new AppServices(services);
}
private void readServices(DataResourceProvider dataResourceProvider) {
try {
dataResourceProvider.accept(new DataResourceProviderVisitor());
} catch (ResourceException e) {
throw new CompilationError(e.getMessage(), e);
}
}
private class DataResourceProviderVisitor implements Visitor {
@Override
public void visit(DataDirectoryResource directory) {
// Ignore.
}
@Override
public void visit(DataEntryResource file) {
try {
String name = file.getName();
if (name.startsWith(SERVICE_DIRECTORY_NAME)) {
String serviceName = name.substring(SERVICE_DIRECTORY_NAME.length());
if (DescriptorUtils.isValidJavaType(serviceName)) {
String serviceDescriptor = DescriptorUtils.javaTypeToDescriptor(serviceName);
DexType serviceType = appView.dexItemFactory().createType(serviceDescriptor);
byte[] bytes = ByteStreams.toByteArray(file.getByteStream());
String contents = new String(bytes, Charset.defaultCharset());
services.put(
serviceType, readServiceImplementationsForService(contents, file.getOrigin()));
}
}
} catch (IOException | ResourceException e) {
throw new CompilationError(e.getMessage(), e);
}
}
private Set<DexType> readServiceImplementationsForService(String contents, Origin origin) {
if (contents != null) {
return Arrays.stream(contents.split(System.lineSeparator()))
.map(String::trim)
.filter(line -> !line.isEmpty())
.filter(DescriptorUtils::isValidJavaType)
.map(DescriptorUtils::javaTypeToDescriptor)
.map(appView.dexItemFactory()::createType)
.filter(
serviceImplementationType -> {
if (!serviceImplementationType.isClassType()) {
// Should never happen.
appView
.options()
.reporter
.warning(
new StringDiagnostic(
"Unexpected service implementation found in META-INF/services/: `"
+ serviceImplementationType.toSourceString()
+ "`.",
origin));
return false;
}
return true;
})
.collect(Collectors.toSet());
}
return ImmutableSet.of();
}
}
}
}