blob: 3c24de463469f32483e4e35150da9dfbfc08da1a [file] [log] [blame]
/*
* Copyright 2016 Google Inc. All Rights Reserved.
*
* 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.google.turbine.deps;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.truth.Truth.assertThat;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.io.ByteStreams;
import com.google.turbine.bytecode.ClassFile;
import com.google.turbine.bytecode.ClassFile.InnerClass;
import com.google.turbine.bytecode.ClassReader;
import com.google.turbine.main.Main;
import com.google.turbine.options.TurbineOptions;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
public abstract class AbstractTransitiveTest {
protected abstract Path runTurbine(ImmutableList<Path> sources, ImmutableList<Path> classpath)
throws IOException;
protected static final ImmutableList<Path> BOOTCLASSPATH =
ImmutableList.of(Paths.get(System.getProperty("java.home")).resolve("lib/rt.jar"));
@Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();
class SourceBuilder {
private final Path lib;
private final ImmutableList.Builder<Path> sources = ImmutableList.builder();
SourceBuilder() throws IOException {
lib = temporaryFolder.newFolder().toPath();
}
SourceBuilder addSourceLines(String name, String... lines) throws IOException {
Path path = lib.resolve(name);
Files.createDirectories(path.getParent());
Files.write(path, Arrays.asList(lines), UTF_8);
sources.add(path);
return this;
}
ImmutableList<Path> build() {
return sources.build();
}
}
private Map<String, byte[]> readJar(Path libb) throws IOException {
Map<String, byte[]> jarEntries = new LinkedHashMap<>();
try (JarFile jf = new JarFile(libb.toFile())) {
Enumeration<JarEntry> entries = jf.entries();
while (entries.hasMoreElements()) {
JarEntry je = entries.nextElement();
jarEntries.put(je.getName(), ByteStreams.toByteArray(jf.getInputStream(je)));
}
}
return jarEntries;
}
@Test
public void transitive() throws Exception {
Path liba =
runTurbine(
new SourceBuilder()
.addSourceLines(
"a/A.java",
"package a;",
"import java.util.Map;",
"public class A {",
" public @interface Anno {",
" int x() default 42;",
" }",
" public static class Inner {}",
" public static final int CONST = 42;",
" public int mutable = 42;",
" public Map.Entry<String, String> f(Map<String, String> m) {",
" return m.entrySet().iterator().next();",
" }",
"}")
.build(),
ImmutableList.of());
Path libb =
runTurbine(
new SourceBuilder()
.addSourceLines("b/B.java", "package b;", "public class B extends a.A {}")
.build(),
ImmutableList.of(liba));
// libb repackages A, and any member types
assertThat(readJar(libb).keySet())
.containsExactly(
"b/B.class",
"META-INF/TRANSITIVE/a/A.class",
"META-INF/TRANSITIVE/a/A$Anno.class",
"META-INF/TRANSITIVE/a/A$Inner.class");
ClassFile a = ClassReader.read(readJar(libb).get("META-INF/TRANSITIVE/a/A.class"));
// methods and non-constant fields are removed
assertThat(getOnlyElement(a.fields()).name()).isEqualTo("CONST");
assertThat(a.methods()).isEmpty();
assertThat(Iterables.transform(a.innerClasses(), InnerClass::innerClass))
.containsExactly("a/A$Anno", "a/A$Inner");
// annotation interface methods are preserved
assertThat(ClassReader.read(readJar(libb).get("META-INF/TRANSITIVE/a/A$Anno.class")).methods())
.hasSize(1);
// A class that references members of the transitive supertype A by simple name
// compiles cleanly against the repackaged version of A.
// Explicitly use turbine; javac-turbine doesn't support direct-classpath compilations.
Path libc = temporaryFolder.newFolder().toPath().resolve("out.jar");
List<String> sources =
new SourceBuilder()
.addSourceLines(
"c/C.java",
"package c;",
"public class C extends b.B {",
" @Anno(x = 2) static final Inner i; // a.A$Inner ",
" static final int X = CONST; // a.A#CONST",
"}")
.build()
.stream()
.map(Path::toString)
.collect(toImmutableList());
boolean ok =
Main.compile(
TurbineOptions.builder()
.addSources(sources)
.addClassPathEntries(
ImmutableList.of(libb).stream().map(Path::toString).collect(toImmutableList()))
.addBootClassPathEntries(Iterables.transform(BOOTCLASSPATH, Path::toString))
.setOutput(libc.toString())
.build());
assertThat(ok).isTrue();
assertThat(readJar(libc).keySet())
.containsExactly(
"c/C.class",
"META-INF/TRANSITIVE/b/B.class",
"META-INF/TRANSITIVE/a/A.class",
"META-INF/TRANSITIVE/a/A$Anno.class",
"META-INF/TRANSITIVE/a/A$Inner.class");
}
@Test
public void anonymous() throws Exception {
Path liba = temporaryFolder.newFolder().toPath().resolve("out.jar");
try (OutputStream os = Files.newOutputStream(liba);
JarOutputStream jos = new JarOutputStream(os)) {
{
jos.putNextEntry(new JarEntry("a/A.class"));
ClassWriter cw = new ClassWriter(0);
cw.visit(52, Opcodes.ACC_SUPER | Opcodes.ACC_PUBLIC, "a/A", null, "java/lang/Object", null);
cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
cw.visitInnerClass("a/A$1", "a/A", null, Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC);
cw.visitInnerClass("a/A$I", "a/A", "I", Opcodes.ACC_STATIC);
jos.write(cw.toByteArray());
}
{
jos.putNextEntry(new JarEntry("a/A$1.class"));
ClassWriter cw = new ClassWriter(0);
cw.visit(
52, Opcodes.ACC_SUPER | Opcodes.ACC_PUBLIC, "a/A$1", null, "java/lang/Object", null);
cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
cw.visitInnerClass("a/A$1", "a/A", "I", Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC);
jos.write(cw.toByteArray());
}
{
jos.putNextEntry(new JarEntry("a/A$I.class"));
ClassWriter cw = new ClassWriter(0);
cw.visit(
52, Opcodes.ACC_SUPER | Opcodes.ACC_PUBLIC, "a/A$I", null, "java/lang/Object", null);
cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
cw.visitInnerClass("a/A$I", "a/A", "I", Opcodes.ACC_STATIC);
jos.write(cw.toByteArray());
}
}
Path libb =
runTurbine(
new SourceBuilder()
.addSourceLines(
"b/B.java", //
"package b;",
"public class B extends a.A {}")
.build(),
ImmutableList.of(liba));
// libb repackages A and any named member types
assertThat(readJar(libb).keySet())
.containsExactly(
"b/B.class", "META-INF/TRANSITIVE/a/A.class", "META-INF/TRANSITIVE/a/A$I.class");
}
@Test
public void childClass() throws Exception {
Path liba =
runTurbine(
new SourceBuilder()
.addSourceLines(
"a/S.java", //
"package a;",
"public class S {}")
.addSourceLines(
"a/A.java", //
"package a;",
"public class A {",
" public class I extends S {}",
"}")
.build(),
ImmutableList.of());
Path libb =
runTurbine(
new SourceBuilder()
.addSourceLines(
"b/B.java", //
"package b;",
"public class B extends a.A {",
" class I extends a.A.I {",
" }",
"}")
.build(),
ImmutableList.of(liba));
assertThat(readJar(libb).keySet())
.containsExactly(
"b/B.class",
"b/B$I.class",
"META-INF/TRANSITIVE/a/A.class",
"META-INF/TRANSITIVE/a/A$I.class",
"META-INF/TRANSITIVE/a/S.class");
}
}