|  | // Copyright (c) 2018, 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.resource; | 
|  |  | 
|  | import static org.junit.Assert.assertEquals; | 
|  | import static org.junit.Assert.assertFalse; | 
|  | import static org.junit.Assert.assertTrue; | 
|  |  | 
|  | import com.android.tools.r8.CompilationFailedException; | 
|  | import com.android.tools.r8.DataDirectoryResource; | 
|  | import com.android.tools.r8.DataEntryResource; | 
|  | import com.android.tools.r8.DataResource; | 
|  | import com.android.tools.r8.DataResourceConsumer; | 
|  | import com.android.tools.r8.DiagnosticsHandler; | 
|  | import com.android.tools.r8.R8Command; | 
|  | import com.android.tools.r8.StringResource; | 
|  | import com.android.tools.r8.ToolHelper; | 
|  | import com.android.tools.r8.errors.Unreachable; | 
|  | import com.android.tools.r8.naming.ClassNameMapper; | 
|  | import com.android.tools.r8.origin.Origin; | 
|  | import com.android.tools.r8.shaking.forceproguardcompatibility.ProguardCompatibilityTestBase; | 
|  | import com.android.tools.r8.utils.AndroidApp; | 
|  | import com.android.tools.r8.utils.BooleanUtils; | 
|  | import com.google.common.collect.ImmutableList; | 
|  | import com.google.common.collect.Sets; | 
|  | import java.util.Collection; | 
|  | import java.util.List; | 
|  | import java.util.Set; | 
|  | import java.util.function.Function; | 
|  | import java.util.stream.Collectors; | 
|  | import org.junit.Test; | 
|  | import org.junit.runner.RunWith; | 
|  | import org.junit.runners.Parameterized; | 
|  |  | 
|  | class Main { | 
|  |  | 
|  | private void method() {} | 
|  |  | 
|  | public static void main(String[] args) { | 
|  | new Main().method(); | 
|  | } | 
|  | } | 
|  |  | 
|  | @RunWith(Parameterized.class) | 
|  | public class KeepDirectoriesTest extends ProguardCompatibilityTestBase { | 
|  |  | 
|  | private Backend backend; | 
|  | private final boolean minify; | 
|  |  | 
|  | @Parameterized.Parameters(name = "Backend: {0}, Minify: {1}") | 
|  | public static Collection<Object[]> data() { | 
|  | return buildParameters(ToolHelper.getBackends(), BooleanUtils.values()); | 
|  | } | 
|  |  | 
|  | public KeepDirectoriesTest(Backend backend, boolean minify) { | 
|  | this.backend = backend; | 
|  | this.minify = minify; | 
|  | } | 
|  |  | 
|  | // Return the original package name for this package. | 
|  | private String pathForThisPackage() { | 
|  | return getClass().getPackage().getName().replace('.', '/'); | 
|  | } | 
|  |  | 
|  | // Return the package name in the app for this package. | 
|  | private String pathForThisPackage(AndroidApp app) throws Exception { | 
|  | String name; | 
|  | if (app.getProguardMapOutputData() != null) { | 
|  | ClassNameMapper mapper = | 
|  | ClassNameMapper.mapperFromString(app.getProguardMapOutputData().getString()); | 
|  | name = mapper.getObfuscatedToOriginalMapping().inverse.get(Main.class.getCanonicalName()); | 
|  | } else { | 
|  | name = Main.class.getTypeName(); | 
|  | } | 
|  | return name.substring(0, name.lastIndexOf('.')).replace('.', '/'); | 
|  | } | 
|  |  | 
|  | private void checkResourceNames(Set<String> resourceNames, String expectedPackageName) { | 
|  | resourceNames.stream() | 
|  | .filter(name -> !name.startsWith("org")) | 
|  | .forEach(name -> assertTrue(name.startsWith(expectedPackageName))); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testKeepNone() throws Exception { | 
|  | CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer(); | 
|  | compileWithR8("", minify, dataResourceConsumer); | 
|  | assertEquals(0, dataResourceConsumer.size()); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testKeepAll() throws Exception { | 
|  | CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer(); | 
|  | AndroidApp output = compileWithR8("-keepdirectories", minify, dataResourceConsumer); | 
|  | assertEquals(getDataResources().size(), dataResourceConsumer.size()); | 
|  | checkResourceNames(dataResourceConsumer.getResourceNames(), pathForThisPackage(output)); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testKeepSome1() throws Exception { | 
|  | CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer(); | 
|  | compileWithR8("-keepdirectories org/example/*", minify, dataResourceConsumer); | 
|  | Set<String> expected = | 
|  | getDataResources().stream() | 
|  | .map(DataResource::getName) | 
|  | .filter(name -> name.startsWith("org/example/")) | 
|  | .collect(Collectors.toSet()); | 
|  | assertEquals(expected, dataResourceConsumer.getResourceNames()); | 
|  | } | 
|  |  | 
|  | private int numberOfPathSeparators(String s) { | 
|  | return (int) s.chars().filter(c -> c == '/').count(); | 
|  | } | 
|  |  | 
|  | class PrefixMapper implements Function<String, String> { | 
|  | private final String original; | 
|  | private final String mapped; | 
|  |  | 
|  | PrefixMapper(String original, String mapped) { | 
|  | this.original = original; | 
|  | this.mapped = mapped; | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public String apply(String s) { | 
|  | if (s.startsWith(original)) { | 
|  | return mapped + s.substring(original.length()); | 
|  | } else { | 
|  | return s; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void testKeepSome2() throws Exception { | 
|  | CustomDataResourceConsumer dataResourceConsumer = new CustomDataResourceConsumer(); | 
|  | AndroidApp output = | 
|  | compileWithR8( | 
|  | "-keepdirectories org/*," + pathForThisPackage() + "/**", minify, dataResourceConsumer); | 
|  | Set<String> expected1 = | 
|  | getDataResources().stream() | 
|  | .map(DataResource::getName) | 
|  | .filter(name -> name.startsWith("org/")) | 
|  | .filter(name -> numberOfPathSeparators(name) < 2) | 
|  | .collect(Collectors.toSet()); | 
|  | PrefixMapper prefixMapper = new PrefixMapper(pathForThisPackage(), pathForThisPackage(output)); | 
|  | Set<String> expected2 = | 
|  | getDataResources().stream() | 
|  | .map(DataResource::getName) | 
|  | .filter(name -> name.startsWith(pathForThisPackage() + "/")) | 
|  | .map(prefixMapper) | 
|  | .collect(Collectors.toSet()); | 
|  | assertEquals(Sets.union(expected1, expected2), dataResourceConsumer.getResourceNames()); | 
|  | } | 
|  |  | 
|  | private AndroidApp compileWithR8( | 
|  | String proguardConfig, boolean allowMinification, DataResourceConsumer dataResourceConsumer) | 
|  | throws CompilationFailedException { | 
|  | String r = | 
|  | "-keep" | 
|  | + (allowMinification ? ",allowobfuscation" : "") | 
|  | + " class " | 
|  | + Main.class.getCanonicalName(); | 
|  | R8Command command = | 
|  | ToolHelper.prepareR8CommandBuilder(getAndroidApp(), emptyConsumer(backend)) | 
|  | .addProguardConfiguration( | 
|  | ImmutableList.of(proguardConfig, "-printmapping", r), Origin.unknown()) | 
|  | .addLibraryFiles(runtimeJar(backend)) | 
|  | .build(); | 
|  | return ToolHelper.runR8( | 
|  | command, options -> options.dataResourceConsumer = dataResourceConsumer); | 
|  | } | 
|  |  | 
|  | private AndroidApp getAndroidApp() { | 
|  | AndroidApp.Builder builder = AndroidApp.builder(); | 
|  | builder.addProgramFiles(ToolHelper.getClassFileForTestClass(Main.class)); | 
|  | getDataResources().forEach(builder::addDataResource); | 
|  | return builder.build(); | 
|  | } | 
|  |  | 
|  | protected static class CustomDataResourceConsumer implements DataResourceConsumer { | 
|  |  | 
|  | private final Set<String> resourceNames = Sets.newHashSet(); | 
|  |  | 
|  | @Override | 
|  | public void accept(DataDirectoryResource directory, DiagnosticsHandler diagnosticsHandler) { | 
|  | assertFalse(resourceNames.contains(directory.getName())); | 
|  | resourceNames.add(directory.getName()); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void accept(DataEntryResource file, DiagnosticsHandler diagnosticsHandler) { | 
|  | throw new Unreachable(); | 
|  | } | 
|  |  | 
|  | @Override | 
|  | public void finished(DiagnosticsHandler handler) {} | 
|  |  | 
|  | public boolean isPresent(String name) { | 
|  | return resourceNames.contains(name); | 
|  | } | 
|  |  | 
|  | public Set<String> getResourceNames() { | 
|  | return resourceNames; | 
|  | } | 
|  |  | 
|  | public int size() { | 
|  | return resourceNames.size(); | 
|  | } | 
|  | } | 
|  |  | 
|  | private List<DataResource> getDataResources() { | 
|  | return ImmutableList.of( | 
|  | DataDirectoryResource.fromName("org", Origin.unknown()), | 
|  | DataDirectoryResource.fromName("org/example", Origin.unknown()), | 
|  | DataDirectoryResource.fromName("org/example/test", Origin.unknown()), | 
|  | DataDirectoryResource.fromName(pathForThisPackage(), Origin.unknown()), | 
|  | DataDirectoryResource.fromName(pathForThisPackage() + "/subpackage1", Origin.unknown()), | 
|  | DataDirectoryResource.fromName(pathForThisPackage() + "/subpackage2", Origin.unknown()), | 
|  | DataDirectoryResource.fromName( | 
|  | pathForThisPackage() + "/subpackage2/subpackage", Origin.unknown())); | 
|  | } | 
|  | } |