From fd61c8ffe48dd4f2bf89353b68b8a73b4beb9930 Mon Sep 17 00:00:00 2001 From: Wagyourtail Date: Thu, 9 Dec 2021 23:23:09 -0700 Subject: [PATCH 01/28] Experimental FG2 Support --- .../providers/forge/ForgeProvider.java | 11 ++ .../providers/forge/ForgeUserdevProvider.java | 156 ++++++++++++------ .../providers/forge/McpConfigProvider.java | 92 +++++++++++ .../providers/forge/PatchProvider.java | 35 ++-- .../providers/forge/SrgProvider.java | 14 +- .../mappings/MappingsProviderImpl.java | 2 +- .../net/fabricmc/loom/util/srg/MCPReader.java | 13 +- 7 files changed, 249 insertions(+), 74 deletions(-) diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeProvider.java index 9e1e4a7a5..f5e103204 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeProvider.java @@ -37,6 +37,8 @@ public class ForgeProvider extends DependencyProvider { private File globalCache; private File projectCache; + private boolean fg2 = false; + public ForgeProvider(Project project) { super(project); } @@ -75,6 +77,15 @@ public String getTargetConfig() { return Constants.Configurations.FORGE; } + + public boolean isFG2() { + return fg2; + } + + public void setFg2(boolean fg2) { + this.fg2 = fg2; + } + public static final class ForgeVersion { private final String combined; private final String minecraftVersion; diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java index fecfa4d37..278b6f6c3 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java @@ -71,6 +71,7 @@ public class ForgeUserdevProvider extends DependencyProvider { private File userdevJar; private JsonObject json; + private Consumer postPopulationScheduler; public ForgeUserdevProvider(Project project) { @@ -99,7 +100,13 @@ public void provide(DependencyInfo dependency, Consumer postPopulation Files.copy(resolved.toPath(), userdevJar.toPath(), StandardCopyOption.REPLACE_EXISTING); try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + resolved.toURI()), ImmutableMap.of("create", false))) { - Files.copy(fs.getPath("config.json"), configJson, StandardCopyOption.REPLACE_EXISTING); + if (Files.exists(fs.getPath("config.json"))) { + //fg3+ + Files.copy(fs.getPath("config.json"), configJson, StandardCopyOption.REPLACE_EXISTING); + } else { + //fg2 + Files.copy(fs.getPath("dev.json"), configJson, StandardCopyOption.REPLACE_EXISTING); + } } } @@ -107,79 +114,124 @@ public void provide(DependencyInfo dependency, Consumer postPopulation json = new Gson().fromJson(reader, JsonObject.class); } - addDependency(json.get("mcp").getAsString(), Constants.Configurations.MCP_CONFIG); - addDependency(json.get("mcp").getAsString(), Constants.Configurations.SRG); - addDependency(json.get("universal").getAsString(), Constants.Configurations.FORGE_UNIVERSAL); + boolean fg2 = !json.has("mcp"); + getExtension().getForgeProvider().setFg2(fg2); - for (JsonElement lib : json.get("libraries").getAsJsonArray()) { - Dependency dep = null; - if (lib.getAsString().startsWith("org.spongepowered:mixin:")) { - if (PropertyUtil.getAndFinalize(getExtension().getForge().getUseCustomMixin())) { - if (lib.getAsString().contains("0.8.2")) { - dep = addDependency("net.fabricmc:sponge-mixin:0.8.2+build.24", Constants.Configurations.FORGE_DEPENDENCIES); - } else { - dep = addDependency("dev.architectury:mixin-patched" + lib.getAsString().substring(lib.getAsString().lastIndexOf(":")) + ".+", Constants.Configurations.FORGE_DEPENDENCIES); - } - } + if (fg2) { + getProject().getLogger().info("FG2 Userdev, using default mcp_config/universal..."); + + String defaultMCPPath = "de.oceanlabs.mcp:mcp:" + getExtension().getMinecraftProvider().minecraftVersion() + ":srg@zip"; + if (getExtension().getMinecraftProvider().minecraftVersion().equals("1.12.2")) { + defaultMCPPath = "de.oceanlabs.mcp:mcp_config:" + getExtension().getMinecraftProvider().minecraftVersion() + "@zip"; } + String defaultUniversalPath = "net.minecraftforge:forge:" + dependency.getResolvedVersion() + ":universal"; + + getProject().getLogger().info("Using default MCP path: " + defaultMCPPath); + getProject().getLogger().info("Using default Universal path: " + defaultUniversalPath); + + addDependency(defaultMCPPath, Constants.Configurations.MCP_CONFIG); + addDependency(defaultMCPPath, Constants.Configurations.SRG); + addDependency(defaultUniversalPath, Constants.Configurations.FORGE_UNIVERSAL); - if (dep == null) { - dep = addDependency(lib.getAsString(), Constants.Configurations.FORGE_DEPENDENCIES); + for (JsonElement lib : json.getAsJsonArray("libraries")) { + JsonObject libObj = lib.getAsJsonObject(); + + Dependency dep = addDependency(libObj.get("name").getAsString(), Constants.Configurations.FORGE_DEPENDENCIES); + + if (libObj.get("name").getAsString().split(":").length < 4) { + ((ModuleDependency) dep).attributes(attributes -> { + attributes.attribute(transformed, true); + }); + } } - if (lib.getAsString().split(":").length < 4) { - ((ModuleDependency) dep).attributes(attributes -> { - attributes.attribute(transformed, true); - }); + //TODO: hard-code fg2 run configs + } else { + getProject().getLogger().info("FG3+ Userdev"); + + addDependency(json.get("mcp").getAsString(), Constants.Configurations.MCP_CONFIG); + addDependency(json.get("mcp").getAsString(), Constants.Configurations.SRG); + addDependency(json.get("universal").getAsString(), Constants.Configurations.FORGE_UNIVERSAL); + + + for (JsonElement lib : json.get("libraries").getAsJsonArray()) { + Dependency dep = null; + + if (lib.getAsString().startsWith("org.spongepowered:mixin:")) { + if (PropertyUtil.getAndFinalize(getExtension().getForge().getUseCustomMixin())) { + if (lib.getAsString().contains("0.8.2")) { + dep = addDependency( + "net.fabricmc:sponge-mixin:0.8.2+build.24", + Constants.Configurations.FORGE_DEPENDENCIES + ); + } else { + dep = addDependency( + "dev.architectury:mixin-patched" + + lib.getAsString().substring(lib.getAsString().lastIndexOf(":")) + ".+", + Constants.Configurations.FORGE_DEPENDENCIES + ); + } + } + } + + if (dep == null) { + dep = addDependency(lib.getAsString(), Constants.Configurations.FORGE_DEPENDENCIES); + } + + if (lib.getAsString().split(":").length < 4) { + ((ModuleDependency) dep).attributes(attributes -> { + attributes.attribute(transformed, true); + }); + } } - } - // TODO: Should I copy the patches from here as well? - // That'd require me to run the "MCP environment" fully up to merging. - for (Map.Entry entry : json.getAsJsonObject("runs").entrySet()) { - LaunchProviderSettings launchSettings = getExtension().getLaunchConfigs().findByName(entry.getKey()); - RunConfigSettings settings = getExtension().getRunConfigs().findByName(entry.getKey()); - JsonObject value = entry.getValue().getAsJsonObject(); - - if (launchSettings != null) { - launchSettings.evaluateLater(() -> { - if (value.has("args")) { - launchSettings.arg(StreamSupport.stream(value.getAsJsonArray("args").spliterator(), false) + // TODO: Should I copy the patches from here as well? + // That'd require me to run the "MCP environment" fully up to merging. + for (Map.Entry entry : json.getAsJsonObject("runs").entrySet()) { + LaunchProviderSettings launchSettings = getExtension().getLaunchConfigs().findByName(entry.getKey()); + RunConfigSettings settings = getExtension().getRunConfigs().findByName(entry.getKey()); + JsonObject value = entry.getValue().getAsJsonObject(); + + if (launchSettings != null) { + launchSettings.evaluateLater(() -> { + if (value.has("args")) { + launchSettings.arg(StreamSupport.stream(value.getAsJsonArray("args").spliterator(), false) .map(JsonElement::getAsString) .map(this::processTemplates) .collect(Collectors.toList())); - } + } - if (value.has("props")) { - for (Map.Entry props : value.getAsJsonObject("props").entrySet()) { - String string = processTemplates(props.getValue().getAsString()); + if (value.has("props")) { + for (Map.Entry props : value.getAsJsonObject("props").entrySet()) { + String string = processTemplates(props.getValue().getAsString()); - launchSettings.property(props.getKey(), string); + launchSettings.property(props.getKey(), string); + } } - } - }); - } + }); + } - if (settings != null) { - settings.evaluateLater(() -> { - settings.defaultMainClass(value.getAsJsonPrimitive("main").getAsString()); + if (settings != null) { + settings.evaluateLater(() -> { + settings.defaultMainClass(value.getAsJsonPrimitive("main").getAsString()); - if (value.has("jvmArgs")) { - settings.vmArgs(StreamSupport.stream(value.getAsJsonArray("jvmArgs").spliterator(), false) + if (value.has("jvmArgs")) { + settings.vmArgs(StreamSupport.stream(value.getAsJsonArray("jvmArgs").spliterator(), false) .map(JsonElement::getAsString) .map(this::processTemplates) .collect(Collectors.toList())); - } + } - if (value.has("env")) { - for (Map.Entry env : value.getAsJsonObject("env").entrySet()) { - String string = processTemplates(env.getValue().getAsString()); + if (value.has("env")) { + for (Map.Entry env : value.getAsJsonObject("env").entrySet()) { + String string = processTemplates(env.getValue().getAsString()); - settings.envVariables.put(env.getKey(), string); + settings.envVariables.put(env.getKey(), string); + } } - } - }); + }); + } } } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java index 7404cc0a6..75f565af6 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java @@ -58,16 +58,31 @@ public class McpConfigProvider extends DependencyProvider { private String mappingsPath; private RemapAction remapAction; + private boolean isSRG = false; + public McpConfigProvider(Project project) { super(project); } + public boolean isSRG() { + return isSRG; + } + @Override public void provide(DependencyInfo dependency, Consumer postPopulationScheduler) throws Exception { init(dependency.getDependency().getVersion()); Path mcpZip = dependency.resolveFile().orElseThrow(() -> new RuntimeException("Could not resolve MCPConfig")).toPath(); + + if (getExtension().getForgeProvider().isFG2()) { + official = false; + mappingsPath = ZipUtils.contains(mcpZip, "joined.srg") ? "joined.srg" : "config/joined.tsrg"; + isSRG = mappingsPath.endsWith(".srg"); + remapAction = new FG2RemapAction(getProject()); + return; + } + if (!Files.exists(mcp) || !Files.exists(configJson) || isRefreshDeps()) { Files.copy(mcpZip, mcp, StandardCopyOption.REPLACE_EXISTING); Files.write(configJson, ZipUtils.unpack(mcp, "config.json"), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); @@ -147,6 +162,83 @@ public interface RemapAction { List getArgs(Path input, Path output, Path mappings, FileCollection libraries); } + public static class FG2RemapAction implements RemapAction { + private final Project project; + private final String name; + private final File mainClasspath; + private final FileCollection classpath; + private final List args; + private boolean hasLibraries; + + public FG2RemapAction(Project project) { + this.project = project; + this.name = "net.md-5:SpecialSource:1.8.3:shaded"; + this.mainClasspath = DependencyDownloader.download(project, this.name, false, true) + .getSingleFile(); + this.classpath = DependencyDownloader.download(project, this.name, true, true); + this.args = List.of( + "--in-jar", + "{input}", + "--out-jar", + "{output}", + "--srg-in", + "{mappings}", + "--kill-source" + ); + } + + @Override + public FileCollection getClasspath() { + return classpath; + } + + @Override + public String getMainClass() { + try { + byte[] manifestBytes = ZipUtils.unpackNullable(mainClasspath.toPath(), "META-INF/MANIFEST.MF"); + + if (manifestBytes == null) { + throw new RuntimeException("Could not find MANIFEST.MF in " + mainClasspath + "!"); + } + + Manifest manifest = new Manifest(new ByteArrayInputStream(manifestBytes)); + Attributes attributes = manifest.getMainAttributes(); + String value = attributes.getValue(Attributes.Name.MAIN_CLASS); + + if (value == null) { + throw new RuntimeException("Could not find main class in " + mainClasspath + "!"); + } else { + return value; + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public List getArgs(Path input, Path output, Path mappings, FileCollection libraries) { + List args = this.args.stream() + .map(str -> { + return switch (str) { + case "{input}" -> input.toAbsolutePath().toString(); + case "{output}" -> output.toAbsolutePath().toString(); + case "{mappings}" -> mappings.toAbsolutePath().toString(); + default -> str; + }; + }) + .collect(Collectors.toList()); + + if (hasLibraries) { + for (File file : libraries) { + args.add("-e=" + file.getAbsolutePath()); + } + } + + return args; + } + + } + public static class ConfigDefinedRemapAction implements RemapAction { private final Project project; private final String name; diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/PatchProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/PatchProvider.java index d49ae8b49..554543551 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/PatchProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/PatchProvider.java @@ -24,14 +24,11 @@ package net.fabricmc.loom.configuration.providers.forge; +import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; import java.net.URI; -import java.nio.file.FileSystem; -import java.nio.file.FileSystems; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; +import java.nio.file.*; import java.util.function.Consumer; import com.google.common.collect.ImmutableMap; @@ -53,14 +50,30 @@ public PatchProvider(Project project) { public void provide(DependencyInfo dependency, Consumer postPopulationScheduler) throws Exception { init(dependency.getDependency().getVersion()); - if (Files.notExists(clientPatches) || Files.notExists(serverPatches) || isRefreshDeps()) { - getProject().getLogger().info(":extracting forge patches"); + if (getExtension().getForgeProvider().isFG2()) { + File forge = getExtension().getForgeUserdevProvider().getUserdevJar(); - Path installerJar = dependency.resolveFile().orElseThrow(() -> new RuntimeException("Could not resolve Forge installer")).toPath(); + if (Files.notExists(clientPatches) || isRefreshDeps()) { + getProject().getLogger().info(":extracting forge patches"); - try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + installerJar.toUri()), ImmutableMap.of("create", false))) { - Files.copy(fs.getPath("data", "client.lzma"), clientPatches, StandardCopyOption.REPLACE_EXISTING); - Files.copy(fs.getPath("data", "server.lzma"), serverPatches, StandardCopyOption.REPLACE_EXISTING); + try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + forge.toURI()), ImmutableMap.of("create", false))) { + Files.copy(fs.getPath("devbinpatches.pack.lzma"), clientPatches, StandardCopyOption.REPLACE_EXISTING); + Files.copy(fs.getPath("devbinpatches.pack.lzma"), serverPatches, StandardCopyOption.REPLACE_EXISTING); + } + + } + } else { + + if (Files.notExists(clientPatches) || Files.notExists(serverPatches) || isRefreshDeps()) { + getProject().getLogger().info(":extracting forge patches"); + + Path installerJar = dependency.resolveFile().orElseThrow(() -> new RuntimeException( + "Could not resolve Forge installer")).toPath(); + + try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + installerJar.toUri()), ImmutableMap.of("create", false))) { + Files.copy(fs.getPath("data", "client.lzma"), clientPatches, StandardCopyOption.REPLACE_EXISTING); + Files.copy(fs.getPath("data", "server.lzma"), serverPatches, StandardCopyOption.REPLACE_EXISTING); + } } } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java index 32a12dc62..78a58c7f7 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java @@ -24,13 +24,7 @@ package net.fabricmc.loom.configuration.providers.forge; -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.File; -import java.io.IOException; -import java.io.PrintStream; -import java.io.StringReader; -import java.io.UncheckedIOException; +import java.io.*; import java.net.URI; import java.nio.charset.StandardCharsets; import java.nio.file.FileSystem; @@ -87,7 +81,11 @@ public void provide(DependencyInfo dependency, Consumer postPopulation Path srgZip = dependency.resolveFile().orElseThrow(() -> new RuntimeException("Could not resolve srg")).toPath(); try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + srgZip.toUri()), ImmutableMap.of("create", false))) { - Files.copy(fs.getPath("config", "joined.tsrg"), srg, StandardCopyOption.REPLACE_EXISTING); + try { + Files.copy(fs.getPath("config", "joined.tsrg"), srg, StandardCopyOption.REPLACE_EXISTING); + } catch (FileNotFoundException e) { + Files.copy(fs.getPath("joined.srg"), srg, StandardCopyOption.REPLACE_EXISTING); + } } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java index 23bf71c83..bdfecba44 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java @@ -343,7 +343,7 @@ private void readAndMergeMCP(Path mcpJar, Consumer postPopulationSched } Path srgPath = getRawSrgFile(); - TinyFile file = new MCPReader(intermediaryTinyPath, srgPath).read(mcpJar); + TinyFile file = new MCPReader(intermediaryTinyPath, srgPath, getExtension().getMcpConfigProvider().isSRG()).read(mcpJar); TinyV2Writer.write(file, tinyMappings); } diff --git a/src/main/java/net/fabricmc/loom/util/srg/MCPReader.java b/src/main/java/net/fabricmc/loom/util/srg/MCPReader.java index 96382bb5d..3e2729813 100644 --- a/src/main/java/net/fabricmc/loom/util/srg/MCPReader.java +++ b/src/main/java/net/fabricmc/loom/util/srg/MCPReader.java @@ -44,6 +44,7 @@ import com.opencsv.exceptions.CsvValidationException; import org.apache.commons.io.IOUtils; import org.cadixdev.lorenz.MappingSet; +import org.cadixdev.lorenz.io.srg.SrgReader; import org.cadixdev.lorenz.io.srg.tsrg.TSrgReader; import org.cadixdev.lorenz.model.ClassMapping; import org.cadixdev.lorenz.model.FieldMapping; @@ -66,9 +67,12 @@ public class MCPReader { private final Path intermediaryTinyPath; private final Path srgTsrgPath; - public MCPReader(Path intermediaryTinyPath, Path srgTsrgPath) { + private final boolean isSrg; + + public MCPReader(Path intermediaryTinyPath, Path srgTsrgPath, boolean isSrg) { this.intermediaryTinyPath = intermediaryTinyPath; this.srgTsrgPath = srgTsrgPath; + this.isSrg = isSrg; } public TinyFile read(Path mcpJar) throws IOException { @@ -191,7 +195,12 @@ private Map readSrg() throws IOException { if (content.startsWith("tsrg2")) { readTsrg2(tokens, content); } else { - MappingSet mappingSet = new TSrgReader(new StringReader(content)).read(); + MappingSet mappingSet; + if (isSrg) { + mappingSet = new SrgReader(new StringReader(content)).read(); + } else { + mappingSet = new TSrgReader(new StringReader(content)).read(); + } for (TopLevelClassMapping classMapping : mappingSet.getTopLevelClassMappings()) { appendClass(tokens, classMapping); From 76051bba7664cdac8e8cb54b458347b1928073bc Mon Sep 17 00:00:00 2001 From: Wagyourtail Date: Thu, 9 Dec 2021 23:55:20 -0700 Subject: [PATCH 02/28] fix try/catch for srg --- .../loom/configuration/providers/forge/SrgProvider.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java index 78a58c7f7..061f85130 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java @@ -27,12 +27,7 @@ import java.io.*; import java.net.URI; import java.nio.charset.StandardCharsets; -import java.nio.file.FileSystem; -import java.nio.file.FileSystems; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.nio.file.StandardOpenOption; +import java.nio.file.*; import java.util.HashMap; import java.util.Map; import java.util.function.Consumer; @@ -83,7 +78,7 @@ public void provide(DependencyInfo dependency, Consumer postPopulation try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + srgZip.toUri()), ImmutableMap.of("create", false))) { try { Files.copy(fs.getPath("config", "joined.tsrg"), srg, StandardCopyOption.REPLACE_EXISTING); - } catch (FileNotFoundException e) { + } catch (NoSuchFileException e) { Files.copy(fs.getPath("joined.srg"), srg, StandardCopyOption.REPLACE_EXISTING); } } From 8efcf911491902b2d534e441eb5994e5688007fb Mon Sep 17 00:00:00 2001 From: Wagyourtail Date: Fri, 10 Dec 2021 00:03:46 -0700 Subject: [PATCH 03/28] still need to package pack200 somehow... --- .../providers/forge/MinecraftPatchedProvider.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java index fadf7d337..8389f3a28 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java @@ -631,7 +631,8 @@ private void patchJars(File clean, File output, Path patches) throws IOException ConsoleTool.main(new String[] { "--clean", clean.getAbsolutePath(), "--output", output.getAbsolutePath(), - "--apply", patches.toAbsolutePath().toString() + "--apply", patches.toAbsolutePath().toString(), + "--pack200" }); try { From ccb215d2b325269320f9ad16694d7811e096e58a Mon Sep 17 00:00:00 2001 From: Wagyourtail Date: Sat, 11 Dec 2021 00:07:29 -0700 Subject: [PATCH 04/28] "working" (wrong checksum) --- build.gradle | 3 +- .../providers/forge/McpConfigProvider.java | 5 ++- .../forge/MinecraftPatchedProvider.java | 34 +++++++++++++------ .../loom/util/srg/SpecialSourceExecutor.java | 20 +++++++++-- 4 files changed, 47 insertions(+), 15 deletions(-) diff --git a/build.gradle b/build.gradle index 2cc27e478..d800caaa4 100644 --- a/build.gradle +++ b/build.gradle @@ -48,6 +48,7 @@ repositories { excludeGroupByRegex "org\\.eclipse\\.?.*" } } + maven { url "https://jitpack.io" } mavenLocal() } @@ -113,7 +114,7 @@ dependencies { // Forge patches implementation ('net.minecraftforge:installertools:1.2.0') - implementation ('net.minecraftforge:binarypatcher:1.1.1') + implementation ('com.github.wagyourtail:BinaryPatcher:383bae7aa3') implementation ('org.cadixdev:lorenz:0.5.3') implementation ('org.cadixdev:lorenz-asm:0.5.3') implementation ('de.oceanlabs.mcp:mcinjector:3.8.0') diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java index 75f565af6..0ab287a77 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java @@ -80,6 +80,9 @@ public void provide(DependencyInfo dependency, Consumer postPopulation mappingsPath = ZipUtils.contains(mcpZip, "joined.srg") ? "joined.srg" : "config/joined.tsrg"; isSRG = mappingsPath.endsWith(".srg"); remapAction = new FG2RemapAction(getProject()); + if (!Files.exists(mcp) || isRefreshDeps()) { + Files.copy(mcpZip, mcp, StandardCopyOption.REPLACE_EXISTING); + } return; } @@ -172,7 +175,7 @@ public static class FG2RemapAction implements RemapAction { public FG2RemapAction(Project project) { this.project = project; - this.name = "net.md-5:SpecialSource:1.8.3:shaded"; + this.name = "net.md-5:SpecialSource:1.7.4:shaded"; this.mainClasspath = DependencyDownloader.download(project, this.name, false, true) .getSingleFile(); this.classpath = DependencyDownloader.download(project, this.name, true, true); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java index 8389f3a28..52c34c931 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java @@ -341,15 +341,17 @@ private void createSrgJars(Logger logger) throws Exception { produceSrgJar(getExtension().isForgeAndOfficial(), minecraftProvider.minecraftClientJar.toPath(), minecraftProvider.getMinecraftServerJar().toPath()); } - private void produceSrgJar(boolean official, Path clientJar, Path serverJar) throws IOException { + private void produceSrgJar(boolean official, Path clientJar, Path serverJar) throws Exception { Path tmpSrg = getToSrgMappings(); Set mcLibs = getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_DEPENDENCIES).resolve(); - ThreadingUtils.run(() -> { - Files.copy(SpecialSourceExecutor.produceSrgJar(getExtension().getMcpConfigProvider().getRemapAction(), getProject(), "client", mcLibs, clientJar, tmpSrg), minecraftClientSrgJar.toPath()); - }, () -> { - Files.copy(SpecialSourceExecutor.produceSrgJar(getExtension().getMcpConfigProvider().getRemapAction(), getProject(), "server", mcLibs, serverJar, tmpSrg), minecraftServerSrgJar.toPath()); - }); + boolean isSrg = getExtension().getMcpConfigProvider().isSRG(); + +// ThreadingUtils.run(() -> { + Files.copy(SpecialSourceExecutor.produceSrgJar(getExtension().getMcpConfigProvider().getRemapAction(), getProject(), "client", mcLibs, clientJar, tmpSrg, isSrg), minecraftClientSrgJar.toPath()); +// }, () -> { + Files.copy(SpecialSourceExecutor.produceSrgJar(getExtension().getMcpConfigProvider().getRemapAction(), getProject(), "server", mcLibs, serverJar, tmpSrg, isSrg), minecraftServerSrgJar.toPath()); +// }); } private Path getToSrgMappings() throws IOException { @@ -605,7 +607,9 @@ private void patchJars(Logger logger) throws IOException { PatchProvider patchProvider = getExtension().getPatchProvider(); patchJars(minecraftClientSrgJar, minecraftClientPatchedSrgJar, patchProvider.clientPatches); - patchJars(minecraftServerSrgJar, minecraftServerPatchedSrgJar, patchProvider.serverPatches); + if (!getExtension().getMcpConfigProvider().isSRG()) { + patchJars(minecraftServerSrgJar, minecraftServerPatchedSrgJar, patchProvider.serverPatches); + } ThreadingUtils.run(Environment.values(), environment -> { copyMissingClasses(environment.srgJar.apply(this), environment.patchedSrgJar.apply(this)); @@ -628,12 +632,22 @@ private void patchJars(File clean, File output, Path patches) throws IOException // Failed to replace logger filter, just ignore } - ConsoleTool.main(new String[] { + if (getExtension().getMcpConfigProvider().isSRG()) { + + ConsoleTool.main(new String[] { "--clean", clean.getAbsolutePath(), "--output", output.getAbsolutePath(), "--apply", patches.toAbsolutePath().toString(), - "--pack200" - }); + "--legacy" + }); + + } else { + ConsoleTool.main(new String[] { + "--clean", clean.getAbsolutePath(), + "--output", output.getAbsolutePath(), + "--apply", patches.toAbsolutePath().toString() + }); + } try { System.setOut(previous); diff --git a/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java b/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java index 59392e944..a54ef7a90 100644 --- a/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java +++ b/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java @@ -56,18 +56,30 @@ private static String trimLeadingSlash(String string) { return string; } - public static Path produceSrgJar(RemapAction remapAction, Project project, String side, Set mcLibs, Path officialJar, Path mappings) + public static Path produceSrgJar(RemapAction remapAction, Project project, String side, Set mcLibs, Path officialJar, Path mappings, boolean isSrg) throws Exception { - Set filter = Files.readAllLines(mappings, StandardCharsets.UTF_8).stream() + Set filter; + if (isSrg) { + filter = Files.readAllLines(mappings, StandardCharsets.UTF_8).stream() + .filter(s -> s.startsWith("CL:")) + .map(s -> s.split(" ")[1] + ".class") + .collect(Collectors.toSet()); + } else { + filter = Files.readAllLines(mappings, StandardCharsets.UTF_8).stream() .filter(s -> !s.startsWith("\t")) .map(s -> s.split(" ")[0] + ".class") .collect(Collectors.toSet()); + } LoomGradleExtension extension = LoomGradleExtension.get(project.getProject()); Path stripped = extension.getFiles().getProjectBuildCache().toPath().resolve(officialJar.getFileName().toString().substring(0, officialJar.getFileName().toString().length() - 4) + "-filtered.jar"); Files.deleteIfExists(stripped); + project.getLogger().info(officialJar.toString()); + Stopwatch stopwatch = Stopwatch.createStarted(); + int count = 0; + try (FileSystemUtil.Delegate output = FileSystemUtil.getJarFileSystem(stripped, true)) { try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(officialJar, false)) { ThreadingUtils.TaskCompleter completer = ThreadingUtils.taskCompleter(); @@ -91,18 +103,20 @@ public static Path produceSrgJar(RemapAction remapAction, Project project, Strin completer.add(() -> { Files.copy(path, to, StandardCopyOption.COPY_ATTRIBUTES); }); + count++; } completer.complete(); } } finally { - project.getLogger().info("Copied class files in " + stopwatch.stop()); + project.getLogger().info("Copied " + count + " class files in " + stopwatch.stop()); } Path output = extension.getFiles().getProjectBuildCache().toPath().resolve(officialJar.getFileName().toString().substring(0, officialJar.getFileName().toString().length() - 4) + "-srg-output.jar"); Files.deleteIfExists(output); stopwatch = Stopwatch.createStarted(); + project.getLogger().info(stripped.toString()); List args = remapAction.getArgs(stripped, output, mappings, project.files(mcLibs)); project.getLogger().lifecycle(":remapping minecraft (" + remapAction + ", " + side + ", official -> mojang)"); From a4e936301efce1ee44acf860acc8f57aafefe77a Mon Sep 17 00:00:00 2001 From: Wagyourtail Date: Sat, 11 Dec 2021 02:25:45 -0700 Subject: [PATCH 05/28] closer now, still some wrong checksums... --- .../forge/MinecraftPatchedProvider.java | 21 +- .../loom/util/srg/SpecialSourceExecutor.java | 39 +- .../minecraftforge/fml/relauncher/Side.java | 43 ++ .../fml/relauncher/SideOnly.java | 33 ++ .../gradle/tasks/MergeJars.java | 552 ++++++++++++++++++ 5 files changed, 666 insertions(+), 22 deletions(-) create mode 100644 src/main/java/net/minecraftforge/fml/relauncher/Side.java create mode 100644 src/main/java/net/minecraftforge/fml/relauncher/SideOnly.java create mode 100644 src/main/java/net/minecraftforge/gradle/tasks/MergeJars.java diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java index 52c34c931..d03cadc23 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java @@ -68,6 +68,7 @@ import dev.architectury.tinyremapper.OutputConsumerPath; import dev.architectury.tinyremapper.TinyRemapper; import net.minecraftforge.binarypatcher.ConsoleTool; +import net.minecraftforge.gradle.tasks.MergeJars; import org.apache.commons.io.output.NullOutputStream; import org.gradle.api.Project; import org.gradle.api.file.ConfigurableFileCollection; @@ -347,11 +348,21 @@ private void produceSrgJar(boolean official, Path clientJar, Path serverJar) thr boolean isSrg = getExtension().getMcpConfigProvider().isSRG(); -// ThreadingUtils.run(() -> { - Files.copy(SpecialSourceExecutor.produceSrgJar(getExtension().getMcpConfigProvider().getRemapAction(), getProject(), "client", mcLibs, clientJar, tmpSrg, isSrg), minecraftClientSrgJar.toPath()); -// }, () -> { - Files.copy(SpecialSourceExecutor.produceSrgJar(getExtension().getMcpConfigProvider().getRemapAction(), getProject(), "server", mcLibs, serverJar, tmpSrg, isSrg), minecraftServerSrgJar.toPath()); -// }); + Path[] clientJarOut = new Path[] { null }; + Path[] serverJarOut = new Path[] { null }; + + ThreadingUtils.run(() -> { + clientJarOut[0] = SpecialSourceExecutor.produceSrgJar(getExtension().getMcpConfigProvider().getRemapAction(), getProject(), "client", mcLibs, clientJar, tmpSrg, isSrg); + }, () -> { + serverJarOut[0] = SpecialSourceExecutor.produceSrgJar(getExtension().getMcpConfigProvider().getRemapAction(), getProject(), "server", mcLibs, serverJar, tmpSrg, isSrg); + }); + + if (isSrg) { + new MergeJars().processJar(clientJarOut[0].toFile(), serverJarOut[0].toFile(), minecraftClientSrgJar); + } else { + Files.copy(clientJarOut[0], minecraftClientSrgJar.toPath()); + Files.copy(serverJarOut[0], minecraftServerSrgJar.toPath()); + } } private Path getToSrgMappings() throws IOException { diff --git a/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java b/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java index a54ef7a90..3dd1b1de8 100644 --- a/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java +++ b/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java @@ -35,6 +35,7 @@ import java.util.stream.Collectors; import com.google.common.base.Stopwatch; +import net.minecraftforge.gradle.tasks.MergeJars; import org.apache.commons.io.output.NullOutputStream; import org.gradle.api.Project; import org.gradle.api.logging.LogLevel; @@ -113,7 +114,7 @@ public static Path produceSrgJar(RemapAction remapAction, Project project, Strin } Path output = extension.getFiles().getProjectBuildCache().toPath().resolve(officialJar.getFileName().toString().substring(0, officialJar.getFileName().toString().length() - 4) + "-srg-output.jar"); - Files.deleteIfExists(output); +// Files.deleteIfExists(output); stopwatch = Stopwatch.createStarted(); project.getLogger().info(stripped.toString()); @@ -123,32 +124,36 @@ public static Path produceSrgJar(RemapAction remapAction, Project project, Strin Path workingDir = tmpDir(); - project.javaexec(spec -> { - spec.setArgs(args); - spec.setClasspath(remapAction.getClasspath()); - spec.workingDir(workingDir.toFile()); - spec.getMainClass().set(remapAction.getMainClass()); + if (!isSrg) { + project.javaexec(spec -> { + spec.setArgs(args); + spec.setClasspath(remapAction.getClasspath()); + spec.workingDir(workingDir.toFile()); + spec.getMainClass().set(remapAction.getMainClass()); - // if running with INFO or DEBUG logging - if (project.getGradle().getStartParameter().getShowStacktrace() != ShowStacktrace.INTERNAL_EXCEPTIONS + // if running with INFO or DEBUG logging + if (project.getGradle().getStartParameter().getShowStacktrace() != ShowStacktrace.INTERNAL_EXCEPTIONS || project.getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) < 0) { - spec.setStandardOutput(System.out); - spec.setErrorOutput(System.err); - } else { - spec.setStandardOutput(NullOutputStream.NULL_OUTPUT_STREAM); - spec.setErrorOutput(NullOutputStream.NULL_OUTPUT_STREAM); - } - }).rethrowFailure().assertNormalExitValue(); + spec.setStandardOutput(System.out); + spec.setErrorOutput(System.err); + } else { + spec.setStandardOutput(NullOutputStream.NULL_OUTPUT_STREAM); + spec.setErrorOutput(NullOutputStream.NULL_OUTPUT_STREAM); + } + }).rethrowFailure().assertNormalExitValue(); + } else { + Files.copy(stripped, output, StandardCopyOption.REPLACE_EXISTING); + } project.getLogger().lifecycle(":remapped minecraft (" + remapAction + ", " + side + ", official -> mojang) in " + stopwatch.stop()); - Files.deleteIfExists(stripped); +// Files.deleteIfExists(stripped); Path tmp = tmpFile(); Files.deleteIfExists(tmp); Files.copy(output, tmp); - Files.deleteIfExists(output); +// Files.deleteIfExists(output); return tmp; } diff --git a/src/main/java/net/minecraftforge/fml/relauncher/Side.java b/src/main/java/net/minecraftforge/fml/relauncher/Side.java new file mode 100644 index 000000000..0acd06b8a --- /dev/null +++ b/src/main/java/net/minecraftforge/fml/relauncher/Side.java @@ -0,0 +1,43 @@ +/* + * Forge Mod Loader + * Copyright (c) 2012-2013 cpw. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser Public License v2.1 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * + * Contributors: + * cpw - implementation + */ + +package net.minecraftforge.fml.relauncher; + +public enum Side { + + /** + * The client side. Specifically, an environment where rendering capability exists. + * Usually in the game client. + */ + CLIENT, + /** + * The server side. Specifically, an environment where NO rendering capability exists. + * Usually on the dedicated server. + */ + SERVER; + + /** + * @return If this is the server environment + */ + public boolean isServer() + { + return !isClient(); + } + + /** + * @return if this is the Client environment + */ + public boolean isClient() + { + return this == CLIENT; + } +} diff --git a/src/main/java/net/minecraftforge/fml/relauncher/SideOnly.java b/src/main/java/net/minecraftforge/fml/relauncher/SideOnly.java new file mode 100644 index 000000000..24e7f7262 --- /dev/null +++ b/src/main/java/net/minecraftforge/fml/relauncher/SideOnly.java @@ -0,0 +1,33 @@ +/* + * Forge Mod Loader + * Copyright (c) 2012-2013 cpw. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Lesser Public License v2.1 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * + * Contributors: + * cpw - implementation + */ + +package net.minecraftforge.fml.relauncher; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +/** + * + * Stolen from FML for use with merging the jars. + * + * @author cpw + * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR}) +public @interface SideOnly +{ + public Side value(); +} diff --git a/src/main/java/net/minecraftforge/gradle/tasks/MergeJars.java b/src/main/java/net/minecraftforge/gradle/tasks/MergeJars.java new file mode 100644 index 000000000..f36289458 --- /dev/null +++ b/src/main/java/net/minecraftforge/gradle/tasks/MergeJars.java @@ -0,0 +1,552 @@ +/* + * A Gradle plugin for the creation of Minecraft mods and MinecraftForge plugins. + * Copyright (C) 2013-2019 Minecraft Forge + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ +package net.minecraftforge.gradle.tasks; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map.Entry; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; + +import org.gradle.api.tasks.InputFile; +import org.gradle.api.tasks.OutputFile; +import org.gradle.api.tasks.TaskAction; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AnnotationNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.InnerClassNode; + +import com.google.common.base.Function; +import com.google.common.base.MoreObjects; +import com.google.common.base.Objects; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import com.google.common.io.ByteStreams; + +import groovy.lang.Closure; +import net.minecraftforge.fml.relauncher.Side; +import net.minecraftforge.fml.relauncher.SideOnly; + +public class MergeJars { + + private static final Class sideClass = net.minecraftforge.fml.relauncher.Side.class; + private static final Class sideOnlyClass = net.minecraftforge.fml.relauncher.SideOnly.class; + + private static final boolean DEBUG = false; + + + public void processJar(File clientInFile, File serverInFile, File outFile) throws IOException + { + try (ZipFile cInJar = new ZipFile(clientInFile); + ZipFile sInJar = new ZipFile(serverInFile); + ZipOutputStream outJar = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(outFile)))) + { + // read in the jars, and initalize some variables + HashSet resources = new HashSet(); + HashMap cClasses = getClassEntries(cInJar, outJar, resources); + HashMap sClasses = getClassEntries(sInJar, outJar, resources); + HashSet cAdded = new HashSet(); + + // start processing + for (Entry entry : cClasses.entrySet()) + { + String name = entry.getKey(); + ZipEntry cEntry = entry.getValue(); + ZipEntry sEntry = sClasses.get(name); + + if (sEntry == null) + { + copyClass(cInJar, cEntry, outJar, true); + cAdded.add(name); + continue; + } + + sClasses.remove(name); + byte[] cData = readEntry(cInJar, entry.getValue()); + byte[] sData = readEntry(sInJar, sEntry); + byte[] data = processClass(cData, sData); + + ZipEntry newEntry = new ZipEntry(cEntry.getName()); + try { + outJar.putNextEntry(newEntry); + outJar.write(data); + } finally { + outJar.closeEntry(); + } + cAdded.add(name); + } + + for (Entry entry : sClasses.entrySet()) + { + if (DEBUG) + { + System.out.println("Copy class s->c : " + entry.getKey()); + } + copyClass(sInJar, entry.getValue(), outJar, false); + } + + for (String name : new String[] { sideOnlyClass.getName(), sideClass.getName() }) + { + String eName = name.replace(".", "/"); + String classPath = eName + ".class"; + ZipEntry newEntry = new ZipEntry(classPath); + if (!cAdded.contains(eName)) + { + try { + outJar.putNextEntry(newEntry); + outJar.write(getClassBytes(name)); + } finally { + outJar.closeEntry(); + } + } + } + + } + catch (FileNotFoundException e) + { + throw new FileNotFoundException("Could not open input/output file: " + e.getMessage()); + } + } + + private void copyClass(ZipFile inJar, ZipEntry entry, ZipOutputStream outJar, boolean isClientOnly) throws IOException + { + ClassReader reader = new ClassReader(readEntry(inJar, entry)); + ClassNode classNode = new ClassNode(); + + reader.accept(classNode, 0); + + if (classNode.visibleAnnotations == null) + { + classNode.visibleAnnotations = new ArrayList(); + } + classNode.visibleAnnotations.add(getSideAnn(isClientOnly)); + + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); + classNode.accept(writer); + byte[] data = writer.toByteArray(); + + ZipEntry newEntry = new ZipEntry(entry.getName()); + if (outJar != null) + { + outJar.putNextEntry(newEntry); + outJar.write(data); + } + } + + private byte[] readEntry(ZipFile inFile, ZipEntry entry) throws IOException + { + try (InputStream is = inFile.getInputStream(entry)) + { + return ByteStreams.toByteArray(is); + } + } + + private AnnotationNode getSideAnn(boolean isClientOnly) + { + AnnotationNode ann = new AnnotationNode(Type.getDescriptor(sideOnlyClass)); + ann.values = new ArrayList(); + ann.values.add("value"); + ann.values.add(new String[] { Type.getDescriptor(sideClass), isClientOnly ? "CLIENT" : "SERVER" }); + return ann; + } + + /** + * @param inFile From which to read classes and resources + * @param outFile The place to write resources and ignored classes + * @param resources The registry to add resources to, and to check against. + * @return HashMap of all the desired Classes and their ZipEntrys + * @throws IOException + */ + private HashMap getClassEntries(ZipFile inFile, ZipOutputStream outFile, HashSet resources) throws IOException + { + HashMap ret = new HashMap(); + + for (ZipEntry entry : Collections.list(inFile.entries())) + { + String entryName = entry.getName(); + // Always skip the manifest + if ("META-INF/MANIFEST.MF".equals(entryName)) + { + continue; + } + if (entry.isDirectory()) + { + /* + * if (!resources.contains(entryName)) + * { + * outFile.putNextEntry(entry); + * } + */ + continue; + } + + if (!entryName.endsWith(".class") || entryName.startsWith(".")) + { + if (!resources.contains(entryName)) + { + ZipEntry newEntry = new ZipEntry(entryName); + outFile.putNextEntry(newEntry); + outFile.write(readEntry(inFile, entry)); + resources.add(entryName); + } + } + else + { + ret.put(entryName.replace(".class", ""), entry); + } + } + return ret; + } + + private byte[] getClassBytes(String name) throws IOException + { + // @TODO: rewrite. + InputStream classStream = null; + try + { + classStream = MergeJars.class.getResourceAsStream("/" + name.replace('.', '/').concat(".class")); + return ByteStreams.toByteArray(classStream); + } + finally + { + if (classStream != null) + { + classStream.close(); + } + } + } + + public byte[] processClass(byte[] cIn, byte[] sIn) + { + ClassNode cClassNode = getClassNode(cIn); + ClassNode sClassNode = getClassNode(sIn); + + processFields(cClassNode, sClassNode); + processMethods(cClassNode, sClassNode); + processInners(cClassNode, sClassNode); + + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); + cClassNode.accept(writer); + return writer.toByteArray(); + } + + private static boolean innerMatches(InnerClassNode o, InnerClassNode o2) + { + if (o.innerName == null && o2.innerName != null) return false; + if (o.innerName != null && !o.innerName.equals(o2.innerName)) return false; + if (o.name == null && o2.name != null) return false; + if (o.name != null && !o.name.equals(o2.name)) return false; + if (o.outerName == null && o2.outerName != null) return false; + if (o.outerName != null && o.outerName.equals(o2.outerName)) return false; + return true; + } + private static boolean contains(List list, InnerClassNode node) + { + for (InnerClassNode n : list) + if (innerMatches(n, node)) + return true; + return false; + } + private static void processInners(ClassNode cClass, ClassNode sClass) + { + List cIners = cClass.innerClasses; + List sIners = sClass.innerClasses; + + for (InnerClassNode n : cIners) + { + if (!contains(sIners, n)) + sIners.add(n); + } + for (InnerClassNode n : sIners) + { + if (!contains(cIners, n)) + cIners.add(n); + } + } + + private ClassNode getClassNode(byte[] data) + { + ClassReader reader = new ClassReader(data); + ClassNode classNode = new ClassNode(); + reader.accept(classNode, 0); + return classNode; + } + + private void processFields(ClassNode cClass, ClassNode sClass) + { + List cFields = cClass.fields; + List sFields = sClass.fields; + + int serverFieldIdx = 0; + if (DEBUG) + System.out.printf("B: Server List: %s\nB: Client List: %s\n", Lists.transform(sFields, FieldName.instance), Lists.transform(cFields, FieldName.instance)); + for (int clientFieldIdx = 0; clientFieldIdx < cFields.size(); clientFieldIdx++) + { + FieldNode clientField = cFields.get(clientFieldIdx); + if (serverFieldIdx < sFields.size()) + { + FieldNode serverField = sFields.get(serverFieldIdx); + if (!clientField.name.equals(serverField.name)) + { + boolean foundServerField = false; + for (int serverFieldSearchIdx = serverFieldIdx + 1; serverFieldSearchIdx < sFields.size(); serverFieldSearchIdx++) + { + if (clientField.name.equals(sFields.get(serverFieldSearchIdx).name)) + { + foundServerField = true; + break; + } + } + // Found a server field match ahead in the list - walk to it and add the missing server fields to the client + if (foundServerField) + { + boolean foundClientField = false; + for (int clientFieldSearchIdx = clientFieldIdx + 1; clientFieldSearchIdx < cFields.size(); clientFieldSearchIdx++) + { + if (serverField.name.equals(cFields.get(clientFieldSearchIdx).name)) + { + foundClientField = true; + break; + } + } + if (!foundClientField) + { + if (serverField.visibleAnnotations == null) + { + serverField.visibleAnnotations = new ArrayList(); + } + serverField.visibleAnnotations.add(getSideAnn(false)); + cFields.add(clientFieldIdx, serverField); + if (DEBUG) + System.out.printf("1. Server List: %s\n1. Client List: %s\nIdx: %d %d\n", Lists.transform(sFields, FieldName.instance), Lists.transform(cFields, FieldName.instance), serverFieldIdx, clientFieldIdx); + } + } + else + { + if (clientField.visibleAnnotations == null) + { + clientField.visibleAnnotations = new ArrayList(); + } + clientField.visibleAnnotations.add(getSideAnn(true)); + sFields.add(serverFieldIdx, clientField); + if (DEBUG) + System.out.printf("2. Server List: %s\n2. Client List: %s\nIdx: %d %d\n", Lists.transform(sFields, FieldName.instance), Lists.transform(cFields, FieldName.instance), serverFieldIdx, clientFieldIdx); + } + } + } + else + { + if (clientField.visibleAnnotations == null) + { + clientField.visibleAnnotations = new ArrayList(); + } + clientField.visibleAnnotations.add(getSideAnn(true)); + sFields.add(serverFieldIdx, clientField); + if (DEBUG) + System.out.printf("3. Server List: %s\n3. Client List: %s\nIdx: %d %d\n", Lists.transform(sFields, FieldName.instance), Lists.transform(cFields, FieldName.instance), serverFieldIdx, clientFieldIdx); + } + serverFieldIdx++; + } + if (DEBUG) + System.out.printf("A. Server List: %s\nA. Client List: %s\n", Lists.transform(sFields, FieldName.instance), Lists.transform(cFields, FieldName.instance)); + if (sFields.size() != cFields.size()) + { + for (int x = cFields.size(); x < sFields.size(); x++) + { + FieldNode sF = sFields.get(x); + if (sF.visibleAnnotations == null) + { + sF.visibleAnnotations = new ArrayList(); + } + sF.visibleAnnotations.add(getSideAnn(true)); + cFields.add(x++, sF); + } + } + if (DEBUG) + System.out.printf("E. Server List: %s\nE. Client List: %s\n", Lists.transform(sFields, FieldName.instance), Lists.transform(cFields, FieldName.instance)); + } + + private static class FieldName implements Function + { + public static FieldName instance = new FieldName(); + + public String apply(FieldNode in) + { + return in.name; + } + } + + private void processMethods(ClassNode cClass, ClassNode sClass) + { + List cMethods = cClass.methods; + List sMethods = sClass.methods; + LinkedHashSet allMethods = Sets.newLinkedHashSet(); + + int cPos = 0; + int sPos = 0; + int cLen = cMethods.size(); + int sLen = sMethods.size(); + String clientName = ""; + String lastName = clientName; + String serverName = ""; + while (cPos < cLen || sPos < sLen) + { + do + { + if (sPos >= sLen) + { + break; + } + MethodNode sM = sMethods.get(sPos); + serverName = sM.name; + if (!serverName.equals(lastName) && cPos != cLen) + { + if (DEBUG) + { + System.out.printf("Server -skip : %s %s %d (%s %d) %d [%s]\n", sClass.name, clientName, cLen - cPos, serverName, sLen - sPos, allMethods.size(), lastName); + } + break; + } + MethodWrapper mw = new MethodWrapper(sM); + mw.server = true; + allMethods.add(mw); + if (DEBUG) + { + System.out.printf("Server *add* : %s %s %d (%s %d) %d [%s]\n", sClass.name, clientName, cLen - cPos, serverName, sLen - sPos, allMethods.size(), lastName); + } + sPos++; + } while (sPos < sLen); + do + { + if (cPos >= cLen) + { + break; + } + MethodNode cM = cMethods.get(cPos); + lastName = clientName; + clientName = cM.name; + if (!clientName.equals(lastName) && sPos != sLen) + { + if (DEBUG) + { + System.out.printf("Client -skip : %s %s %d (%s %d) %d [%s]\n", cClass.name, clientName, cLen - cPos, serverName, sLen - sPos, allMethods.size(), lastName); + } + break; + } + MethodWrapper mw = new MethodWrapper(cM); + mw.client = true; + allMethods.add(mw); + if (DEBUG) + { + System.out.printf("Client *add* : %s %s %d (%s %d) %d [%s]\n", cClass.name, clientName, cLen - cPos, serverName, sLen - sPos, allMethods.size(), lastName); + } + cPos++; + } while (cPos < cLen); + } + + cMethods.clear(); + sMethods.clear(); + + for (MethodWrapper mw : allMethods) + { + if (DEBUG) + { + System.out.println(mw); + } + cMethods.add(mw.node); + sMethods.add(mw.node); + if (mw.server && mw.client) + { + // no op + } + else + { + if (mw.node.visibleAnnotations == null) + { + mw.node.visibleAnnotations = Lists.newArrayListWithExpectedSize(1); + } + + mw.node.visibleAnnotations.add(getSideAnn(mw.client)); + } + } + } + + private class MethodWrapper + { + private MethodNode node; + public boolean client; + public boolean server; + + public MethodWrapper(MethodNode node) + { + this.node = node; + } + + @Override + public boolean equals(Object obj) + { + if (obj == null || !(obj instanceof MethodWrapper)) + { + return false; + } + MethodWrapper mw = (MethodWrapper) obj; + boolean eq = Objects.equal(node.name, mw.node.name) && Objects.equal(node.desc, mw.node.desc); + if (eq) + { + mw.client = client | mw.client; + mw.server = server | mw.server; + client = client | mw.client; + server = server | mw.server; + if (DEBUG) + { + System.out.printf(" eq: %s %s\n", this, mw); + } + } + return eq; + } + + @Override + public int hashCode() + { + return Objects.hashCode(node.name, node.desc); + } + + @Override + public String toString() + { + return MoreObjects.toStringHelper(this).add("name", node.name).add("desc", node.desc).add("server", server).add("client", client).toString(); + } + } +} From 700231235f7f2e71ae8e7592e99dd854dd67ab3a Mon Sep 17 00:00:00 2001 From: Wagyourtail Date: Sat, 11 Dec 2021 03:16:08 -0700 Subject: [PATCH 06/28] switch to non-dev binpatches, now it wrong checksums in a different plac --- .../fabricmc/loom/configuration/CompileConfiguration.java | 2 +- .../providers/forge/MinecraftPatchedProvider.java | 8 ++++---- .../loom/configuration/providers/forge/PatchProvider.java | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java index afa5992de..cfaf52aac 100644 --- a/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java +++ b/src/main/java/net/fabricmc/loom/configuration/CompileConfiguration.java @@ -181,6 +181,7 @@ public static void configureCompile(Project p) { if (extension.isForge()) { dependencyManager.addProvider(new ForgeProvider(project)); dependencyManager.addProvider(new ForgeUserdevProvider(project)); + dependencyManager.addProvider(new ForgeUniversalProvider(project)); } if (extension.shouldGenerateSrgTiny()) { @@ -190,7 +191,6 @@ public static void configureCompile(Project p) { if (extension.isForge()) { dependencyManager.addProvider(new McpConfigProvider(project)); dependencyManager.addProvider(new PatchProvider(project)); - dependencyManager.addProvider(new ForgeUniversalProvider(project)); } dependencyManager.addProvider(extension.isForge() ? new FieldMigratedMappingsProvider(project) : new MappingsProviderImpl(project)); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java index d03cadc23..7f9f88a91 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java @@ -357,12 +357,12 @@ private void produceSrgJar(boolean official, Path clientJar, Path serverJar) thr serverJarOut[0] = SpecialSourceExecutor.produceSrgJar(getExtension().getMcpConfigProvider().getRemapAction(), getProject(), "server", mcLibs, serverJar, tmpSrg, isSrg); }); - if (isSrg) { - new MergeJars().processJar(clientJarOut[0].toFile(), serverJarOut[0].toFile(), minecraftClientSrgJar); - } else { +// if (isSrg) { +// new MergeJars().processJar(clientJarOut[0].toFile(), serverJarOut[0].toFile(), minecraftClientSrgJar); +// } else { Files.copy(clientJarOut[0], minecraftClientSrgJar.toPath()); Files.copy(serverJarOut[0], minecraftServerSrgJar.toPath()); - } +// } } private Path getToSrgMappings() throws IOException { diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/PatchProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/PatchProvider.java index 554543551..b59a60ced 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/PatchProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/PatchProvider.java @@ -51,14 +51,14 @@ public void provide(DependencyInfo dependency, Consumer postPopulation init(dependency.getDependency().getVersion()); if (getExtension().getForgeProvider().isFG2()) { - File forge = getExtension().getForgeUserdevProvider().getUserdevJar(); + File forge = getExtension().getForgeUniversalProvider().getForge(); if (Files.notExists(clientPatches) || isRefreshDeps()) { getProject().getLogger().info(":extracting forge patches"); try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + forge.toURI()), ImmutableMap.of("create", false))) { - Files.copy(fs.getPath("devbinpatches.pack.lzma"), clientPatches, StandardCopyOption.REPLACE_EXISTING); - Files.copy(fs.getPath("devbinpatches.pack.lzma"), serverPatches, StandardCopyOption.REPLACE_EXISTING); + Files.copy(fs.getPath("binpatches.pack.lzma"), clientPatches, StandardCopyOption.REPLACE_EXISTING); + Files.copy(fs.getPath("binpatches.pack.lzma"), serverPatches, StandardCopyOption.REPLACE_EXISTING); } } From 3e6f79f21b346ecb27a39746f2b19147a11f0ff5 Mon Sep 17 00:00:00 2001 From: Wagyourtail Date: Sat, 11 Dec 2021 05:24:38 -0700 Subject: [PATCH 07/28] binpatches apply, now have to fix remapping --- .../forge/MinecraftPatchedProvider.java | 16 +- .../gradle/tasks/TaskApplyBinPatches.java | 246 ++++++++++++++++++ 2 files changed, 251 insertions(+), 11 deletions(-) create mode 100644 src/main/java/net/minecraftforge/gradle/tasks/TaskApplyBinPatches.java diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java index 7f9f88a91..ffc8533b9 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java @@ -69,6 +69,7 @@ import dev.architectury.tinyremapper.TinyRemapper; import net.minecraftforge.binarypatcher.ConsoleTool; import net.minecraftforge.gradle.tasks.MergeJars; +import net.minecraftforge.gradle.tasks.TaskApplyBinPatches; import org.apache.commons.io.output.NullOutputStream; import org.gradle.api.Project; import org.gradle.api.file.ConfigurableFileCollection; @@ -617,10 +618,8 @@ private void patchJars(Logger logger) throws IOException { logger.lifecycle(":patching jars"); PatchProvider patchProvider = getExtension().getPatchProvider(); - patchJars(minecraftClientSrgJar, minecraftClientPatchedSrgJar, patchProvider.clientPatches); - if (!getExtension().getMcpConfigProvider().isSRG()) { - patchJars(minecraftServerSrgJar, minecraftServerPatchedSrgJar, patchProvider.serverPatches); - } + patchJars(minecraftClientSrgJar, minecraftClientPatchedSrgJar, patchProvider.clientPatches, "client"); + patchJars(minecraftServerSrgJar, minecraftServerPatchedSrgJar, patchProvider.serverPatches, "server"); ThreadingUtils.run(Environment.values(), environment -> { copyMissingClasses(environment.srgJar.apply(this), environment.patchedSrgJar.apply(this)); @@ -634,7 +633,7 @@ private void patchJars(Logger logger) throws IOException { logger.lifecycle(":patched jars in " + stopwatch.stop()); } - private void patchJars(File clean, File output, Path patches) throws IOException { + private void patchJars(File clean, File output, Path patches, String side) throws IOException { PrintStream previous = System.out; try { @@ -645,12 +644,7 @@ private void patchJars(File clean, File output, Path patches) throws IOException if (getExtension().getMcpConfigProvider().isSRG()) { - ConsoleTool.main(new String[] { - "--clean", clean.getAbsolutePath(), - "--output", output.getAbsolutePath(), - "--apply", patches.toAbsolutePath().toString(), - "--legacy" - }); + new TaskApplyBinPatches().doTask(getProject(), clean.getAbsoluteFile(), patches.toFile().getAbsoluteFile(), output.getAbsoluteFile(), side); } else { ConsoleTool.main(new String[] { diff --git a/src/main/java/net/minecraftforge/gradle/tasks/TaskApplyBinPatches.java b/src/main/java/net/minecraftforge/gradle/tasks/TaskApplyBinPatches.java new file mode 100644 index 000000000..536df6fa2 --- /dev/null +++ b/src/main/java/net/minecraftforge/gradle/tasks/TaskApplyBinPatches.java @@ -0,0 +1,246 @@ +/* + * A Gradle plugin for the creation of Minecraft mods and MinecraftForge plugins. + * Copyright (C) 2013 Minecraft Forge + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 + * USA + */ +package net.minecraftforge.gradle.tasks; + +import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.jar.JarEntry; +import java.util.jar.JarInputStream; +import java.util.jar.JarOutputStream; +import java.util.regex.Pattern; +import java.util.zip.Adler32; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +import net.minecraftforge.java.util.jar.Pack200; +import org.gradle.api.Project; +import org.gradle.api.file.FileVisitDetails; +import org.gradle.api.file.FileVisitor; +import com.google.common.base.Joiner; +import com.google.common.base.Throwables; +import com.google.common.collect.Maps; +import com.google.common.io.ByteArrayDataInput; +import com.google.common.io.ByteStreams; +import com.nothome.delta.GDiffPatcher; + +import lzma.sdk.lzma.Decoder; +import lzma.streams.LzmaInputStream; + +public class TaskApplyBinPatches { + + private HashMap patchlist = Maps.newHashMap(); + private GDiffPatcher patcher = new GDiffPatcher(); + + private Project project; + + public void doTask(Project project, File inJar, File patches, File outjar, String side) throws IOException + { + this.project = project; + setup(patches, side); + + if (outjar.exists()) + { + outjar.delete(); + } + + ZipFile in = new ZipFile(inJar); + ZipInputStream classesIn = new ZipInputStream(new FileInputStream(inJar)); + final ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(outjar))); + final HashSet entries = new HashSet(); + + try + { + // DO PATCHES + log("Patching Class:"); + for (ZipEntry e : Collections.list(in.entries())) + { + if (e.getName().contains("META-INF")) + continue; + + if (e.isDirectory()) + { + out.putNextEntry(e); + } + else + { + ZipEntry n = new ZipEntry(e.getName()); + n.setTime(e.getTime()); + out.putNextEntry(n); + + byte[] data = ByteStreams.toByteArray(in.getInputStream(e)); + ClassPatch patch = patchlist.get(e.getName().replace('\\', '/')); + + if (patch != null) + { + log("\t%s (%s) (input size %d)", patch.targetClassName, patch.sourceClassName, data.length); + int inputChecksum = adlerHash(data); + if (patch.inputChecksum != inputChecksum) + { + throw new RuntimeException(String.format("There is a binary discrepency between the expected input class %s (%s) and the actual class. Checksum on disk is %x, in patch %x. Things are probably about to go very wrong. Did you put something into the jar file?", patch.targetClassName, patch.sourceClassName, inputChecksum, patch.inputChecksum)); + } + synchronized (patcher) + { + data = patcher.patch(data, patch.patch); + } + } + + out.write(data); + } + + // add the names to the hashset + entries.add(e.getName()); + } + + // COPY DATA + ZipEntry entry = null; + while ((entry = classesIn.getNextEntry()) != null) + { + if (entries.contains(entry.getName())) + continue; + + out.putNextEntry(entry); + out.write(ByteStreams.toByteArray(classesIn)); + entries.add(entry.getName()); + } + } + finally + { + classesIn.close(); + in.close(); + out.close(); + } + } + + private int adlerHash(byte[] input) + { + Adler32 hasher = new Adler32(); + hasher.update(input); + return (int) hasher.getValue(); + } + + public void setup(File patches, String side) + { + Pattern matcher = Pattern.compile(String.format("binpatch/%s/.*.binpatch", side)); + + JarInputStream jis; + try + { + LzmaInputStream binpatchesDecompressed = new LzmaInputStream(new FileInputStream(patches), new Decoder()); + ByteArrayOutputStream jarBytes = new ByteArrayOutputStream(); + JarOutputStream jos = new JarOutputStream(jarBytes); + Pack200.newUnpacker().unpack(binpatchesDecompressed, jos); + jis = new JarInputStream(new ByteArrayInputStream(jarBytes.toByteArray())); + } + catch (Exception e) + { + throw Throwables.propagate(e); + } + + log("Reading Patches:"); + do + { + try + { + JarEntry entry = jis.getNextJarEntry(); + if (entry == null) + { + break; + } + + if (matcher.matcher(entry.getName()).matches()) + { + ClassPatch cp = readPatch(entry, jis); + patchlist.put(cp.sourceClassName.replace('.', '/') + ".class", cp); + } + else + { + log("skipping entry: %s", entry.getName()); + jis.closeEntry(); + } + } + catch (IOException e) + {} + } while (true); + log("Read %d binary patches", patchlist.size()); + log("Patch list :\n\t%s", Joiner.on("\n\t").join(patchlist.entrySet())); + } + + private ClassPatch readPatch(JarEntry patchEntry, JarInputStream jis) throws IOException + { + log("\t%s", patchEntry.getName()); + ByteArrayDataInput input = ByteStreams.newDataInput(ByteStreams.toByteArray(jis)); + + String name = input.readUTF(); + String sourceClassName = input.readUTF(); + String targetClassName = input.readUTF(); + boolean exists = input.readBoolean(); + int inputChecksum = 0; + if (exists) + { + inputChecksum = input.readInt(); + } + int patchLength = input.readInt(); + byte[] patchBytes = new byte[patchLength]; + input.readFully(patchBytes); + + return new ClassPatch(name, sourceClassName, targetClassName, exists, inputChecksum, patchBytes); + } + + private void log(String format, Object... args) + { + this.project.getLogger().info(String.format(format, args)); + } + + public static class ClassPatch + { + public final String name; + public final String sourceClassName; + public final String targetClassName; + public final boolean existsAtTarget; + public final byte[] patch; + public final int inputChecksum; + + public ClassPatch(String name, String sourceClassName, String targetClassName, boolean existsAtTarget, int inputChecksum, byte[] patch) + { + this.name = name; + this.sourceClassName = sourceClassName; + this.targetClassName = targetClassName; + this.existsAtTarget = existsAtTarget; + this.inputChecksum = inputChecksum; + this.patch = patch; + } + + @Override + public String toString() + { + return String.format("%s : %s => %s (%b) size %d", name, sourceClassName, targetClassName, existsAtTarget, patch.length); + } + } +} From 7940be66fa1dc7f231ab6fd916c5ea44b4373d6f Mon Sep 17 00:00:00 2001 From: Wagyourtail Date: Sat, 11 Dec 2021 06:12:05 -0700 Subject: [PATCH 08/28] merge and transform to actually be srg --- .../forge/MinecraftPatchedProvider.java | 17 +- .../loom/util/srg/SpecialSourceExecutor.java | 26 +- .../gradle/tasks/MergeJars.java | 552 ------------------ 3 files changed, 24 insertions(+), 571 deletions(-) delete mode 100644 src/main/java/net/minecraftforge/gradle/tasks/MergeJars.java diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java index ffc8533b9..fe267d7b0 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java @@ -68,7 +68,6 @@ import dev.architectury.tinyremapper.OutputConsumerPath; import dev.architectury.tinyremapper.TinyRemapper; import net.minecraftforge.binarypatcher.ConsoleTool; -import net.minecraftforge.gradle.tasks.MergeJars; import net.minecraftforge.gradle.tasks.TaskApplyBinPatches; import org.apache.commons.io.output.NullOutputStream; import org.gradle.api.Project; @@ -353,9 +352,9 @@ private void produceSrgJar(boolean official, Path clientJar, Path serverJar) thr Path[] serverJarOut = new Path[] { null }; ThreadingUtils.run(() -> { - clientJarOut[0] = SpecialSourceExecutor.produceSrgJar(getExtension().getMcpConfigProvider().getRemapAction(), getProject(), "client", mcLibs, clientJar, tmpSrg, isSrg); + clientJarOut[0] = SpecialSourceExecutor.produceSrgJar(getExtension().getMcpConfigProvider().getRemapAction(), getProject(), "client", mcLibs, clientJar, tmpSrg, isSrg, false); }, () -> { - serverJarOut[0] = SpecialSourceExecutor.produceSrgJar(getExtension().getMcpConfigProvider().getRemapAction(), getProject(), "server", mcLibs, serverJar, tmpSrg, isSrg); + serverJarOut[0] = SpecialSourceExecutor.produceSrgJar(getExtension().getMcpConfigProvider().getRemapAction(), getProject(), "server", mcLibs, serverJar, tmpSrg, isSrg, false); }); // if (isSrg) { @@ -661,11 +660,19 @@ private void patchJars(File clean, File output, Path patches, String side) throw } } - private void mergeJars(Logger logger) throws IOException { + private void mergeJars(Logger logger) throws Exception { // FIXME: Hack here: There are no server-only classes so we can just copy the client JAR. // This will change if upstream Loom adds the possibility for separate projects/source sets per environment. - Files.copy(minecraftClientPatchedSrgJar.toPath(), minecraftMergedPatchedSrgJar.toPath()); + + if (getExtension().getMcpConfigProvider().isSRG()) { + logger.lifecycle(":merging jars"); + Path tmpSrg = getToSrgMappings(); + Set mcLibs = getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_DEPENDENCIES).resolve(); + Files.copy(SpecialSourceExecutor.produceSrgJar(getExtension().getMcpConfigProvider().getRemapAction(), getProject(), "merged", mcLibs, minecraftClientPatchedSrgJar.toPath(), tmpSrg, true, true), minecraftMergedPatchedSrgJar.toPath()); + } else { + Files.copy(minecraftClientPatchedSrgJar.toPath(), minecraftMergedPatchedSrgJar.toPath()); + } logger.lifecycle(":copying resources"); // Copy resources diff --git a/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java b/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java index 3dd1b1de8..98b625c22 100644 --- a/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java +++ b/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java @@ -24,6 +24,16 @@ package net.fabricmc.loom.util.srg; +import com.google.common.base.Stopwatch; +import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.configuration.providers.forge.McpConfigProvider.RemapAction; +import net.fabricmc.loom.util.FileSystemUtil; +import net.fabricmc.loom.util.ThreadingUtils; +import org.apache.commons.io.output.NullOutputStream; +import org.gradle.api.Project; +import org.gradle.api.logging.LogLevel; +import org.gradle.api.logging.configuration.ShowStacktrace; + import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -34,18 +44,6 @@ import java.util.Set; import java.util.stream.Collectors; -import com.google.common.base.Stopwatch; -import net.minecraftforge.gradle.tasks.MergeJars; -import org.apache.commons.io.output.NullOutputStream; -import org.gradle.api.Project; -import org.gradle.api.logging.LogLevel; -import org.gradle.api.logging.configuration.ShowStacktrace; - -import net.fabricmc.loom.LoomGradleExtension; -import net.fabricmc.loom.configuration.providers.forge.McpConfigProvider.RemapAction; -import net.fabricmc.loom.util.FileSystemUtil; -import net.fabricmc.loom.util.ThreadingUtils; - public class SpecialSourceExecutor { private static String trimLeadingSlash(String string) { if (string.startsWith(File.separator)) { @@ -57,7 +55,7 @@ private static String trimLeadingSlash(String string) { return string; } - public static Path produceSrgJar(RemapAction remapAction, Project project, String side, Set mcLibs, Path officialJar, Path mappings, boolean isSrg) + public static Path produceSrgJar(RemapAction remapAction, Project project, String side, Set mcLibs, Path officialJar, Path mappings, boolean isSrg, boolean actuallyRemapSrg) throws Exception { Set filter; if (isSrg) { @@ -124,7 +122,7 @@ public static Path produceSrgJar(RemapAction remapAction, Project project, Strin Path workingDir = tmpDir(); - if (!isSrg) { + if (!isSrg || actuallyRemapSrg) { project.javaexec(spec -> { spec.setArgs(args); spec.setClasspath(remapAction.getClasspath()); diff --git a/src/main/java/net/minecraftforge/gradle/tasks/MergeJars.java b/src/main/java/net/minecraftforge/gradle/tasks/MergeJars.java deleted file mode 100644 index f36289458..000000000 --- a/src/main/java/net/minecraftforge/gradle/tasks/MergeJars.java +++ /dev/null @@ -1,552 +0,0 @@ -/* - * A Gradle plugin for the creation of Minecraft mods and MinecraftForge plugins. - * Copyright (C) 2013-2019 Minecraft Forge - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 - * USA - */ -package net.minecraftforge.gradle.tasks; - -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map.Entry; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; -import java.util.zip.ZipOutputStream; - -import org.gradle.api.tasks.InputFile; -import org.gradle.api.tasks.OutputFile; -import org.gradle.api.tasks.TaskAction; -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.Type; -import org.objectweb.asm.tree.AnnotationNode; -import org.objectweb.asm.tree.ClassNode; -import org.objectweb.asm.tree.FieldNode; -import org.objectweb.asm.tree.MethodNode; -import org.objectweb.asm.tree.InnerClassNode; - -import com.google.common.base.Function; -import com.google.common.base.MoreObjects; -import com.google.common.base.Objects; -import com.google.common.collect.Lists; -import com.google.common.collect.Sets; -import com.google.common.io.ByteStreams; - -import groovy.lang.Closure; -import net.minecraftforge.fml.relauncher.Side; -import net.minecraftforge.fml.relauncher.SideOnly; - -public class MergeJars { - - private static final Class sideClass = net.minecraftforge.fml.relauncher.Side.class; - private static final Class sideOnlyClass = net.minecraftforge.fml.relauncher.SideOnly.class; - - private static final boolean DEBUG = false; - - - public void processJar(File clientInFile, File serverInFile, File outFile) throws IOException - { - try (ZipFile cInJar = new ZipFile(clientInFile); - ZipFile sInJar = new ZipFile(serverInFile); - ZipOutputStream outJar = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(outFile)))) - { - // read in the jars, and initalize some variables - HashSet resources = new HashSet(); - HashMap cClasses = getClassEntries(cInJar, outJar, resources); - HashMap sClasses = getClassEntries(sInJar, outJar, resources); - HashSet cAdded = new HashSet(); - - // start processing - for (Entry entry : cClasses.entrySet()) - { - String name = entry.getKey(); - ZipEntry cEntry = entry.getValue(); - ZipEntry sEntry = sClasses.get(name); - - if (sEntry == null) - { - copyClass(cInJar, cEntry, outJar, true); - cAdded.add(name); - continue; - } - - sClasses.remove(name); - byte[] cData = readEntry(cInJar, entry.getValue()); - byte[] sData = readEntry(sInJar, sEntry); - byte[] data = processClass(cData, sData); - - ZipEntry newEntry = new ZipEntry(cEntry.getName()); - try { - outJar.putNextEntry(newEntry); - outJar.write(data); - } finally { - outJar.closeEntry(); - } - cAdded.add(name); - } - - for (Entry entry : sClasses.entrySet()) - { - if (DEBUG) - { - System.out.println("Copy class s->c : " + entry.getKey()); - } - copyClass(sInJar, entry.getValue(), outJar, false); - } - - for (String name : new String[] { sideOnlyClass.getName(), sideClass.getName() }) - { - String eName = name.replace(".", "/"); - String classPath = eName + ".class"; - ZipEntry newEntry = new ZipEntry(classPath); - if (!cAdded.contains(eName)) - { - try { - outJar.putNextEntry(newEntry); - outJar.write(getClassBytes(name)); - } finally { - outJar.closeEntry(); - } - } - } - - } - catch (FileNotFoundException e) - { - throw new FileNotFoundException("Could not open input/output file: " + e.getMessage()); - } - } - - private void copyClass(ZipFile inJar, ZipEntry entry, ZipOutputStream outJar, boolean isClientOnly) throws IOException - { - ClassReader reader = new ClassReader(readEntry(inJar, entry)); - ClassNode classNode = new ClassNode(); - - reader.accept(classNode, 0); - - if (classNode.visibleAnnotations == null) - { - classNode.visibleAnnotations = new ArrayList(); - } - classNode.visibleAnnotations.add(getSideAnn(isClientOnly)); - - ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); - classNode.accept(writer); - byte[] data = writer.toByteArray(); - - ZipEntry newEntry = new ZipEntry(entry.getName()); - if (outJar != null) - { - outJar.putNextEntry(newEntry); - outJar.write(data); - } - } - - private byte[] readEntry(ZipFile inFile, ZipEntry entry) throws IOException - { - try (InputStream is = inFile.getInputStream(entry)) - { - return ByteStreams.toByteArray(is); - } - } - - private AnnotationNode getSideAnn(boolean isClientOnly) - { - AnnotationNode ann = new AnnotationNode(Type.getDescriptor(sideOnlyClass)); - ann.values = new ArrayList(); - ann.values.add("value"); - ann.values.add(new String[] { Type.getDescriptor(sideClass), isClientOnly ? "CLIENT" : "SERVER" }); - return ann; - } - - /** - * @param inFile From which to read classes and resources - * @param outFile The place to write resources and ignored classes - * @param resources The registry to add resources to, and to check against. - * @return HashMap of all the desired Classes and their ZipEntrys - * @throws IOException - */ - private HashMap getClassEntries(ZipFile inFile, ZipOutputStream outFile, HashSet resources) throws IOException - { - HashMap ret = new HashMap(); - - for (ZipEntry entry : Collections.list(inFile.entries())) - { - String entryName = entry.getName(); - // Always skip the manifest - if ("META-INF/MANIFEST.MF".equals(entryName)) - { - continue; - } - if (entry.isDirectory()) - { - /* - * if (!resources.contains(entryName)) - * { - * outFile.putNextEntry(entry); - * } - */ - continue; - } - - if (!entryName.endsWith(".class") || entryName.startsWith(".")) - { - if (!resources.contains(entryName)) - { - ZipEntry newEntry = new ZipEntry(entryName); - outFile.putNextEntry(newEntry); - outFile.write(readEntry(inFile, entry)); - resources.add(entryName); - } - } - else - { - ret.put(entryName.replace(".class", ""), entry); - } - } - return ret; - } - - private byte[] getClassBytes(String name) throws IOException - { - // @TODO: rewrite. - InputStream classStream = null; - try - { - classStream = MergeJars.class.getResourceAsStream("/" + name.replace('.', '/').concat(".class")); - return ByteStreams.toByteArray(classStream); - } - finally - { - if (classStream != null) - { - classStream.close(); - } - } - } - - public byte[] processClass(byte[] cIn, byte[] sIn) - { - ClassNode cClassNode = getClassNode(cIn); - ClassNode sClassNode = getClassNode(sIn); - - processFields(cClassNode, sClassNode); - processMethods(cClassNode, sClassNode); - processInners(cClassNode, sClassNode); - - ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); - cClassNode.accept(writer); - return writer.toByteArray(); - } - - private static boolean innerMatches(InnerClassNode o, InnerClassNode o2) - { - if (o.innerName == null && o2.innerName != null) return false; - if (o.innerName != null && !o.innerName.equals(o2.innerName)) return false; - if (o.name == null && o2.name != null) return false; - if (o.name != null && !o.name.equals(o2.name)) return false; - if (o.outerName == null && o2.outerName != null) return false; - if (o.outerName != null && o.outerName.equals(o2.outerName)) return false; - return true; - } - private static boolean contains(List list, InnerClassNode node) - { - for (InnerClassNode n : list) - if (innerMatches(n, node)) - return true; - return false; - } - private static void processInners(ClassNode cClass, ClassNode sClass) - { - List cIners = cClass.innerClasses; - List sIners = sClass.innerClasses; - - for (InnerClassNode n : cIners) - { - if (!contains(sIners, n)) - sIners.add(n); - } - for (InnerClassNode n : sIners) - { - if (!contains(cIners, n)) - cIners.add(n); - } - } - - private ClassNode getClassNode(byte[] data) - { - ClassReader reader = new ClassReader(data); - ClassNode classNode = new ClassNode(); - reader.accept(classNode, 0); - return classNode; - } - - private void processFields(ClassNode cClass, ClassNode sClass) - { - List cFields = cClass.fields; - List sFields = sClass.fields; - - int serverFieldIdx = 0; - if (DEBUG) - System.out.printf("B: Server List: %s\nB: Client List: %s\n", Lists.transform(sFields, FieldName.instance), Lists.transform(cFields, FieldName.instance)); - for (int clientFieldIdx = 0; clientFieldIdx < cFields.size(); clientFieldIdx++) - { - FieldNode clientField = cFields.get(clientFieldIdx); - if (serverFieldIdx < sFields.size()) - { - FieldNode serverField = sFields.get(serverFieldIdx); - if (!clientField.name.equals(serverField.name)) - { - boolean foundServerField = false; - for (int serverFieldSearchIdx = serverFieldIdx + 1; serverFieldSearchIdx < sFields.size(); serverFieldSearchIdx++) - { - if (clientField.name.equals(sFields.get(serverFieldSearchIdx).name)) - { - foundServerField = true; - break; - } - } - // Found a server field match ahead in the list - walk to it and add the missing server fields to the client - if (foundServerField) - { - boolean foundClientField = false; - for (int clientFieldSearchIdx = clientFieldIdx + 1; clientFieldSearchIdx < cFields.size(); clientFieldSearchIdx++) - { - if (serverField.name.equals(cFields.get(clientFieldSearchIdx).name)) - { - foundClientField = true; - break; - } - } - if (!foundClientField) - { - if (serverField.visibleAnnotations == null) - { - serverField.visibleAnnotations = new ArrayList(); - } - serverField.visibleAnnotations.add(getSideAnn(false)); - cFields.add(clientFieldIdx, serverField); - if (DEBUG) - System.out.printf("1. Server List: %s\n1. Client List: %s\nIdx: %d %d\n", Lists.transform(sFields, FieldName.instance), Lists.transform(cFields, FieldName.instance), serverFieldIdx, clientFieldIdx); - } - } - else - { - if (clientField.visibleAnnotations == null) - { - clientField.visibleAnnotations = new ArrayList(); - } - clientField.visibleAnnotations.add(getSideAnn(true)); - sFields.add(serverFieldIdx, clientField); - if (DEBUG) - System.out.printf("2. Server List: %s\n2. Client List: %s\nIdx: %d %d\n", Lists.transform(sFields, FieldName.instance), Lists.transform(cFields, FieldName.instance), serverFieldIdx, clientFieldIdx); - } - } - } - else - { - if (clientField.visibleAnnotations == null) - { - clientField.visibleAnnotations = new ArrayList(); - } - clientField.visibleAnnotations.add(getSideAnn(true)); - sFields.add(serverFieldIdx, clientField); - if (DEBUG) - System.out.printf("3. Server List: %s\n3. Client List: %s\nIdx: %d %d\n", Lists.transform(sFields, FieldName.instance), Lists.transform(cFields, FieldName.instance), serverFieldIdx, clientFieldIdx); - } - serverFieldIdx++; - } - if (DEBUG) - System.out.printf("A. Server List: %s\nA. Client List: %s\n", Lists.transform(sFields, FieldName.instance), Lists.transform(cFields, FieldName.instance)); - if (sFields.size() != cFields.size()) - { - for (int x = cFields.size(); x < sFields.size(); x++) - { - FieldNode sF = sFields.get(x); - if (sF.visibleAnnotations == null) - { - sF.visibleAnnotations = new ArrayList(); - } - sF.visibleAnnotations.add(getSideAnn(true)); - cFields.add(x++, sF); - } - } - if (DEBUG) - System.out.printf("E. Server List: %s\nE. Client List: %s\n", Lists.transform(sFields, FieldName.instance), Lists.transform(cFields, FieldName.instance)); - } - - private static class FieldName implements Function - { - public static FieldName instance = new FieldName(); - - public String apply(FieldNode in) - { - return in.name; - } - } - - private void processMethods(ClassNode cClass, ClassNode sClass) - { - List cMethods = cClass.methods; - List sMethods = sClass.methods; - LinkedHashSet allMethods = Sets.newLinkedHashSet(); - - int cPos = 0; - int sPos = 0; - int cLen = cMethods.size(); - int sLen = sMethods.size(); - String clientName = ""; - String lastName = clientName; - String serverName = ""; - while (cPos < cLen || sPos < sLen) - { - do - { - if (sPos >= sLen) - { - break; - } - MethodNode sM = sMethods.get(sPos); - serverName = sM.name; - if (!serverName.equals(lastName) && cPos != cLen) - { - if (DEBUG) - { - System.out.printf("Server -skip : %s %s %d (%s %d) %d [%s]\n", sClass.name, clientName, cLen - cPos, serverName, sLen - sPos, allMethods.size(), lastName); - } - break; - } - MethodWrapper mw = new MethodWrapper(sM); - mw.server = true; - allMethods.add(mw); - if (DEBUG) - { - System.out.printf("Server *add* : %s %s %d (%s %d) %d [%s]\n", sClass.name, clientName, cLen - cPos, serverName, sLen - sPos, allMethods.size(), lastName); - } - sPos++; - } while (sPos < sLen); - do - { - if (cPos >= cLen) - { - break; - } - MethodNode cM = cMethods.get(cPos); - lastName = clientName; - clientName = cM.name; - if (!clientName.equals(lastName) && sPos != sLen) - { - if (DEBUG) - { - System.out.printf("Client -skip : %s %s %d (%s %d) %d [%s]\n", cClass.name, clientName, cLen - cPos, serverName, sLen - sPos, allMethods.size(), lastName); - } - break; - } - MethodWrapper mw = new MethodWrapper(cM); - mw.client = true; - allMethods.add(mw); - if (DEBUG) - { - System.out.printf("Client *add* : %s %s %d (%s %d) %d [%s]\n", cClass.name, clientName, cLen - cPos, serverName, sLen - sPos, allMethods.size(), lastName); - } - cPos++; - } while (cPos < cLen); - } - - cMethods.clear(); - sMethods.clear(); - - for (MethodWrapper mw : allMethods) - { - if (DEBUG) - { - System.out.println(mw); - } - cMethods.add(mw.node); - sMethods.add(mw.node); - if (mw.server && mw.client) - { - // no op - } - else - { - if (mw.node.visibleAnnotations == null) - { - mw.node.visibleAnnotations = Lists.newArrayListWithExpectedSize(1); - } - - mw.node.visibleAnnotations.add(getSideAnn(mw.client)); - } - } - } - - private class MethodWrapper - { - private MethodNode node; - public boolean client; - public boolean server; - - public MethodWrapper(MethodNode node) - { - this.node = node; - } - - @Override - public boolean equals(Object obj) - { - if (obj == null || !(obj instanceof MethodWrapper)) - { - return false; - } - MethodWrapper mw = (MethodWrapper) obj; - boolean eq = Objects.equal(node.name, mw.node.name) && Objects.equal(node.desc, mw.node.desc); - if (eq) - { - mw.client = client | mw.client; - mw.server = server | mw.server; - client = client | mw.client; - server = server | mw.server; - if (DEBUG) - { - System.out.printf(" eq: %s %s\n", this, mw); - } - } - return eq; - } - - @Override - public int hashCode() - { - return Objects.hashCode(node.name, node.desc); - } - - @Override - public String toString() - { - return MoreObjects.toStringHelper(this).add("name", node.name).add("desc", node.desc).add("server", server).add("client", client).toString(); - } - } -} From 2500e5310339de17d61dc58b90a5890cb07e5c7b Mon Sep 17 00:00:00 2001 From: Wagyourtail Date: Sat, 11 Dec 2021 06:41:16 -0700 Subject: [PATCH 09/28] not needed anymore --- .../configuration/providers/forge/ForgeUserdevProvider.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java index 278b6f6c3..26f9707b2 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java @@ -122,9 +122,6 @@ public void provide(DependencyInfo dependency, Consumer postPopulation getProject().getLogger().info("FG2 Userdev, using default mcp_config/universal..."); String defaultMCPPath = "de.oceanlabs.mcp:mcp:" + getExtension().getMinecraftProvider().minecraftVersion() + ":srg@zip"; - if (getExtension().getMinecraftProvider().minecraftVersion().equals("1.12.2")) { - defaultMCPPath = "de.oceanlabs.mcp:mcp_config:" + getExtension().getMinecraftProvider().minecraftVersion() + "@zip"; - } String defaultUniversalPath = "net.minecraftforge:forge:" + dependency.getResolvedVersion() + ":universal"; getProject().getLogger().info("Using default MCP path: " + defaultMCPPath); From 47d62bd7850bfe5cd8d56278a7cf2546c98678ef Mon Sep 17 00:00:00 2001 From: Wagyourtail Date: Sat, 11 Dec 2021 07:44:47 -0700 Subject: [PATCH 10/28] fix srg tiny mappings creation --- .../forge/FieldMigratedMappingsProvider.java | 2 +- .../providers/forge/SrgProvider.java | 9 ++++++++- .../mappings/MappingsProviderImpl.java | 2 +- .../net/fabricmc/loom/util/srg/SrgMerger.java | 20 +++++++++++++------ 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java index b2ebfa714..66a056523 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java @@ -116,7 +116,7 @@ public void manipulateMappings(Path mappingsJar) throws IOException { if (getExtension().shouldGenerateSrgTiny()) { if (Files.notExists(rawTinyMappingsWithSrg) || isRefreshDeps()) { // Merge tiny mappings with srg - SrgMerger.mergeSrg(getProject().getLogger(), getExtension().getMappingsProvider()::getMojmapSrgFileIfPossible, getRawSrgFile(), rawTinyMappings, rawTinyMappingsWithSrg, true); + SrgMerger.mergeSrg(getProject().getLogger(), getExtension().getMappingsProvider()::getMojmapSrgFileIfPossible, getRawSrgFile(), rawTinyMappings, rawTinyMappingsWithSrg, true, extension.getSrgProvider().isLegacy()); } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java index 061f85130..8802be3e2 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java @@ -58,6 +58,7 @@ public class SrgProvider extends DependencyProvider { private Path srg; private Boolean isTsrgV2; + private boolean isLegacy; private Path mergedMojangRaw; private Path mergedMojang; private Path mergedMojangTrimmed; @@ -85,7 +86,9 @@ public void provide(DependencyInfo dependency, Consumer postPopulation } try (BufferedReader reader = Files.newBufferedReader(srg)) { - isTsrgV2 = reader.readLine().startsWith("tsrg2"); + String line = reader.readLine(); + isTsrgV2 = line.startsWith("tsrg2"); + isLegacy = line.startsWith("PK:") || line.startsWith("CL:"); } if (isTsrgV2) { @@ -210,6 +213,10 @@ public boolean isTsrgV2() { return isTsrgV2; } + public boolean isLegacy() { + return isLegacy; + } + public static Path getMojmapTsrg(Project project, LoomGradleExtension extension) throws IOException { if (mojmapTsrg != null) return mojmapTsrg; diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java index bdfecba44..ef4b3f2a0 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java @@ -157,7 +157,7 @@ public void provide(DependencyInfo dependency, Consumer postPopulation if (getExtension().shouldGenerateSrgTiny()) { if (Files.notExists(tinyMappingsWithSrg) || isRefreshDeps()) { // Merge tiny mappings with srg - SrgMerger.mergeSrg(getProject().getLogger(), getExtension().getMappingsProvider()::getMojmapSrgFileIfPossible, getRawSrgFile(), tinyMappings, tinyMappingsWithSrg, true); + SrgMerger.mergeSrg(getProject().getLogger(), getExtension().getMappingsProvider()::getMojmapSrgFileIfPossible, getRawSrgFile(), tinyMappings, tinyMappingsWithSrg, true, getExtension().getSrgProvider().isLegacy()); } mappingTreeWithSrg = readMappings(tinyMappingsWithSrg); diff --git a/src/main/java/net/fabricmc/loom/util/srg/SrgMerger.java b/src/main/java/net/fabricmc/loom/util/srg/SrgMerger.java index ede2dbad8..5025e0c98 100644 --- a/src/main/java/net/fabricmc/loom/util/srg/SrgMerger.java +++ b/src/main/java/net/fabricmc/loom/util/srg/SrgMerger.java @@ -44,6 +44,7 @@ import com.google.common.base.MoreObjects; import com.google.common.base.Stopwatch; +import net.fabricmc.mappingio.format.SrgReader; import org.apache.commons.io.IOUtils; import org.gradle.api.logging.Logger; import org.jetbrains.annotations.Nullable; @@ -74,8 +75,11 @@ public final class SrgMerger { private final boolean lenient; private final Set methodSrgNames = new HashSet<>(); - public SrgMerger(Logger logger, Path srg, @Nullable Supplier mojmap, Path tiny, boolean lenient) throws IOException { + private final boolean isSrg; + + public SrgMerger(Logger logger, Path srg, @Nullable Supplier mojmap, Path tiny, boolean lenient, boolean isSrg) throws IOException { this.logger = logger; + this.isSrg = isSrg; this.srg = readSrg(srg, mojmap); this.src = new MemoryMappingTree(); this.output = new MemoryMappingTree(); @@ -119,10 +123,10 @@ public MemoryMappingTree merge() throws IOException { * @throws MappingException if the input tiny tree's default namespace is not 'official' * or if an element mentioned in the SRG file does not have tiny mappings */ - public static void mergeSrg(Logger logger, @Nullable Supplier mojmap, Path srg, Path tiny, Path out, boolean lenient) + public static void mergeSrg(Logger logger, @Nullable Supplier mojmap, Path srg, Path tiny, Path out, boolean lenient, boolean isSrg) throws IOException, MappingException { Stopwatch stopwatch = Stopwatch.createStarted(); - MemoryMappingTree tree = mergeSrg(logger, mojmap, srg, tiny, lenient); + MemoryMappingTree tree = mergeSrg(logger, mojmap, srg, tiny, lenient, isSrg); try (Tiny2Writer writer = new Tiny2Writer(Files.newBufferedWriter(out), false)) { tree.accept(writer); @@ -145,9 +149,9 @@ public static void mergeSrg(Logger logger, @Nullable Supplier mojmap, Path * @throws MappingException if the input tiny tree's default namespace is not 'official' * or if an element mentioned in the SRG file does not have tiny mappings */ - public static MemoryMappingTree mergeSrg(Logger logger, @Nullable Supplier mojmap, Path srg, Path tiny, boolean lenient) + public static MemoryMappingTree mergeSrg(Logger logger, @Nullable Supplier mojmap, Path srg, Path tiny, boolean lenient, boolean isSrg) throws IOException, MappingException { - return new SrgMerger(logger, srg, mojmap, tiny, lenient).merge(); + return new SrgMerger(logger, srg, mojmap, tiny, lenient, isSrg).merge(); } private MemoryMappingTree readSrg(Path srg, @Nullable Supplier mojmap) throws IOException { @@ -159,7 +163,11 @@ private MemoryMappingTree readSrg(Path srg, @Nullable Supplier mojmap) thr } MemoryMappingTree tsrg = new MemoryMappingTree(); - TsrgReader.read(new StringReader(content), tsrg); + if (isSrg) { + SrgReader.read(new StringReader(content), tsrg); + } else { + TsrgReader.read(new StringReader(content), tsrg); + } return tsrg; } } From 689eb98ca4ca2257bda6949b48296d1f1eb7a4d8 Mon Sep 17 00:00:00 2001 From: Wagyourtail Date: Sat, 11 Dec 2021 07:55:39 -0700 Subject: [PATCH 11/28] well, it works, cleanup needed --- .../providers/forge/McpConfigProvider.java | 8 ++++---- .../providers/forge/MinecraftPatchedProvider.java | 12 ++++++------ .../providers/mappings/MappingsProviderImpl.java | 2 +- .../loom/util/srg/SpecialSourceExecutor.java | 6 +++--- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java index 0ab287a77..69ed6ded7 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java @@ -58,14 +58,14 @@ public class McpConfigProvider extends DependencyProvider { private String mappingsPath; private RemapAction remapAction; - private boolean isSRG = false; + private boolean isFG2 = false; public McpConfigProvider(Project project) { super(project); } - public boolean isSRG() { - return isSRG; + public boolean isFG2() { + return isFG2; } @Override @@ -78,7 +78,7 @@ public void provide(DependencyInfo dependency, Consumer postPopulation if (getExtension().getForgeProvider().isFG2()) { official = false; mappingsPath = ZipUtils.contains(mcpZip, "joined.srg") ? "joined.srg" : "config/joined.tsrg"; - isSRG = mappingsPath.endsWith(".srg"); + isFG2 = mappingsPath.endsWith(".srg"); remapAction = new FG2RemapAction(getProject()); if (!Files.exists(mcp) || isRefreshDeps()) { Files.copy(mcpZip, mcp, StandardCopyOption.REPLACE_EXISTING); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java index fe267d7b0..c500ffbb7 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java @@ -346,15 +346,15 @@ private void produceSrgJar(boolean official, Path clientJar, Path serverJar) thr Path tmpSrg = getToSrgMappings(); Set mcLibs = getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_DEPENDENCIES).resolve(); - boolean isSrg = getExtension().getMcpConfigProvider().isSRG(); + boolean isSrg = getExtension().getSrgProvider().isLegacy(); Path[] clientJarOut = new Path[] { null }; Path[] serverJarOut = new Path[] { null }; ThreadingUtils.run(() -> { - clientJarOut[0] = SpecialSourceExecutor.produceSrgJar(getExtension().getMcpConfigProvider().getRemapAction(), getProject(), "client", mcLibs, clientJar, tmpSrg, isSrg, false); + clientJarOut[0] = SpecialSourceExecutor.produceSrgJar(getExtension().getMcpConfigProvider().getRemapAction(), getProject(), "client", mcLibs, clientJar, tmpSrg, isSrg, true); }, () -> { - serverJarOut[0] = SpecialSourceExecutor.produceSrgJar(getExtension().getMcpConfigProvider().getRemapAction(), getProject(), "server", mcLibs, serverJar, tmpSrg, isSrg, false); + serverJarOut[0] = SpecialSourceExecutor.produceSrgJar(getExtension().getMcpConfigProvider().getRemapAction(), getProject(), "server", mcLibs, serverJar, tmpSrg, isSrg, true); }); // if (isSrg) { @@ -641,7 +641,7 @@ private void patchJars(File clean, File output, Path patches, String side) throw // Failed to replace logger filter, just ignore } - if (getExtension().getMcpConfigProvider().isSRG()) { + if (getExtension().getMcpConfigProvider().isFG2()) { new TaskApplyBinPatches().doTask(getProject(), clean.getAbsoluteFile(), patches.toFile().getAbsoluteFile(), output.getAbsoluteFile(), side); @@ -665,11 +665,11 @@ private void mergeJars(Logger logger) throws Exception { // This will change if upstream Loom adds the possibility for separate projects/source sets per environment. - if (getExtension().getMcpConfigProvider().isSRG()) { + if (getExtension().getMcpConfigProvider().isFG2()) { logger.lifecycle(":merging jars"); Path tmpSrg = getToSrgMappings(); Set mcLibs = getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_DEPENDENCIES).resolve(); - Files.copy(SpecialSourceExecutor.produceSrgJar(getExtension().getMcpConfigProvider().getRemapAction(), getProject(), "merged", mcLibs, minecraftClientPatchedSrgJar.toPath(), tmpSrg, true, true), minecraftMergedPatchedSrgJar.toPath()); + Files.copy(SpecialSourceExecutor.produceSrgJar(getExtension().getMcpConfigProvider().getRemapAction(), getProject(), "merged", mcLibs, minecraftClientPatchedSrgJar.toPath(), tmpSrg, getExtension().getSrgProvider().isLegacy(), false), minecraftMergedPatchedSrgJar.toPath()); } else { Files.copy(minecraftClientPatchedSrgJar.toPath(), minecraftMergedPatchedSrgJar.toPath()); } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java index ef4b3f2a0..7dd8a69eb 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java @@ -343,7 +343,7 @@ private void readAndMergeMCP(Path mcpJar, Consumer postPopulationSched } Path srgPath = getRawSrgFile(); - TinyFile file = new MCPReader(intermediaryTinyPath, srgPath, getExtension().getMcpConfigProvider().isSRG()).read(mcpJar); + TinyFile file = new MCPReader(intermediaryTinyPath, srgPath, getExtension().getSrgProvider().isLegacy()).read(mcpJar); TinyV2Writer.write(file, tinyMappings); } diff --git a/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java b/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java index 98b625c22..64b47b718 100644 --- a/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java +++ b/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java @@ -55,10 +55,10 @@ private static String trimLeadingSlash(String string) { return string; } - public static Path produceSrgJar(RemapAction remapAction, Project project, String side, Set mcLibs, Path officialJar, Path mappings, boolean isSrg, boolean actuallyRemapSrg) + public static Path produceSrgJar(RemapAction remapAction, Project project, String side, Set mcLibs, Path officialJar, Path mappings, boolean isLegacy, boolean isFG2) throws Exception { Set filter; - if (isSrg) { + if (isLegacy) { filter = Files.readAllLines(mappings, StandardCharsets.UTF_8).stream() .filter(s -> s.startsWith("CL:")) .map(s -> s.split(" ")[1] + ".class") @@ -122,7 +122,7 @@ public static Path produceSrgJar(RemapAction remapAction, Project project, Strin Path workingDir = tmpDir(); - if (!isSrg || actuallyRemapSrg) { + if (!isFG2) { project.javaexec(spec -> { spec.setArgs(args); spec.setClasspath(remapAction.getClasspath()); From 4d83a458fe828b133c31adfce65c417f5dfcb95a Mon Sep 17 00:00:00 2001 From: Wagyourtail Date: Sat, 11 Dec 2021 08:49:44 -0700 Subject: [PATCH 12/28] well, that didn't quite work... --- .../providers/forge/ForgeUserdevProvider.java | 28 +++++++++++++++---- .../providers/forge/McpConfigProvider.java | 2 +- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java index 26f9707b2..f460c21f3 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java @@ -34,16 +34,14 @@ import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.stream.StreamSupport; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonObject; @@ -143,7 +141,27 @@ public void provide(DependencyInfo dependency, Consumer postPopulation } } - //TODO: hard-code fg2 run configs + for (String name : new String[] {"client", "server"}) { + LaunchProviderSettings launchSettings = getExtension().getLaunchConfigs().findByName(name); + RunConfigSettings settings = getExtension().getRunConfigs().findByName(name); + + if (launchSettings != null) { + launchSettings.evaluateLater(() -> { + launchSettings.arg(Arrays.stream(json.get("minecraftArguments").getAsString().split(" ")) + .map(this::processTemplates) + .collect(Collectors.toList())); + + }); + } + + if (settings != null) { + settings.evaluateLater(() -> { + settings.defaultMainClass("net.minecraft.launchwrapper.Launch"); + }); + } + + } + } else { getProject().getLogger().info("FG3+ Userdev"); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java index 69ed6ded7..45177de43 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java @@ -175,7 +175,7 @@ public static class FG2RemapAction implements RemapAction { public FG2RemapAction(Project project) { this.project = project; - this.name = "net.md-5:SpecialSource:1.7.4:shaded"; + this.name = "net.md-5:SpecialSource:1.8.3:shaded"; this.mainClasspath = DependencyDownloader.download(project, this.name, false, true) .getSingleFile(); this.classpath = DependencyDownloader.download(project, this.name, true, true); From 24ce766f50773faa387087bc9ece0c6bc24c6a00 Mon Sep 17 00:00:00 2001 From: Wagyourtail Date: Sun, 12 Dec 2021 02:55:13 -0700 Subject: [PATCH 13/28] cleanup --- build.gradle | 3 +- .../forge/FieldMigratedMappingsProvider.java | 31 +- .../providers/forge/McpConfigProvider.java | 79 +- .../forge/MinecraftPatchedProvider.java | 1346 ++++---- .../forge/fg2/FG2TaskApplyBinPatches.java} | 28 +- .../fg2/MinecraftPatchedProviderFG2.java | 270 ++ .../fg3/MinecraftPatchedProviderFG3.java | 262 ++ .../mappings/MappingsProviderImpl.java | 15 +- .../loom/util/srg/SpecialSourceExecutor.java | 83 +- .../java/util/jar/pack/AdaptiveCoding.java | 297 ++ .../com/sun/java/util/jar/pack/Attribute.java | 1695 ++++++++++ .../sun/java/util/jar/pack/BandStructure.java | 2759 +++++++++++++++++ .../sun/java/util/jar/pack/ClassReader.java | 636 ++++ .../sun/java/util/jar/pack/ClassWriter.java | 310 ++ .../com/sun/java/util/jar/pack/Code.java | 396 +++ .../com/sun/java/util/jar/pack/Coding.java | 906 ++++++ .../sun/java/util/jar/pack/CodingChooser.java | 1489 +++++++++ .../sun/java/util/jar/pack/CodingMethod.java | 43 + .../sun/java/util/jar/pack/ConstantPool.java | 1657 ++++++++++ .../com/sun/java/util/jar/pack/Constants.java | 503 +++ .../com/sun/java/util/jar/pack/Driver.java | 749 +++++ .../java/util/jar/pack/DriverResource.java | 136 + .../java/util/jar/pack/DriverResource_ja.java | 136 + .../util/jar/pack/DriverResource_zh_CN.java | 136 + .../com/sun/java/util/jar/pack/FixedList.java | 174 ++ .../com/sun/java/util/jar/pack/Fixups.java | 570 ++++ .../com/sun/java/util/jar/pack/Histogram.java | 818 +++++ .../sun/java/util/jar/pack/Instruction.java | 687 ++++ .../sun/java/util/jar/pack/NativeUnpack.java | 331 ++ .../com/sun/java/util/jar/pack/Package.java | 1375 ++++++++ .../sun/java/util/jar/pack/PackageReader.java | 2371 ++++++++++++++ .../sun/java/util/jar/pack/PackageWriter.java | 1738 +++++++++++ .../sun/java/util/jar/pack/PackerImpl.java | 607 ++++ .../java/util/jar/pack/PopulationCoding.java | 499 +++ .../com/sun/java/util/jar/pack/PropMap.java | 315 ++ .../com/sun/java/util/jar/pack/TLGlobals.java | 115 + .../sun/java/util/jar/pack/UnpackerImpl.java | 264 ++ .../com/sun/java/util/jar/pack/Utils.java | 321 ++ .../fabricmc/shade/java/util/jar/Pack200.java | 738 +++++ .../minecraftforge/fml/relauncher/Side.java | 43 - .../fml/relauncher/SideOnly.java | 33 - 41 files changed, 23944 insertions(+), 1020 deletions(-) rename src/main/java/net/{minecraftforge/gradle/tasks/TaskApplyBinPatches.java => fabricmc/loom/configuration/providers/forge/fg2/FG2TaskApplyBinPatches.java} (90%) create mode 100644 src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/MinecraftPatchedProviderFG2.java create mode 100644 src/main/java/net/fabricmc/loom/configuration/providers/forge/fg3/MinecraftPatchedProviderFG3.java create mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/AdaptiveCoding.java create mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Attribute.java create mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/BandStructure.java create mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/ClassReader.java create mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/ClassWriter.java create mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Code.java create mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Coding.java create mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/CodingChooser.java create mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/CodingMethod.java create mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/ConstantPool.java create mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Constants.java create mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Driver.java create mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/DriverResource.java create mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/DriverResource_ja.java create mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/DriverResource_zh_CN.java create mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/FixedList.java create mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Fixups.java create mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Histogram.java create mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Instruction.java create mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/NativeUnpack.java create mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Package.java create mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/PackageReader.java create mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/PackageWriter.java create mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/PackerImpl.java create mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/PopulationCoding.java create mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/PropMap.java create mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/TLGlobals.java create mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/UnpackerImpl.java create mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Utils.java create mode 100644 src/main/java/net/fabricmc/shade/java/util/jar/Pack200.java delete mode 100644 src/main/java/net/minecraftforge/fml/relauncher/Side.java delete mode 100644 src/main/java/net/minecraftforge/fml/relauncher/SideOnly.java diff --git a/build.gradle b/build.gradle index d800caaa4..2cc27e478 100644 --- a/build.gradle +++ b/build.gradle @@ -48,7 +48,6 @@ repositories { excludeGroupByRegex "org\\.eclipse\\.?.*" } } - maven { url "https://jitpack.io" } mavenLocal() } @@ -114,7 +113,7 @@ dependencies { // Forge patches implementation ('net.minecraftforge:installertools:1.2.0') - implementation ('com.github.wagyourtail:BinaryPatcher:383bae7aa3') + implementation ('net.minecraftforge:binarypatcher:1.1.1') implementation ('org.cadixdev:lorenz:0.5.3') implementation ('org.cadixdev:lorenz-asm:0.5.3') implementation ('de.oceanlabs.mcp:mcinjector:3.8.0') diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java index 66a056523..0f9d128fd 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java @@ -47,7 +47,9 @@ import com.google.common.reflect.TypeToken; import com.google.gson.Gson; import dev.architectury.refmapremapper.utils.DescriptorRemapper; +import net.fabricmc.loom.configuration.providers.forge.fg3.MinecraftPatchedProviderFG3; import org.gradle.api.Project; +import org.gradle.internal.normalization.java.impl.FieldMember; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.FieldVisitor; @@ -203,20 +205,25 @@ public FieldVisitor visitField(int access, String name, String descriptor, Strin Visitor visitor = new Visitor(Opcodes.ASM9); - for (MinecraftPatchedProvider.Environment environment : MinecraftPatchedProvider.Environment.values()) { - File patchedSrgJar = environment.patchedSrgJar.apply(extension.getMappingsProvider().patchedProvider); - FileSystemUtil.Delegate system = FileSystemUtil.getJarFileSystem(patchedSrgJar, false); - completer.onComplete(value -> system.close()); - - for (Path fsPath : (Iterable) Files.walk(system.get().getPath("/"))::iterator) { - if (Files.isRegularFile(fsPath) && fsPath.toString().endsWith(".class")) { - completer.add(() -> { - byte[] bytes = Files.readAllBytes(fsPath); - new ClassReader(bytes).accept(visitor, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); - }); + if (extension.getMappingsProvider().patchedProvider instanceof MinecraftPatchedProviderFG3 pp3) { + for (MinecraftPatchedProviderFG3.Environment environment : MinecraftPatchedProviderFG3.Environment.values()) { + File patchedSrgJar = environment.patchedSrgJar.apply(pp3); + FileSystemUtil.Delegate system = FileSystemUtil.getJarFileSystem(patchedSrgJar, false); + completer.onComplete(value -> system.close()); + + for (Path fsPath : (Iterable) Files.walk(system.get().getPath("/"))::iterator) { + if (Files.isRegularFile(fsPath) && fsPath.toString().endsWith(".class")) { + completer.add(() -> { + byte[] bytes = Files.readAllBytes(fsPath); + new ClassReader(bytes).accept( + visitor, + ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES + ); + }); + } } } - } + } //TODO: DOES FG2 NEED THIS???? completer.complete(); Map migratedFields = new HashMap<>(); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java index 45177de43..cbcca4525 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java @@ -79,7 +79,7 @@ public void provide(DependencyInfo dependency, Consumer postPopulation official = false; mappingsPath = ZipUtils.contains(mcpZip, "joined.srg") ? "joined.srg" : "config/joined.tsrg"; isFG2 = mappingsPath.endsWith(".srg"); - remapAction = new FG2RemapAction(getProject()); + remapAction = null; if (!Files.exists(mcp) || isRefreshDeps()) { Files.copy(mcpZip, mcp, StandardCopyOption.REPLACE_EXISTING); } @@ -165,83 +165,6 @@ public interface RemapAction { List getArgs(Path input, Path output, Path mappings, FileCollection libraries); } - public static class FG2RemapAction implements RemapAction { - private final Project project; - private final String name; - private final File mainClasspath; - private final FileCollection classpath; - private final List args; - private boolean hasLibraries; - - public FG2RemapAction(Project project) { - this.project = project; - this.name = "net.md-5:SpecialSource:1.8.3:shaded"; - this.mainClasspath = DependencyDownloader.download(project, this.name, false, true) - .getSingleFile(); - this.classpath = DependencyDownloader.download(project, this.name, true, true); - this.args = List.of( - "--in-jar", - "{input}", - "--out-jar", - "{output}", - "--srg-in", - "{mappings}", - "--kill-source" - ); - } - - @Override - public FileCollection getClasspath() { - return classpath; - } - - @Override - public String getMainClass() { - try { - byte[] manifestBytes = ZipUtils.unpackNullable(mainClasspath.toPath(), "META-INF/MANIFEST.MF"); - - if (manifestBytes == null) { - throw new RuntimeException("Could not find MANIFEST.MF in " + mainClasspath + "!"); - } - - Manifest manifest = new Manifest(new ByteArrayInputStream(manifestBytes)); - Attributes attributes = manifest.getMainAttributes(); - String value = attributes.getValue(Attributes.Name.MAIN_CLASS); - - if (value == null) { - throw new RuntimeException("Could not find main class in " + mainClasspath + "!"); - } else { - return value; - } - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Override - public List getArgs(Path input, Path output, Path mappings, FileCollection libraries) { - List args = this.args.stream() - .map(str -> { - return switch (str) { - case "{input}" -> input.toAbsolutePath().toString(); - case "{output}" -> output.toAbsolutePath().toString(); - case "{mappings}" -> mappings.toAbsolutePath().toString(); - default -> str; - }; - }) - .collect(Collectors.toList()); - - if (hasLibraries) { - for (File file : libraries) { - args.add("-e=" + file.getAbsolutePath()); - } - } - - return args; - } - - } - public static class ConfigDefinedRemapAction implements RemapAction { private final Project project; private final String name; diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java index c500ffbb7..f5d3a999b 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java @@ -1,62 +1,5 @@ -/* - * This file is part of fabric-loom, licensed under the MIT License (MIT). - * - * Copyright (c) 2020-2021 FabricMC - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - package net.fabricmc.loom.configuration.providers.forge; -import java.io.ByteArrayInputStream; -import java.io.Closeable; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.PrintStream; -import java.io.UncheckedIOException; -import java.net.URI; -import java.nio.file.FileSystem; -import java.nio.file.FileSystems; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.nio.file.StandardOpenOption; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.jar.Attributes; -import java.util.jar.Manifest; -import java.util.regex.Pattern; -import java.util.stream.Stream; - import com.google.common.base.Preconditions; import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableList; @@ -67,8 +10,12 @@ import dev.architectury.tinyremapper.InputTag; import dev.architectury.tinyremapper.OutputConsumerPath; import dev.architectury.tinyremapper.TinyRemapper; -import net.minecraftforge.binarypatcher.ConsoleTool; -import net.minecraftforge.gradle.tasks.TaskApplyBinPatches; +import net.fabricmc.loom.configuration.DependencyProvider; +import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl; +import net.fabricmc.loom.util.*; +import net.fabricmc.loom.util.function.FsPathConsumer; +import net.fabricmc.loom.util.srg.InnerClassRemapper; +import net.fabricmc.mappingio.tree.MemoryMappingTree; import org.apache.commons.io.output.NullOutputStream; import org.gradle.api.Project; import org.gradle.api.file.ConfigurableFileCollection; @@ -78,736 +25,559 @@ import org.gradle.api.logging.configuration.ShowStacktrace; import org.gradle.api.plugins.JavaPluginConvention; import org.gradle.api.tasks.SourceSet; -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.Label; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; +import org.objectweb.asm.*; import org.objectweb.asm.tree.ClassNode; -import net.fabricmc.loom.configuration.DependencyProvider; -import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl; -import net.fabricmc.loom.util.Constants; -import net.fabricmc.loom.util.DependencyDownloader; -import net.fabricmc.loom.util.FileSystemUtil; -import net.fabricmc.loom.util.MappingsProviderVerbose; -import net.fabricmc.loom.util.ThreadingUtils; -import net.fabricmc.loom.util.TinyRemapperHelper; -import net.fabricmc.loom.util.ZipUtils; -import net.fabricmc.loom.util.function.FsPathConsumer; -import net.fabricmc.loom.util.srg.InnerClassRemapper; -import net.fabricmc.loom.util.srg.SpecialSourceExecutor; -import net.fabricmc.mappingio.tree.MemoryMappingTree; +import java.io.*; +import java.net.URI; +import java.nio.file.*; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.jar.Attributes; +import java.util.jar.Manifest; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +public abstract class MinecraftPatchedProvider extends DependencyProvider { + private static final String LOOM_PATCH_VERSION_KEY = "Loom-Patch-Version"; + private static final String CURRENT_LOOM_PATCH_VERSION = "5"; + private static final String NAME_MAPPING_SERVICE_PATH = "/inject/META-INF/services/cpw.mods.modlauncher.api.INameMappingService"; + + // step 3 - fg2: srg transform, fg3: merge + protected File minecraftMergedPatchedSrgJar; + + // step 4 - Access transform + protected File minecraftMergedPatchedSrgAtJar; + + // step 5 - Offical Mapped Patched AT & Forge + protected File minecraftMergedPatchedAtJar; + protected File forgeMergedJar; + protected File minecraftClientExtra; + protected File projectAtHash; + + protected Set projectAts = new HashSet<>(); + protected boolean atDirty = false; + protected boolean filesDirty = false; + protected Path mcpConfigMappings; + + + public MinecraftPatchedProvider(Project project) { + super(project); + } + + @Override + public void provide(DependencyInfo dependency, Consumer postPopulationScheduler) throws Exception { + initFiles(); + testCleanAllCaches(); + beginTransform(); + } + + protected abstract void beginTransform() throws Exception; + + public abstract void endTransform() throws Exception; + + @Override + public String getTargetConfig() { + return Constants.Configurations.MINECRAFT; + } + + + public void initFiles() throws IOException { + filesDirty = false; + projectAtHash = new File(getDirectories().getProjectPersistentCache(), "at.sha256"); + ConfigurableFileCollection accessTransformers = getExtension().getForge().getAccessTransformers(); + accessTransformers.finalizeValue(); + projectAts = accessTransformers.getFiles(); + + if (projectAts.isEmpty()) { + SourceSet main = getProject().getConvention().findPlugin(JavaPluginConvention.class).getSourceSets().getByName("main"); + + for (File srcDir : main.getResources().getSrcDirs()) { + File projectAt = new File(srcDir, Constants.Forge.ACCESS_TRANSFORMER_PATH); + + if (projectAt.exists()) { + this.projectAts.add(projectAt); + break; + } + } + } + + if (isRefreshDeps() || !projectAtHash.exists()) { + writeAtHash(); + atDirty = !projectAts.isEmpty(); + } else { + byte[] expected = com.google.common.io.Files.asByteSource(projectAtHash).read(); + byte[] current = getProjectAtsHash(); + boolean mismatched = !Arrays.equals(current, expected); + + if (mismatched) { + writeAtHash(); + } + + atDirty = mismatched; + } + + MinecraftProviderImpl minecraftProvider = getExtension().getMinecraftProvider(); + PatchProvider patchProvider = getExtension().getPatchProvider(); + String minecraftVersion = minecraftProvider.minecraftVersion(); + String patchId = "forge-" + getExtension().getForgeProvider().getVersion().getCombined() + "-"; + + if (getExtension().isForgeAndOfficial()) { + minecraftProvider.setJarPrefix(patchId); + } + + File globalCache = getExtension().getForgeProvider().getGlobalCache(); + File projectDir = usesProjectCache() ? getExtension().getForgeProvider().getProjectCache() : globalCache; + projectDir.mkdirs(); + + forgeMergedJar = getExtension().isForgeAndOfficial() ? null : new File(globalCache, "forge-official.jar"); + minecraftMergedPatchedAtJar = new File(projectDir, "merged-at-patched.jar"); + minecraftClientExtra = new File(globalCache, "forge-client-extra.jar"); + minecraftMergedPatchedSrgJar = new File(globalCache, "merged-srg-patched.jar"); + minecraftMergedPatchedSrgAtJar = new File(projectDir, "merged-srg-at-patched.jar"); + + } + + public void testCleanAllCaches() throws IOException { + if (isRefreshDeps() || Stream.of(getGlobalCaches()).anyMatch(((Predicate) File::exists).negate()) + || !isPatchedJarUpToDate(minecraftMergedPatchedAtJar)) { + cleanAllCache(); + } else if (atDirty || Stream.of(getProjectCache()).anyMatch(((Predicate) File::exists).negate())) { + cleanProjectCache(); + } + } + + public void applyLoomPatchVersion(Path target) throws IOException { + try (FileSystemUtil.Delegate delegate = FileSystemUtil.getJarFileSystem(target, false)) { + Path manifestPath = delegate.get().getPath("META-INF/MANIFEST.MF"); + + Preconditions.checkArgument(Files.exists(manifestPath), "META-INF/MANIFEST.MF does not exist in patched srg jar!"); + Manifest manifest = new Manifest(); + + if (Files.exists(manifestPath)) { + try (InputStream stream = Files.newInputStream(manifestPath)) { + manifest.read(stream); + manifest.getMainAttributes().putValue(LOOM_PATCH_VERSION_KEY, CURRENT_LOOM_PATCH_VERSION); + } + } + + try (OutputStream stream = Files.newOutputStream(manifestPath, StandardOpenOption.CREATE)) { + manifest.write(stream); + } + } + } + + protected boolean isPatchedJarUpToDate(File jar) throws IOException { + if (!jar.exists()) return false; + + byte[] manifestBytes = ZipUtils.unpackNullable(jar.toPath(), "META-INF/MANIFEST.MF"); + + if (manifestBytes == null) { + return false; + } + + Manifest manifest = new Manifest(new ByteArrayInputStream(manifestBytes)); + Attributes attributes = manifest.getMainAttributes(); + String value = attributes.getValue(LOOM_PATCH_VERSION_KEY); + + if (Objects.equals(value, CURRENT_LOOM_PATCH_VERSION)) { + return true; + } else { + getProject().getLogger().lifecycle(":forge patched jars not up to date. current version: " + value); + return false; + } + } + + public boolean usesProjectCache() { + return !projectAts.isEmpty(); + } + + public File getMergedJar() { + return minecraftMergedPatchedAtJar; + } + + public File getForgeMergedJar() { + return forgeMergedJar; + } + + public boolean isAtDirty() { + return atDirty || filesDirty; + } + + + public void cleanAllCache() { + for (File file : getGlobalCaches()) { + file.delete(); + } + + cleanProjectCache(); + } + + protected File[] getGlobalCaches() { + File[] files = { + minecraftClientExtra + }; + + if (forgeMergedJar != null) { + Arrays.copyOf(files, files.length + 1); + files[files.length - 1] = forgeMergedJar; + } + + return files; + } + + public void cleanProjectCache() { + for (File file : getProjectCache()) { + file.delete(); + } + } + + protected File[] getProjectCache() { + return new File[] { + minecraftMergedPatchedSrgAtJar, + minecraftMergedPatchedAtJar + }; + } + + private void writeAtHash() throws IOException { + try (FileOutputStream out = new FileOutputStream(projectAtHash)) { + out.write(getProjectAtsHash()); + } + } + + private byte[] getProjectAtsHash() throws IOException { + if (projectAts.isEmpty()) return ByteSource.empty().hash(Hashing.sha256()).asBytes(); + List currentBytes = new ArrayList<>(); + + for (File projectAt : projectAts) { + currentBytes.add(com.google.common.io.Files.asByteSource(projectAt)); + } + + return ByteSource.concat(currentBytes).hash(Hashing.sha256()).asBytes(); + } + + private void walkFileSystems(File source, File target, Predicate filter, Function> toWalk, FsPathConsumer action) + throws IOException { + try (FileSystemUtil.Delegate sourceFs = FileSystemUtil.getJarFileSystem(source, false); + FileSystemUtil.Delegate targetFs = FileSystemUtil.getJarFileSystem(target, false)) { + for (Path sourceDir : toWalk.apply(sourceFs.get())) { + Path dir = sourceDir.toAbsolutePath(); + if (!Files.exists(dir)) continue; + Files.walk(dir) + .filter(Files::isRegularFile) + .filter(filter) + .forEach(it -> { + boolean root = dir.getParent() == null; + + try { + Path relativeSource = root ? it : dir.relativize(it); + Path targetPath = targetFs.get().getPath(relativeSource.toString()); + action.accept(sourceFs.get(), targetFs.get(), it, targetPath); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + } + } + } + + protected abstract void patchJars(File clean, File output, Path patches, String side) throws IOException; + + protected abstract void mergeJars(Logger logger) throws Exception; + + protected void copyMissingClasses(File source, File target) throws IOException { + walkFileSystems(source, target, it -> it.toString().endsWith(".class"), (sourceFs, targetFs, sourcePath, targetPath) -> { + if (Files.exists(targetPath)) return; + Path parent = targetPath.getParent(); + + if (parent != null) { + Files.createDirectories(parent); + } + + Files.copy(sourcePath, targetPath); + }); + } + + protected void copyNonClassFiles(File source, File target) throws IOException { + Predicate filter = getExtension().isForgeAndOfficial() ? file -> { + String s = file.toString(); + return !s.endsWith(".class"); + } : file -> { + String s = file.toString(); + return !s.endsWith(".class") || (s.startsWith("META-INF") && !s.startsWith("META-INF/services")); + }; + + walkFileSystems(source, target, filter, this::copyReplacing); + } + + private void walkFileSystems(File source, File target, Predicate filter, FsPathConsumer action) throws IOException { + walkFileSystems(source, target, filter, FileSystem::getRootDirectories, action); + } + + private void copyAll(File source, File target) throws IOException { + walkFileSystems(source, target, it -> true, this::copyReplacing); + } + + private void copyReplacing(FileSystem sourceFs, FileSystem targetFs, Path sourcePath, Path targetPath) throws IOException { + Path parent = targetPath.getParent(); + + if (parent != null) { + Files.createDirectories(parent); + } + + Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING); + } + + protected void copyUserdevFiles(File source, File target) throws IOException { + // Removes the Forge name mapping service definition so that our own is used. + // If there are multiple name mapping services with the same "understanding" pair + // (source -> target namespace pair), modlauncher throws a fit and will crash. + // To use our YarnNamingService instead of MCPNamingService, we have to remove this file. + Predicate filter = file -> !file.toString().endsWith(".class") && !file.toString().equals(NAME_MAPPING_SERVICE_PATH); + + walkFileSystems(source, target, filter, fs -> Collections.singleton(fs.getPath("inject")), (sourceFs, targetFs, sourcePath, targetPath) -> { + Path parent = targetPath.getParent(); + + if (parent != null) { + Files.createDirectories(parent); + } + + Files.copy(sourcePath, targetPath); + }); + } + + protected void deleteParameterNames(File jarFile) throws Exception { + getProject().getLogger().info(":deleting parameter names for " + jarFile.getAbsolutePath()); + Stopwatch stopwatch = Stopwatch.createStarted(); + + try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + jarFile.toURI()), ImmutableMap.of("create", false))) { + ThreadingUtils.TaskCompleter completer = ThreadingUtils.taskCompleter(); + Pattern vignetteParameters = Pattern.compile("p_[0-9a-zA-Z]+_(?:[0-9a-zA-Z]+_)?"); + + for (Path file : (Iterable) Files.walk(fs.getPath("/"))::iterator) { + if (!file.toString().endsWith(".class")) continue; + + completer.add(() -> { + byte[] bytes = Files.readAllBytes(file); + ClassReader reader = new ClassReader(bytes); + ClassWriter writer = new ClassWriter(0); + + reader.accept(new ClassVisitor(Opcodes.ASM9, writer) { + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { + return new MethodVisitor(Opcodes.ASM9, super.visitMethod(access, name, descriptor, signature, exceptions)) { + @Override + public void visitParameter(String name, int access) { + if (vignetteParameters.matcher(name).matches()) { + super.visitParameter(null, access); + } else { + super.visitParameter(name, access); + } + } + + @Override + public void visitLocalVariable(String name, String descriptor, String signature, Label start, Label end, int index) { + if (!vignetteParameters.matcher(name).matches()) { + super.visitLocalVariable(name, descriptor, signature, start, end, index); + } + } + }; + } + }, 0); + + byte[] out = writer.toByteArray(); + + if (!Arrays.equals(bytes, out)) { + Files.delete(file); + Files.write(file, out); + } + }); + } + + completer.complete(); + } + + getProject().getLogger().info(":deleting parameter names for " + jarFile.getAbsolutePath() + " in " + stopwatch); + } + + protected void fixParameterAnnotation(File jarFile) throws Exception { + getProject().getLogger().info(":fixing parameter annotations for " + jarFile.getAbsolutePath()); + Stopwatch stopwatch = Stopwatch.createStarted(); + + try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + jarFile.toURI()), ImmutableMap.of("create", false))) { + ThreadingUtils.TaskCompleter completer = ThreadingUtils.taskCompleter(); + + for (Path file : (Iterable) Files.walk(fs.getPath("/"))::iterator) { + if (!file.toString().endsWith(".class")) continue; + + completer.add(() -> { + byte[] bytes = Files.readAllBytes(file); + ClassReader reader = new ClassReader(bytes); + ClassNode node = new ClassNode(); + ClassVisitor visitor = new ParameterAnnotationFixer(node, null); + reader.accept(visitor, 0); + + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); + node.accept(writer); + byte[] out = writer.toByteArray(); + + if (!Arrays.equals(bytes, out)) { + Files.delete(file); + Files.write(file, out); + } + }); + } + + completer.complete(); + } + + getProject().getLogger().info(":fixing parameter annotations for " + jarFile.getAbsolutePath() + " in " + stopwatch); + } + + + protected void accessTransformForge(Logger logger) throws Exception { + MinecraftProviderImpl minecraftProvider = getExtension().getMinecraftProvider(); + List toDelete = new ArrayList<>(); + String atDependency = Constants.Dependencies.ACCESS_TRANSFORMERS + (minecraftProvider.isNewerThan21w39a() ? Constants.Dependencies.Versions.ACCESS_TRANSFORMERS_NEW : Constants.Dependencies.Versions.ACCESS_TRANSFORMERS); + FileCollection classpath = DependencyDownloader.download(getProject(), atDependency); + Stopwatch stopwatch = Stopwatch.createStarted(); + + logger.lifecycle(":access transforming minecraft"); + + File input = minecraftMergedPatchedSrgJar; + File target = minecraftMergedPatchedSrgAtJar; + Files.deleteIfExists(target.toPath()); + + List args = new ArrayList<>(); + args.add("--inJar"); + args.add(input.getAbsolutePath()); + args.add("--outJar"); + args.add(target.getAbsolutePath()); + + for (File jar : ImmutableList.of(getForgeJar(), getForgeUserdevJar(), minecraftMergedPatchedSrgJar)) { + byte[] atBytes = ZipUtils.unpackNullable(jar.toPath(), Constants.Forge.ACCESS_TRANSFORMER_PATH); + + if (atBytes != null) { + File tmpFile = File.createTempFile("at-conf", ".cfg"); + toDelete.add(tmpFile); + Files.write(tmpFile.toPath(), atBytes, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + args.add("--atFile"); + args.add(tmpFile.getAbsolutePath()); + } + } + + if (usesProjectCache()) { + for (File projectAt : projectAts) { + args.add("--atFile"); + args.add(projectAt.getAbsolutePath()); + } + } + + getProject().javaexec(spec -> { + spec.getMainClass().set("net.minecraftforge.accesstransformer.TransformerProcessor"); + spec.setArgs(args); + spec.setClasspath(classpath); + + // if running with INFO or DEBUG logging + if (getProject().getGradle().getStartParameter().getShowStacktrace() != ShowStacktrace.INTERNAL_EXCEPTIONS + || getProject().getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) < 0) { + spec.setStandardOutput(System.out); + spec.setErrorOutput(System.err); + } else { + spec.setStandardOutput(NullOutputStream.NULL_OUTPUT_STREAM); + spec.setErrorOutput(NullOutputStream.NULL_OUTPUT_STREAM); + } + }).rethrowFailure().assertNormalExitValue(); + + for (File file : toDelete) { + file.delete(); + } + + logger.lifecycle(":access transformed minecraft in " + stopwatch.stop()); + } + + + protected File getForgeJar() { + return getExtension().getForgeUniversalProvider().getForge(); + } + + protected File getForgeUserdevJar() { + return getExtension().getForgeUserdevProvider().getUserdevJar(); + } + + + + protected TinyRemapper buildRemapper(Path input, String from, String to) throws IOException { + Path[] libraries = TinyRemapperHelper.getMinecraftDependencies(getProject()); + MemoryMappingTree mappingsWithSrg = getExtension().getMappingsProvider().getMappingsWithSrg(); + + TinyRemapper remapper = TinyRemapper.newRemapper() + .logger(getProject().getLogger()::lifecycle) + .logUnknownInvokeDynamic(false) + .withMappings(TinyRemapperHelper.create(mappingsWithSrg, from, to, true)) + .withMappings(InnerClassRemapper.of(InnerClassRemapper.readClassNames(input), mappingsWithSrg, from, to)) + .renameInvalidLocals(true) + .rebuildSourceFilenames(true) + .fixPackageAccess(true) + .build(); + + if (getProject().getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) < 0) { + MappingsProviderVerbose.saveFile(remapper); + } + + remapper.readClassPath(libraries); + remapper.prepareClasses(); + return remapper; + } + + protected void remapPatchedJar(Logger logger) throws Exception { + getProject().getLogger().lifecycle(":remapping minecraft (TinyRemapper, srg -> official)"); + Path mcInput = minecraftMergedPatchedSrgAtJar.toPath(); + Path mcOutput = minecraftMergedPatchedAtJar.toPath(); + Path forgeJar = getForgeJar().toPath(); + Path forgeUserdevJar = getForgeUserdevJar().toPath(); + Path forgeOutput = null; + Files.deleteIfExists(mcOutput); + boolean splitJars = forgeMergedJar != null; + + if (splitJars) { + forgeOutput = forgeMergedJar.toPath(); + Files.deleteIfExists(forgeOutput); + } + + TinyRemapper remapper = buildRemapper(mcInput, "srg", "official"); + + try ( + OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(mcOutput).build(); + Closeable outputConsumerForge = !splitJars ? () -> { + } : new OutputConsumerPath.Builder(forgeOutput).build()) { + outputConsumer.addNonClassFiles(mcInput); + + InputTag mcTag = remapper.createInputTag(); + InputTag forgeTag = remapper.createInputTag(); + List> futures = new ArrayList<>(); + futures.add(remapper.readInputsAsync(mcTag, mcInput)); + futures.add(remapper.readInputsAsync(forgeTag, forgeJar, forgeUserdevJar)); + CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); + remapper.apply(outputConsumer, mcTag); + remapper.apply(splitJars ? (OutputConsumerPath) outputConsumerForge : outputConsumer, forgeTag); + } finally { + remapper.finish(); + } + + copyNonClassFiles(forgeJar.toFile(), splitJars ? forgeMergedJar : minecraftMergedPatchedAtJar); + copyUserdevFiles(forgeUserdevJar.toFile(), splitJars ? forgeMergedJar : minecraftMergedPatchedAtJar); + applyLoomPatchVersion(mcOutput); + } + + protected void fillClientExtraJar() throws IOException { + Files.deleteIfExists(minecraftClientExtra.toPath()); + FileSystemUtil.getJarFileSystem(minecraftClientExtra, true).close(); + + copyNonClassFiles(getExtension().getMinecraftProvider().minecraftClientJar, minecraftClientExtra); + } -public class MinecraftPatchedProvider extends DependencyProvider { - private static final String LOOM_PATCH_VERSION_KEY = "Loom-Patch-Version"; - private static final String CURRENT_LOOM_PATCH_VERSION = "5"; - private static final String NAME_MAPPING_SERVICE_PATH = "/inject/META-INF/services/cpw.mods.modlauncher.api.INameMappingService"; - - // Step 1: Remap Minecraft to SRG (global) - private File minecraftClientSrgJar; - private File minecraftServerSrgJar; - // Step 2: Binary Patch (global) - private File minecraftClientPatchedSrgJar; - private File minecraftServerPatchedSrgJar; - // Step 3: Merge (global) - private File minecraftMergedPatchedSrgJar; - // Step 4: Access Transform (global or project) - private File minecraftMergedPatchedSrgAtJar; - // Step 5: Remap Patched AT & Forge to Official (global or project) - private File minecraftMergedPatchedJar; - private File forgeMergedJar; - private File minecraftClientExtra; - - private File projectAtHash; - private Set projectAts = new HashSet<>(); - private boolean atDirty = false; - private boolean filesDirty = false; - private Path mcpConfigMappings; - private Path[] mergedMojangTsrg2Files; - - public MinecraftPatchedProvider(Project project) { - super(project); - } - - public void initFiles() throws IOException { - filesDirty = false; - projectAtHash = new File(getDirectories().getProjectPersistentCache(), "at.sha256"); - ConfigurableFileCollection accessTransformers = getExtension().getForge().getAccessTransformers(); - accessTransformers.finalizeValue(); - projectAts = accessTransformers.getFiles(); - - if (projectAts.isEmpty()) { - SourceSet main = getProject().getConvention().findPlugin(JavaPluginConvention.class).getSourceSets().getByName("main"); - - for (File srcDir : main.getResources().getSrcDirs()) { - File projectAt = new File(srcDir, Constants.Forge.ACCESS_TRANSFORMER_PATH); - - if (projectAt.exists()) { - this.projectAts.add(projectAt); - break; - } - } - } - - if (isRefreshDeps() || !projectAtHash.exists()) { - writeAtHash(); - atDirty = !projectAts.isEmpty(); - } else { - byte[] expected = com.google.common.io.Files.asByteSource(projectAtHash).read(); - byte[] current = getProjectAtsHash(); - boolean mismatched = !Arrays.equals(current, expected); - - if (mismatched) { - writeAtHash(); - } - - atDirty = mismatched; - } - - MinecraftProviderImpl minecraftProvider = getExtension().getMinecraftProvider(); - PatchProvider patchProvider = getExtension().getPatchProvider(); - String minecraftVersion = minecraftProvider.minecraftVersion(); - String patchId = "forge-" + getExtension().getForgeProvider().getVersion().getCombined() + "-"; - - if (getExtension().isForgeAndOfficial()) { - minecraftProvider.setJarPrefix(patchId); - } - - File globalCache = getExtension().getForgeProvider().getGlobalCache(); - File projectDir = usesProjectCache() ? getExtension().getForgeProvider().getProjectCache() : globalCache; - projectDir.mkdirs(); - - minecraftClientSrgJar = new File(globalCache, "minecraft-client-srg.jar"); - minecraftServerSrgJar = new File(globalCache, "minecraft-server-srg.jar"); - minecraftClientPatchedSrgJar = new File(globalCache, "client-srg-patched.jar"); - minecraftServerPatchedSrgJar = new File(globalCache, "server-srg-patched.jar"); - minecraftMergedPatchedSrgJar = new File(globalCache, "merged-srg-patched.jar"); - forgeMergedJar = getExtension().isForgeAndOfficial() ? null : new File(globalCache, "forge-official.jar"); - minecraftMergedPatchedSrgAtJar = new File(projectDir, "merged-srg-at-patched.jar"); - minecraftMergedPatchedJar = new File(projectDir, "merged-patched.jar"); - minecraftClientExtra = new File(globalCache, "forge-client-extra.jar"); - - if (isRefreshDeps() || Stream.of(getGlobalCaches()).anyMatch(((Predicate) File::exists).negate()) - || !isPatchedJarUpToDate(minecraftMergedPatchedJar)) { - cleanAllCache(); - } else if (atDirty || Stream.of(getProjectCache()).anyMatch(((Predicate) File::exists).negate())) { - cleanProjectCache(); - } - } - - private byte[] getProjectAtsHash() throws IOException { - if (projectAts.isEmpty()) return ByteSource.empty().hash(Hashing.sha256()).asBytes(); - List currentBytes = new ArrayList<>(); - - for (File projectAt : projectAts) { - currentBytes.add(com.google.common.io.Files.asByteSource(projectAt)); - } - - return ByteSource.concat(currentBytes).hash(Hashing.sha256()).asBytes(); - } - - public void cleanAllCache() { - for (File file : getGlobalCaches()) { - file.delete(); - } - - cleanProjectCache(); - } - - private File[] getGlobalCaches() { - File[] files = { - minecraftClientSrgJar, - minecraftServerSrgJar, - minecraftClientPatchedSrgJar, - minecraftServerPatchedSrgJar, - minecraftMergedPatchedSrgJar, - minecraftClientExtra, - }; - - if (forgeMergedJar != null) { - Arrays.copyOf(files, files.length + 1); - files[files.length - 1] = forgeMergedJar; - } - - return files; - } - - public void cleanProjectCache() { - for (File file : getProjectCache()) { - file.delete(); - } - } - - private File[] getProjectCache() { - return new File[] { - minecraftMergedPatchedSrgAtJar, - minecraftMergedPatchedJar - }; - } - - private boolean dirty; - - @Override - public void provide(DependencyInfo dependency, Consumer postPopulationScheduler) throws Exception { - initFiles(); - - if (atDirty) { - getProject().getLogger().lifecycle(":found dirty access transformers"); - } - - this.dirty = false; - - if (!minecraftClientSrgJar.exists() || !minecraftServerSrgJar.exists()) { - this.dirty = true; - // Remap official jars to MCPConfig remapped srg jars - createSrgJars(getProject().getLogger()); - } - - if (!minecraftClientPatchedSrgJar.exists() || !minecraftServerPatchedSrgJar.exists()) { - this.dirty = true; - patchJars(getProject().getLogger()); - } - } - - public void finishProvide() throws Exception { - if (dirty || !minecraftMergedPatchedSrgJar.exists()) { - mergeJars(getProject().getLogger()); - } - - if (atDirty || !minecraftMergedPatchedSrgAtJar.exists()) { - this.dirty = true; - accessTransformForge(getProject().getLogger()); - } - - if (forgeMergedJar != null && !forgeMergedJar.exists()) { - this.dirty = true; - } - - if (dirty) { - remapPatchedJar(getProject().getLogger()); - - if (getExtension().isForgeAndOfficial()) { - fillClientExtraJar(); - } - } - - this.filesDirty = dirty; - this.dirty = false; - - if (getExtension().isForgeAndOfficial()) { - addDependency(minecraftClientExtra, Constants.Configurations.FORGE_EXTRA); - } - } - - private void fillClientExtraJar() throws IOException { - Files.deleteIfExists(minecraftClientExtra.toPath()); - FileSystemUtil.getJarFileSystem(minecraftClientExtra, true).close(); - - copyNonClassFiles(getExtension().getMinecraftProvider().minecraftClientJar, minecraftClientExtra); - } - - private TinyRemapper buildRemapper(Path input) throws IOException { - Path[] libraries = TinyRemapperHelper.getMinecraftDependencies(getProject()); - MemoryMappingTree mappingsWithSrg = getExtension().getMappingsProvider().getMappingsWithSrg(); - - TinyRemapper remapper = TinyRemapper.newRemapper() - .logger(getProject().getLogger()::lifecycle) - .logUnknownInvokeDynamic(false) - .withMappings(TinyRemapperHelper.create(mappingsWithSrg, "srg", "official", true)) - .withMappings(InnerClassRemapper.of(InnerClassRemapper.readClassNames(input), mappingsWithSrg, "srg", "official")) - .renameInvalidLocals(true) - .rebuildSourceFilenames(true) - .fixPackageAccess(true) - .build(); - - if (getProject().getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) < 0) { - MappingsProviderVerbose.saveFile(remapper); - } - - remapper.readClassPath(libraries); - remapper.prepareClasses(); - return remapper; - } - - private void writeAtHash() throws IOException { - try (FileOutputStream out = new FileOutputStream(projectAtHash)) { - out.write(getProjectAtsHash()); - } - } - - private void createSrgJars(Logger logger) throws Exception { - MinecraftProviderImpl minecraftProvider = getExtension().getMinecraftProvider(); - produceSrgJar(getExtension().isForgeAndOfficial(), minecraftProvider.minecraftClientJar.toPath(), minecraftProvider.getMinecraftServerJar().toPath()); - } - - private void produceSrgJar(boolean official, Path clientJar, Path serverJar) throws Exception { - Path tmpSrg = getToSrgMappings(); - Set mcLibs = getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_DEPENDENCIES).resolve(); - - boolean isSrg = getExtension().getSrgProvider().isLegacy(); - - Path[] clientJarOut = new Path[] { null }; - Path[] serverJarOut = new Path[] { null }; - - ThreadingUtils.run(() -> { - clientJarOut[0] = SpecialSourceExecutor.produceSrgJar(getExtension().getMcpConfigProvider().getRemapAction(), getProject(), "client", mcLibs, clientJar, tmpSrg, isSrg, true); - }, () -> { - serverJarOut[0] = SpecialSourceExecutor.produceSrgJar(getExtension().getMcpConfigProvider().getRemapAction(), getProject(), "server", mcLibs, serverJar, tmpSrg, isSrg, true); - }); - -// if (isSrg) { -// new MergeJars().processJar(clientJarOut[0].toFile(), serverJarOut[0].toFile(), minecraftClientSrgJar); -// } else { - Files.copy(clientJarOut[0], minecraftClientSrgJar.toPath()); - Files.copy(serverJarOut[0], minecraftServerSrgJar.toPath()); -// } - } - - private Path getToSrgMappings() throws IOException { - if (getExtension().getSrgProvider().isTsrgV2()) { - return getExtension().getSrgProvider().getMergedMojangRaw(); - } else { - return getExtension().getMcpConfigProvider().getMappings(); - } - } - - private void fixParameterAnnotation(File jarFile) throws Exception { - getProject().getLogger().info(":fixing parameter annotations for " + jarFile.getAbsolutePath()); - Stopwatch stopwatch = Stopwatch.createStarted(); - - try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + jarFile.toURI()), ImmutableMap.of("create", false))) { - ThreadingUtils.TaskCompleter completer = ThreadingUtils.taskCompleter(); - - for (Path file : (Iterable) Files.walk(fs.getPath("/"))::iterator) { - if (!file.toString().endsWith(".class")) continue; - - completer.add(() -> { - byte[] bytes = Files.readAllBytes(file); - ClassReader reader = new ClassReader(bytes); - ClassNode node = new ClassNode(); - ClassVisitor visitor = new ParameterAnnotationFixer(node, null); - reader.accept(visitor, 0); - - ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); - node.accept(writer); - byte[] out = writer.toByteArray(); - - if (!Arrays.equals(bytes, out)) { - Files.delete(file); - Files.write(file, out); - } - }); - } - - completer.complete(); - } - - getProject().getLogger().info(":fixing parameter annotations for " + jarFile.getAbsolutePath() + " in " + stopwatch); - } - - private void deleteParameterNames(File jarFile) throws Exception { - getProject().getLogger().info(":deleting parameter names for " + jarFile.getAbsolutePath()); - Stopwatch stopwatch = Stopwatch.createStarted(); - - try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + jarFile.toURI()), ImmutableMap.of("create", false))) { - ThreadingUtils.TaskCompleter completer = ThreadingUtils.taskCompleter(); - Pattern vignetteParameters = Pattern.compile("p_[0-9a-zA-Z]+_(?:[0-9a-zA-Z]+_)?"); - - for (Path file : (Iterable) Files.walk(fs.getPath("/"))::iterator) { - if (!file.toString().endsWith(".class")) continue; - - completer.add(() -> { - byte[] bytes = Files.readAllBytes(file); - ClassReader reader = new ClassReader(bytes); - ClassWriter writer = new ClassWriter(0); - - reader.accept(new ClassVisitor(Opcodes.ASM9, writer) { - @Override - public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { - return new MethodVisitor(Opcodes.ASM9, super.visitMethod(access, name, descriptor, signature, exceptions)) { - @Override - public void visitParameter(String name, int access) { - if (vignetteParameters.matcher(name).matches()) { - super.visitParameter(null, access); - } else { - super.visitParameter(name, access); - } - } - - @Override - public void visitLocalVariable(String name, String descriptor, String signature, Label start, Label end, int index) { - if (!vignetteParameters.matcher(name).matches()) { - super.visitLocalVariable(name, descriptor, signature, start, end, index); - } - } - }; - } - }, 0); - - byte[] out = writer.toByteArray(); - - if (!Arrays.equals(bytes, out)) { - Files.delete(file); - Files.write(file, out); - } - }); - } - - completer.complete(); - } - - getProject().getLogger().info(":deleting parameter names for " + jarFile.getAbsolutePath() + " in " + stopwatch); - } - - private File getForgeJar() { - return getExtension().getForgeUniversalProvider().getForge(); - } - - private File getForgeUserdevJar() { - return getExtension().getForgeUserdevProvider().getUserdevJar(); - } - - private boolean isPatchedJarUpToDate(File jar) throws IOException { - if (!jar.exists()) return false; - - byte[] manifestBytes = ZipUtils.unpackNullable(jar.toPath(), "META-INF/MANIFEST.MF"); - - if (manifestBytes == null) { - return false; - } - - Manifest manifest = new Manifest(new ByteArrayInputStream(manifestBytes)); - Attributes attributes = manifest.getMainAttributes(); - String value = attributes.getValue(LOOM_PATCH_VERSION_KEY); - - if (Objects.equals(value, CURRENT_LOOM_PATCH_VERSION)) { - return true; - } else { - getProject().getLogger().lifecycle(":forge patched jars not up to date. current version: " + value); - return false; - } - } - - private void accessTransformForge(Logger logger) throws Exception { - MinecraftProviderImpl minecraftProvider = getExtension().getMinecraftProvider(); - List toDelete = new ArrayList<>(); - String atDependency = Constants.Dependencies.ACCESS_TRANSFORMERS + (minecraftProvider.isNewerThan21w39a() ? Constants.Dependencies.Versions.ACCESS_TRANSFORMERS_NEW : Constants.Dependencies.Versions.ACCESS_TRANSFORMERS); - FileCollection classpath = DependencyDownloader.download(getProject(), atDependency); - Stopwatch stopwatch = Stopwatch.createStarted(); - - logger.lifecycle(":access transforming minecraft"); - - File input = minecraftMergedPatchedSrgJar; - File target = minecraftMergedPatchedSrgAtJar; - Files.deleteIfExists(target.toPath()); - - List args = new ArrayList<>(); - args.add("--inJar"); - args.add(input.getAbsolutePath()); - args.add("--outJar"); - args.add(target.getAbsolutePath()); - - for (File jar : ImmutableList.of(getForgeJar(), getForgeUserdevJar(), minecraftMergedPatchedSrgJar)) { - byte[] atBytes = ZipUtils.unpackNullable(jar.toPath(), Constants.Forge.ACCESS_TRANSFORMER_PATH); - - if (atBytes != null) { - File tmpFile = File.createTempFile("at-conf", ".cfg"); - toDelete.add(tmpFile); - Files.write(tmpFile.toPath(), atBytes, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); - args.add("--atFile"); - args.add(tmpFile.getAbsolutePath()); - } - } - - if (usesProjectCache()) { - for (File projectAt : projectAts) { - args.add("--atFile"); - args.add(projectAt.getAbsolutePath()); - } - } - - getProject().javaexec(spec -> { - spec.getMainClass().set("net.minecraftforge.accesstransformer.TransformerProcessor"); - spec.setArgs(args); - spec.setClasspath(classpath); - - // if running with INFO or DEBUG logging - if (getProject().getGradle().getStartParameter().getShowStacktrace() != ShowStacktrace.INTERNAL_EXCEPTIONS - || getProject().getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) < 0) { - spec.setStandardOutput(System.out); - spec.setErrorOutput(System.err); - } else { - spec.setStandardOutput(NullOutputStream.NULL_OUTPUT_STREAM); - spec.setErrorOutput(NullOutputStream.NULL_OUTPUT_STREAM); - } - }).rethrowFailure().assertNormalExitValue(); - - for (File file : toDelete) { - file.delete(); - } - - logger.lifecycle(":access transformed minecraft in " + stopwatch.stop()); - } - - public enum Environment { - CLIENT(provider -> provider.minecraftClientSrgJar, - provider -> provider.minecraftClientPatchedSrgJar - ), - SERVER(provider -> provider.minecraftServerSrgJar, - provider -> provider.minecraftServerPatchedSrgJar - ); - - final Function srgJar; - final Function patchedSrgJar; - - Environment(Function srgJar, - Function patchedSrgJar) { - this.srgJar = srgJar; - this.patchedSrgJar = patchedSrgJar; - } - - public String side() { - return name().toLowerCase(Locale.ROOT); - } - } - - private void remapPatchedJar(Logger logger) throws Exception { - getProject().getLogger().lifecycle(":remapping minecraft (TinyRemapper, srg -> official)"); - Path mcInput = minecraftMergedPatchedSrgAtJar.toPath(); - Path mcOutput = minecraftMergedPatchedJar.toPath(); - Path forgeJar = getForgeJar().toPath(); - Path forgeUserdevJar = getForgeUserdevJar().toPath(); - Path forgeOutput = null; - Files.deleteIfExists(mcOutput); - boolean splitJars = forgeMergedJar != null; - - if (splitJars) { - forgeOutput = forgeMergedJar.toPath(); - Files.deleteIfExists(forgeOutput); - } - - TinyRemapper remapper = buildRemapper(mcInput); - - try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(mcOutput).build(); - Closeable outputConsumerForge = !splitJars ? () -> { - } : new OutputConsumerPath.Builder(forgeOutput).build()) { - outputConsumer.addNonClassFiles(mcInput); - - InputTag mcTag = remapper.createInputTag(); - InputTag forgeTag = remapper.createInputTag(); - List> futures = new ArrayList<>(); - futures.add(remapper.readInputsAsync(mcTag, mcInput)); - futures.add(remapper.readInputsAsync(forgeTag, forgeJar, forgeUserdevJar)); - CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); - remapper.apply(outputConsumer, mcTag); - remapper.apply(splitJars ? (OutputConsumerPath) outputConsumerForge : outputConsumer, forgeTag); - } finally { - remapper.finish(); - } - - copyNonClassFiles(forgeJar.toFile(), splitJars ? forgeMergedJar : minecraftMergedPatchedJar); - copyUserdevFiles(forgeUserdevJar.toFile(), splitJars ? forgeMergedJar : minecraftMergedPatchedJar); - applyLoomPatchVersion(mcOutput); - } - - private void patchJars(Logger logger) throws IOException { - Stopwatch stopwatch = Stopwatch.createStarted(); - logger.lifecycle(":patching jars"); - - PatchProvider patchProvider = getExtension().getPatchProvider(); - patchJars(minecraftClientSrgJar, minecraftClientPatchedSrgJar, patchProvider.clientPatches, "client"); - patchJars(minecraftServerSrgJar, minecraftServerPatchedSrgJar, patchProvider.serverPatches, "server"); - - ThreadingUtils.run(Environment.values(), environment -> { - copyMissingClasses(environment.srgJar.apply(this), environment.patchedSrgJar.apply(this)); - deleteParameterNames(environment.patchedSrgJar.apply(this)); - - if (getExtension().isForgeAndNotOfficial()) { - fixParameterAnnotation(environment.patchedSrgJar.apply(this)); - } - }); - - logger.lifecycle(":patched jars in " + stopwatch.stop()); - } - - private void patchJars(File clean, File output, Path patches, String side) throws IOException { - PrintStream previous = System.out; - - try { - System.setOut(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM)); - } catch (SecurityException ignored) { - // Failed to replace logger filter, just ignore - } - - if (getExtension().getMcpConfigProvider().isFG2()) { - - new TaskApplyBinPatches().doTask(getProject(), clean.getAbsoluteFile(), patches.toFile().getAbsoluteFile(), output.getAbsoluteFile(), side); - - } else { - ConsoleTool.main(new String[] { - "--clean", clean.getAbsolutePath(), - "--output", output.getAbsolutePath(), - "--apply", patches.toAbsolutePath().toString() - }); - } - - try { - System.setOut(previous); - } catch (SecurityException ignored) { - // Failed to replace logger filter, just ignore - } - } - - private void mergeJars(Logger logger) throws Exception { - // FIXME: Hack here: There are no server-only classes so we can just copy the client JAR. - // This will change if upstream Loom adds the possibility for separate projects/source sets per environment. - - - if (getExtension().getMcpConfigProvider().isFG2()) { - logger.lifecycle(":merging jars"); - Path tmpSrg = getToSrgMappings(); - Set mcLibs = getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_DEPENDENCIES).resolve(); - Files.copy(SpecialSourceExecutor.produceSrgJar(getExtension().getMcpConfigProvider().getRemapAction(), getProject(), "merged", mcLibs, minecraftClientPatchedSrgJar.toPath(), tmpSrg, getExtension().getSrgProvider().isLegacy(), false), minecraftMergedPatchedSrgJar.toPath()); - } else { - Files.copy(minecraftClientPatchedSrgJar.toPath(), minecraftMergedPatchedSrgJar.toPath()); - } - logger.lifecycle(":copying resources"); - - // Copy resources - if (getExtension().isForgeAndNotOfficial()) { - // Copy resources - MinecraftProviderImpl minecraftProvider = getExtension().getMinecraftProvider(); - copyNonClassFiles(minecraftProvider.minecraftClientJar, minecraftMergedPatchedSrgJar); - copyNonClassFiles(minecraftProvider.getMinecraftServerJar(), minecraftMergedPatchedSrgJar); - } - } - - private void walkFileSystems(File source, File target, Predicate filter, Function> toWalk, FsPathConsumer action) - throws IOException { - try (FileSystemUtil.Delegate sourceFs = FileSystemUtil.getJarFileSystem(source, false); - FileSystemUtil.Delegate targetFs = FileSystemUtil.getJarFileSystem(target, false)) { - for (Path sourceDir : toWalk.apply(sourceFs.get())) { - Path dir = sourceDir.toAbsolutePath(); - if (!Files.exists(dir)) continue; - Files.walk(dir) - .filter(Files::isRegularFile) - .filter(filter) - .forEach(it -> { - boolean root = dir.getParent() == null; - - try { - Path relativeSource = root ? it : dir.relativize(it); - Path targetPath = targetFs.get().getPath(relativeSource.toString()); - action.accept(sourceFs.get(), targetFs.get(), it, targetPath); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - }); - } - } - } - - private void walkFileSystems(File source, File target, Predicate filter, FsPathConsumer action) throws IOException { - walkFileSystems(source, target, filter, FileSystem::getRootDirectories, action); - } - - private void copyAll(File source, File target) throws IOException { - walkFileSystems(source, target, it -> true, this::copyReplacing); - } - - private void copyMissingClasses(File source, File target) throws IOException { - walkFileSystems(source, target, it -> it.toString().endsWith(".class"), (sourceFs, targetFs, sourcePath, targetPath) -> { - if (Files.exists(targetPath)) return; - Path parent = targetPath.getParent(); - - if (parent != null) { - Files.createDirectories(parent); - } - - Files.copy(sourcePath, targetPath); - }); - } - - private void copyNonClassFiles(File source, File target) throws IOException { - Predicate filter = getExtension().isForgeAndOfficial() ? file -> { - String s = file.toString(); - return !s.endsWith(".class"); - } : file -> { - String s = file.toString(); - return !s.endsWith(".class") || (s.startsWith("META-INF") && !s.startsWith("META-INF/services")); - }; - - walkFileSystems(source, target, filter, this::copyReplacing); - } - - private void copyReplacing(FileSystem sourceFs, FileSystem targetFs, Path sourcePath, Path targetPath) throws IOException { - Path parent = targetPath.getParent(); - - if (parent != null) { - Files.createDirectories(parent); - } - - Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING); - } - - private void copyUserdevFiles(File source, File target) throws IOException { - // Removes the Forge name mapping service definition so that our own is used. - // If there are multiple name mapping services with the same "understanding" pair - // (source -> target namespace pair), modlauncher throws a fit and will crash. - // To use our YarnNamingService instead of MCPNamingService, we have to remove this file. - Predicate filter = file -> !file.toString().endsWith(".class") && !file.toString().equals(NAME_MAPPING_SERVICE_PATH); - - walkFileSystems(source, target, filter, fs -> Collections.singleton(fs.getPath("inject")), (sourceFs, targetFs, sourcePath, targetPath) -> { - Path parent = targetPath.getParent(); - - if (parent != null) { - Files.createDirectories(parent); - } - - Files.copy(sourcePath, targetPath); - }); - } - - public void applyLoomPatchVersion(Path target) throws IOException { - try (FileSystemUtil.Delegate delegate = FileSystemUtil.getJarFileSystem(target, false)) { - Path manifestPath = delegate.get().getPath("META-INF/MANIFEST.MF"); - - Preconditions.checkArgument(Files.exists(manifestPath), "META-INF/MANIFEST.MF does not exist in patched srg jar!"); - Manifest manifest = new Manifest(); - - if (Files.exists(manifestPath)) { - try (InputStream stream = Files.newInputStream(manifestPath)) { - manifest.read(stream); - manifest.getMainAttributes().putValue(LOOM_PATCH_VERSION_KEY, CURRENT_LOOM_PATCH_VERSION); - } - } - - try (OutputStream stream = Files.newOutputStream(manifestPath, StandardOpenOption.CREATE)) { - manifest.write(stream); - } - } - } - - public File getMergedJar() { - return minecraftMergedPatchedJar; - } - - public File getForgeMergedJar() { - return forgeMergedJar; - } - - public boolean usesProjectCache() { - return !projectAts.isEmpty(); - } - - public boolean isAtDirty() { - return atDirty || filesDirty; - } - - @Override - public String getTargetConfig() { - return Constants.Configurations.MINECRAFT; - } } diff --git a/src/main/java/net/minecraftforge/gradle/tasks/TaskApplyBinPatches.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/FG2TaskApplyBinPatches.java similarity index 90% rename from src/main/java/net/minecraftforge/gradle/tasks/TaskApplyBinPatches.java rename to src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/FG2TaskApplyBinPatches.java index 536df6fa2..181208b5c 100644 --- a/src/main/java/net/minecraftforge/gradle/tasks/TaskApplyBinPatches.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/FG2TaskApplyBinPatches.java @@ -17,7 +17,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * USA */ -package net.minecraftforge.gradle.tasks; +package net.fabricmc.loom.configuration.providers.forge.fg2; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; @@ -39,10 +39,8 @@ import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; -import net.minecraftforge.java.util.jar.Pack200; +import net.fabricmc.shade.java.util.jar.Pack200; import org.gradle.api.Project; -import org.gradle.api.file.FileVisitDetails; -import org.gradle.api.file.FileVisitor; import com.google.common.base.Joiner; import com.google.common.base.Throwables; import com.google.common.collect.Maps; @@ -53,16 +51,16 @@ import lzma.sdk.lzma.Decoder; import lzma.streams.LzmaInputStream; -public class TaskApplyBinPatches { +public class FG2TaskApplyBinPatches { - private HashMap patchlist = Maps.newHashMap(); - private GDiffPatcher patcher = new GDiffPatcher(); + private static final HashMap patchlist = Maps.newHashMap(); + private static final GDiffPatcher patcher = new GDiffPatcher(); - private Project project; + private static Project project; - public void doTask(Project project, File inJar, File patches, File outjar, String side) throws IOException + public static void doTask(Project project, File inJar, File patches, File outjar, String side) throws IOException { - this.project = project; + FG2TaskApplyBinPatches.project = project; setup(patches, side); if (outjar.exists()) @@ -138,14 +136,14 @@ public void doTask(Project project, File inJar, File patches, File outjar, Strin } } - private int adlerHash(byte[] input) + private static int adlerHash(byte[] input) { Adler32 hasher = new Adler32(); hasher.update(input); return (int) hasher.getValue(); } - public void setup(File patches, String side) + public static void setup(File patches, String side) { Pattern matcher = Pattern.compile(String.format("binpatch/%s/.*.binpatch", side)); @@ -192,7 +190,7 @@ public void setup(File patches, String side) log("Patch list :\n\t%s", Joiner.on("\n\t").join(patchlist.entrySet())); } - private ClassPatch readPatch(JarEntry patchEntry, JarInputStream jis) throws IOException + private static ClassPatch readPatch(JarEntry patchEntry, JarInputStream jis) throws IOException { log("\t%s", patchEntry.getName()); ByteArrayDataInput input = ByteStreams.newDataInput(ByteStreams.toByteArray(jis)); @@ -213,9 +211,9 @@ private ClassPatch readPatch(JarEntry patchEntry, JarInputStream jis) throws IOE return new ClassPatch(name, sourceClassName, targetClassName, exists, inputChecksum, patchBytes); } - private void log(String format, Object... args) + private static void log(String format, Object... args) { - this.project.getLogger().info(String.format(format, args)); + project.getLogger().info(String.format(format, args)); } public static class ClassPatch diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/MinecraftPatchedProviderFG2.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/MinecraftPatchedProviderFG2.java new file mode 100644 index 000000000..6bae728c9 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/MinecraftPatchedProviderFG2.java @@ -0,0 +1,270 @@ +package net.fabricmc.loom.configuration.providers.forge.fg2; + +import com.google.common.base.Stopwatch; +import dev.architectury.tinyremapper.InputTag; +import dev.architectury.tinyremapper.OutputConsumerPath; +import dev.architectury.tinyremapper.TinyRemapper; +import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl; +import net.fabricmc.loom.configuration.providers.forge.MinecraftPatchedProvider; +import net.fabricmc.loom.configuration.providers.forge.PatchProvider; +import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.ThreadingUtils; +import net.fabricmc.loom.util.srg.SpecialSourceExecutor; +import org.apache.commons.io.output.NullOutputStream; +import org.gradle.api.Project; +import org.gradle.api.logging.Logger; + +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class MinecraftPatchedProviderFG2 extends MinecraftPatchedProvider { + + // step 0: strip minecraft jars + private File minecraftClientJar; + private File minecraftServerJar; + + // Step 1: Binary Patch (global) + private File minecraftClientPatchedJar; + private File minecraftServerPatchedJar; + + // Step 2: Merge (global) + private File minecraftMergedPatchedJar; + + // Step 3: Srg Transform (global) + // field in super + + // step 4: Access Transform (global or project) + // field in super + + // Step 5: Remap Patched AT & Forge to Official (global or project) + // fields in super + + + public MinecraftPatchedProviderFG2(Project project) { + super(project); + } + + @Override + public void initFiles() throws IOException { + super.initFiles(); + File globalCache = getExtension().getForgeProvider().getGlobalCache(); + File projectDir = usesProjectCache() ? getExtension().getForgeProvider().getProjectCache() : globalCache; + minecraftClientJar = new File(globalCache, "client-stripped.jar"); + minecraftServerJar = new File(globalCache, "server-stripped.jar"); + minecraftClientPatchedJar = new File(globalCache, "client-patched.jar"); + minecraftServerPatchedJar = new File(globalCache, "server-patched.jar"); + minecraftMergedPatchedJar = new File(globalCache, "merged-patched.jar"); + + } + + @Override + protected File[] getGlobalCaches() { + File[] files = { + minecraftClientPatchedJar, + minecraftServerPatchedJar, + minecraftMergedPatchedJar, + minecraftMergedPatchedSrgJar, + minecraftClientExtra, + }; + + if (forgeMergedJar != null) { + Arrays.copyOf(files, files.length + 1); + files[files.length - 1] = forgeMergedJar; + } + + return files; + } + + private boolean dirty; + @Override + protected void beginTransform() throws Exception { + if (atDirty) { + getProject().getLogger().lifecycle(":found dirty access transformers"); + } + + this.dirty = false; + + // Step 0: strip the client/server jars + if (!minecraftClientJar.exists() || !minecraftServerJar.exists()) { + this.dirty = true; + stripJars(getProject().getLogger()); + } + + // Step 1: Binary Patch (global) + if (!minecraftClientPatchedJar.exists() || !minecraftServerPatchedJar.exists()) { + this.dirty = true; + patchJars(getProject().getLogger()); + } + } + + @Override + public void endTransform() throws Exception { + + // Step 2: Merge (global) + if (dirty || !minecraftMergedPatchedJar.exists()) { + mergeJars(getProject().getLogger()); + } + + // Step 3: Srg Transform (global) + if (dirty || !minecraftMergedPatchedSrgJar.exists()) { + remapPatchedJarToSrg(getProject().getLogger()); + } + + // Step 4: Access Transform (global or project) + if (atDirty || !minecraftMergedPatchedSrgAtJar.exists()) { + this.dirty = true; + accessTransformForge(getProject().getLogger()); + } + + if (forgeMergedJar != null && !forgeMergedJar.exists()) { + this.dirty = true; + } + + // Step 5: Remap Patched AT & Forge to Official (global or project) + if (dirty) { + remapPatchedJar(getProject().getLogger()); + + if (getExtension().isForgeAndOfficial()) { + fillClientExtraJar(); + } + } + + this.filesDirty = dirty; + this.dirty = false; + + if (getExtension().isForgeAndOfficial()) { + addDependency(minecraftClientExtra, Constants.Configurations.FORGE_EXTRA); + } + + + } + + + public enum Environment { + CLIENT(provider -> provider.minecraftClientJar, + provider -> provider.minecraftClientPatchedJar + ), + SERVER(provider -> provider.minecraftServerJar, + provider -> provider.minecraftServerPatchedJar + ); + + final Function srgJar; + final Function patchedSrgJar; + + Environment(Function srgJar, + Function patchedSrgJar) { + this.srgJar = srgJar; + this.patchedSrgJar = patchedSrgJar; + } + + public String side() { + return name().toLowerCase(Locale.ROOT); + } + } + + private void stripJars(Logger logger) throws IOException { + logger.lifecycle(":stripping jars"); + Set filter = Files.readAllLines(getExtension().getMcpConfigProvider().getMappings(), StandardCharsets.UTF_8).stream() + .filter(s -> s.startsWith("CL:")) + .map(s -> s.split(" ")[1] + ".class") + .collect(Collectors.toSet()); + MinecraftProviderImpl minecraftProvider = getExtension().getMinecraftProvider(); + SpecialSourceExecutor.stripJar(getProject(), minecraftProvider.minecraftClientJar.toPath(), minecraftClientJar.toPath(), filter); + SpecialSourceExecutor.stripJar(getProject(), minecraftProvider.getMinecraftServerJar().toPath(), minecraftServerJar.toPath(), filter); + } + + private void patchJars(Logger logger) throws IOException { + Stopwatch stopwatch = Stopwatch.createStarted(); + logger.lifecycle(":patching jars"); + + PatchProvider patchProvider = getExtension().getPatchProvider(); + patchJars(minecraftClientJar, minecraftClientPatchedJar, patchProvider.clientPatches, "client"); + patchJars(minecraftServerJar, minecraftServerPatchedJar, patchProvider.serverPatches, "server"); + + + ThreadingUtils.run(Environment.values(), environment -> { + copyMissingClasses(environment.srgJar.apply(this), environment.patchedSrgJar.apply(this)); + deleteParameterNames(environment.patchedSrgJar.apply(this)); + + if (getExtension().isForgeAndNotOfficial()) { + fixParameterAnnotation(environment.patchedSrgJar.apply(this)); + } + }); + + logger.lifecycle(":patched jars in " + stopwatch.stop()); + } + + @Override + protected void patchJars(File clean, File output, Path patches, String side) throws IOException { + PrintStream previous = System.out; + + try { + System.setOut(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM)); + } catch (SecurityException ignored) { + // Failed to replace logger filter, just ignore + } + + FG2TaskApplyBinPatches.doTask(getProject(), clean.getAbsoluteFile(), patches.toFile().getAbsoluteFile(), output.getAbsoluteFile(), side); + + try { + System.setOut(previous); + } catch (SecurityException ignored) { + // Failed to replace logger filter, just ignore + } + } + + @Override + protected void mergeJars(Logger logger) throws Exception { + // FIXME: Hack here: There are no server-only classes so we can just copy the client JAR. + // This will change if upstream Loom adds the possibility for separate projects/source sets per environment. + + + logger.lifecycle(":merging jars"); + Set mcLibs = getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_DEPENDENCIES).resolve(); + Files.copy(minecraftClientPatchedJar.toPath(), minecraftMergedPatchedJar.toPath()); + + + logger.lifecycle(":copying resources"); + + // Copy resources + if (getExtension().isForgeAndNotOfficial()) { + // Copy resources + MinecraftProviderImpl minecraftProvider = getExtension().getMinecraftProvider(); + copyNonClassFiles(minecraftProvider.minecraftClientJar, minecraftMergedPatchedJar); + copyNonClassFiles(minecraftProvider.getMinecraftServerJar(), minecraftMergedPatchedJar); + } + } + + protected void remapPatchedJarToSrg(Logger logger) throws Exception { + getProject().getLogger().lifecycle(":remapping minecraft (TinyRemapper, official -> srg)"); + Path mcInput = minecraftMergedPatchedJar.toPath(); + Path mcOutput = minecraftMergedPatchedSrgJar.toPath(); + + TinyRemapper remapper = buildRemapper(mcInput, "official", "srg"); + + try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(mcOutput).build()) { + InputTag mcTag = remapper.createInputTag(); + remapper.readInputsAsync(mcTag, mcInput).join(); + remapper.apply(outputConsumer, mcTag); + } finally { + remapper.finish(); + } + + logger.lifecycle(":copying resources"); + MinecraftProviderImpl minecraftProvider = getExtension().getMinecraftProvider(); + copyNonClassFiles(minecraftProvider.minecraftClientJar, mcOutput.toFile()); + copyNonClassFiles(minecraftProvider.getMinecraftServerJar(), mcOutput.toFile()); + } + + +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg3/MinecraftPatchedProviderFG3.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg3/MinecraftPatchedProviderFG3.java new file mode 100644 index 000000000..702441704 --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg3/MinecraftPatchedProviderFG3.java @@ -0,0 +1,262 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2020-2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.configuration.providers.forge.fg3; + +import com.google.common.base.Stopwatch; +import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl; +import net.fabricmc.loom.configuration.providers.forge.MinecraftPatchedProvider; +import net.fabricmc.loom.configuration.providers.forge.PatchProvider; +import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.ThreadingUtils; +import net.fabricmc.loom.util.srg.SpecialSourceExecutor; +import net.minecraftforge.binarypatcher.ConsoleTool; +import org.apache.commons.io.output.NullOutputStream; +import org.gradle.api.Project; +import org.gradle.api.logging.Logger; + +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Locale; +import java.util.Set; +import java.util.function.Function; + +public class MinecraftPatchedProviderFG3 extends MinecraftPatchedProvider { + // Step 1: Remap Minecraft to SRG (global) + private File minecraftClientSrgJar; + private File minecraftServerSrgJar; + + // Step 2: Binary Patch (global) + private File minecraftClientPatchedSrgJar; + private File minecraftServerPatchedSrgJar; + + // Step 3: Merge (global) + // field in super + + // Step 4: Access Transform (global or project) + // field in super + + // Step 5: Remap Patched AT & Forge to Official (global or project) + // fields in super + + private Path[] mergedMojangTsrg2Files; + + public MinecraftPatchedProviderFG3(Project project) { + super(project); + } + + @Override + public void initFiles() throws IOException { + super.initFiles(); + File globalCache = getExtension().getForgeProvider().getGlobalCache(); + minecraftClientSrgJar = new File(globalCache, "minecraft-client-srg.jar"); + minecraftServerSrgJar = new File(globalCache, "minecraft-server-srg.jar"); + minecraftClientPatchedSrgJar = new File(globalCache, "client-srg-patched.jar"); + minecraftServerPatchedSrgJar = new File(globalCache, "server-srg-patched.jar"); + } + + @Override + protected File[] getGlobalCaches() { + File[] files = { + minecraftClientSrgJar, + minecraftServerSrgJar, + minecraftClientPatchedSrgJar, + minecraftServerPatchedSrgJar, + minecraftMergedPatchedSrgJar, + minecraftClientExtra, + }; + + if (forgeMergedJar != null) { + Arrays.copyOf(files, files.length + 1); + files[files.length - 1] = forgeMergedJar; + } + + return files; + } + + private boolean dirty; + + @Override + public void beginTransform() throws Exception { + if (atDirty) { + getProject().getLogger().lifecycle(":found dirty access transformers"); + } + + this.dirty = false; + + if (!minecraftClientSrgJar.exists() || !minecraftServerSrgJar.exists()) { + this.dirty = true; + // Remap official jars to MCPConfig remapped srg jars + createSrgJars(getProject().getLogger()); + } + + if (!minecraftClientPatchedSrgJar.exists() || !minecraftServerPatchedSrgJar.exists()) { + this.dirty = true; + patchJars(getProject().getLogger()); + } + } + + @Override + public void endTransform() throws Exception { + if (dirty || !minecraftMergedPatchedSrgJar.exists()) { + mergeJars(getProject().getLogger()); + } + + if (atDirty || !minecraftMergedPatchedSrgAtJar.exists()) { + this.dirty = true; + accessTransformForge(getProject().getLogger()); + } + + if (forgeMergedJar != null && !forgeMergedJar.exists()) { + this.dirty = true; + } + + if (dirty) { + remapPatchedJar(getProject().getLogger()); + + if (getExtension().isForgeAndOfficial()) { + fillClientExtraJar(); + } + } + + this.filesDirty = dirty; + this.dirty = false; + + if (getExtension().isForgeAndOfficial()) { + addDependency(minecraftClientExtra, Constants.Configurations.FORGE_EXTRA); + } + } + + private void createSrgJars(Logger logger) throws Exception { + MinecraftProviderImpl minecraftProvider = getExtension().getMinecraftProvider(); + produceSrgJar(getExtension().isForgeAndOfficial(), minecraftProvider.minecraftClientJar.toPath(), minecraftProvider.getMinecraftServerJar().toPath()); + } + + private void produceSrgJar(boolean official, Path clientJar, Path serverJar) throws IOException { + Path tmpSrg = getToSrgMappings(); + Set mcLibs = getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_DEPENDENCIES).resolve(); + + ThreadingUtils.run(() -> { + Files.copy(SpecialSourceExecutor.produceSrgJar(getExtension().getMcpConfigProvider().getRemapAction(), getProject(), "client", mcLibs, clientJar, tmpSrg), minecraftClientSrgJar.toPath()); + }, () -> { + Files.copy(SpecialSourceExecutor.produceSrgJar(getExtension().getMcpConfigProvider().getRemapAction(), getProject(), "server", mcLibs, serverJar, tmpSrg), minecraftServerSrgJar.toPath()); + }); + } + + private Path getToSrgMappings() throws IOException { + if (getExtension().getSrgProvider().isTsrgV2()) { + return getExtension().getSrgProvider().getMergedMojangRaw(); + } else { + return getExtension().getMcpConfigProvider().getMappings(); + } + } + + public enum Environment { + CLIENT(provider -> provider.minecraftClientSrgJar, + provider -> provider.minecraftClientPatchedSrgJar + ), + SERVER(provider -> provider.minecraftServerSrgJar, + provider -> provider.minecraftServerPatchedSrgJar + ); + + final Function srgJar; + public final Function patchedSrgJar; + + Environment(Function srgJar, + Function patchedSrgJar) { + this.srgJar = srgJar; + this.patchedSrgJar = patchedSrgJar; + } + + public String side() { + return name().toLowerCase(Locale.ROOT); + } + } + + private void patchJars(Logger logger) throws IOException { + Stopwatch stopwatch = Stopwatch.createStarted(); + logger.lifecycle(":patching jars"); + + PatchProvider patchProvider = getExtension().getPatchProvider(); + patchJars(minecraftClientSrgJar, minecraftClientPatchedSrgJar, patchProvider.clientPatches, "client"); + patchJars(minecraftServerSrgJar, minecraftServerPatchedSrgJar, patchProvider.serverPatches, "server"); + + ThreadingUtils.run(Environment.values(), environment -> { + copyMissingClasses(environment.srgJar.apply(this), environment.patchedSrgJar.apply(this)); + deleteParameterNames(environment.patchedSrgJar.apply(this)); + + if (getExtension().isForgeAndNotOfficial()) { + fixParameterAnnotation(environment.patchedSrgJar.apply(this)); + } + }); + + logger.lifecycle(":patched jars in " + stopwatch.stop()); + } + + @Override + protected void patchJars(File clean, File output, Path patches, String side) throws IOException { + PrintStream previous = System.out; + + try { + System.setOut(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM)); + } catch (SecurityException ignored) { + // Failed to replace logger filter, just ignore + } + + ConsoleTool.main(new String[] { + "--clean", clean.getAbsolutePath(), + "--output", output.getAbsolutePath(), + "--apply", patches.toAbsolutePath().toString() + }); + + try { + System.setOut(previous); + } catch (SecurityException ignored) { + // Failed to replace logger filter, just ignore + } + } + + @Override + protected void mergeJars(Logger logger) throws Exception { + // FIXME: Hack here: There are no server-only classes so we can just copy the client JAR. + // This will change if upstream Loom adds the possibility for separate projects/source sets per environment. + + + Files.copy(minecraftClientPatchedSrgJar.toPath(), minecraftMergedPatchedSrgJar.toPath()); + + logger.lifecycle(":copying resources"); + + // Copy resources + if (getExtension().isForgeAndNotOfficial()) { + // Copy resources + MinecraftProviderImpl minecraftProvider = getExtension().getMinecraftProvider(); + copyNonClassFiles(minecraftProvider.minecraftClientJar, minecraftMergedPatchedSrgJar); + copyNonClassFiles(minecraftProvider.getMinecraftServerJar(), minecraftMergedPatchedSrgJar); + } + } +} diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java index 7dd8a69eb..a9dec9e82 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java @@ -49,6 +49,8 @@ import com.google.common.base.Stopwatch; import com.google.common.net.UrlEscapers; import com.google.gson.JsonObject; +import net.fabricmc.loom.configuration.providers.forge.MinecraftPatchedProvider; +import net.fabricmc.loom.configuration.providers.forge.fg2.MinecraftPatchedProviderFG2; import org.apache.tools.ant.util.StringUtils; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; @@ -64,7 +66,7 @@ import net.fabricmc.loom.configuration.processors.JarProcessorManager; import net.fabricmc.loom.configuration.processors.MinecraftProcessedProvider; import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl; -import net.fabricmc.loom.configuration.providers.forge.MinecraftPatchedProvider; +import net.fabricmc.loom.configuration.providers.forge.fg3.MinecraftPatchedProviderFG3; import net.fabricmc.loom.configuration.providers.forge.SrgProvider; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftMappedProvider; import net.fabricmc.loom.util.Constants; @@ -147,8 +149,13 @@ public void provide(DependencyInfo dependency, Consumer postPopulation } if (getExtension().isForge()) { - patchedProvider = new MinecraftPatchedProvider(getProject()); - patchedProvider.provide(dependency, postPopulationScheduler); + if (getExtension().getMcpConfigProvider().isFG2()) { + patchedProvider = new MinecraftPatchedProviderFG2(getProject()); + patchedProvider.provide(dependency, postPopulationScheduler); + } else { + patchedProvider = new MinecraftPatchedProviderFG3(getProject()); + patchedProvider.provide(dependency, postPopulationScheduler); + } } mappingTree = readMappings(tinyMappings); @@ -219,7 +226,7 @@ public void provide(DependencyInfo dependency, Consumer postPopulation processorManager.setupProcessors(); if (extension.isForge()) { - patchedProvider.finishProvide(); + patchedProvider.endTransform(); } if (processorManager.active() || (extension.isForge() && patchedProvider.usesProjectCache())) { diff --git a/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java b/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java index 64b47b718..de1756cc3 100644 --- a/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java +++ b/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java @@ -55,32 +55,14 @@ private static String trimLeadingSlash(String string) { return string; } - public static Path produceSrgJar(RemapAction remapAction, Project project, String side, Set mcLibs, Path officialJar, Path mappings, boolean isLegacy, boolean isFG2) - throws Exception { - Set filter; - if (isLegacy) { - filter = Files.readAllLines(mappings, StandardCharsets.UTF_8).stream() - .filter(s -> s.startsWith("CL:")) - .map(s -> s.split(" ")[1] + ".class") - .collect(Collectors.toSet()); - } else { - filter = Files.readAllLines(mappings, StandardCharsets.UTF_8).stream() - .filter(s -> !s.startsWith("\t")) - .map(s -> s.split(" ")[0] + ".class") - .collect(Collectors.toSet()); - } - LoomGradleExtension extension = LoomGradleExtension.get(project.getProject()); - Path stripped = extension.getFiles().getProjectBuildCache().toPath().resolve(officialJar.getFileName().toString().substring(0, officialJar.getFileName().toString().length() - 4) + "-filtered.jar"); - Files.deleteIfExists(stripped); - - project.getLogger().info(officialJar.toString()); + public static void stripJar(Project project, Path inJar, Path outJar, Set filter) throws IOException { Stopwatch stopwatch = Stopwatch.createStarted(); int count = 0; - try (FileSystemUtil.Delegate output = FileSystemUtil.getJarFileSystem(stripped, true)) { - try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(officialJar, false)) { + try (FileSystemUtil.Delegate output = FileSystemUtil.getJarFileSystem(outJar, true)) { + try (FileSystemUtil.Delegate fs = FileSystemUtil.getJarFileSystem(inJar, false)) { ThreadingUtils.TaskCompleter completer = ThreadingUtils.taskCompleter(); for (Path path : (Iterable) Files.walk(fs.get().getPath("/"))::iterator) { @@ -110,10 +92,25 @@ public static Path produceSrgJar(RemapAction remapAction, Project project, Strin } finally { project.getLogger().info("Copied " + count + " class files in " + stopwatch.stop()); } + } + + public static Path produceSrgJar(RemapAction remapAction, Project project, String side, Set mcLibs, Path officialJar, Path mappings) + throws Exception { + Set filter = Files.readAllLines(mappings, StandardCharsets.UTF_8).stream() + .filter(s -> !s.startsWith("\t")) + .map(s -> s.split(" ")[0] + ".class") + .collect(Collectors.toSet()); + + LoomGradleExtension extension = LoomGradleExtension.get(project.getProject()); + Path stripped = extension.getFiles().getProjectBuildCache().toPath().resolve(officialJar.getFileName().toString().substring(0, officialJar.getFileName().toString().length() - 4) + "-filtered.jar"); + Files.deleteIfExists(stripped); + + stripJar(project, officialJar, stripped, filter); + Path output = extension.getFiles().getProjectBuildCache().toPath().resolve(officialJar.getFileName().toString().substring(0, officialJar.getFileName().toString().length() - 4) + "-srg-output.jar"); -// Files.deleteIfExists(output); - stopwatch = Stopwatch.createStarted(); + Files.deleteIfExists(output); + Stopwatch stopwatch = Stopwatch.createStarted(); project.getLogger().info(stripped.toString()); List args = remapAction.getArgs(stripped, output, mappings, project.files(mcLibs)); @@ -122,36 +119,32 @@ public static Path produceSrgJar(RemapAction remapAction, Project project, Strin Path workingDir = tmpDir(); - if (!isFG2) { - project.javaexec(spec -> { - spec.setArgs(args); - spec.setClasspath(remapAction.getClasspath()); - spec.workingDir(workingDir.toFile()); - spec.getMainClass().set(remapAction.getMainClass()); - - // if running with INFO or DEBUG logging - if (project.getGradle().getStartParameter().getShowStacktrace() != ShowStacktrace.INTERNAL_EXCEPTIONS - || project.getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) < 0) { - spec.setStandardOutput(System.out); - spec.setErrorOutput(System.err); - } else { - spec.setStandardOutput(NullOutputStream.NULL_OUTPUT_STREAM); - spec.setErrorOutput(NullOutputStream.NULL_OUTPUT_STREAM); - } - }).rethrowFailure().assertNormalExitValue(); - } else { - Files.copy(stripped, output, StandardCopyOption.REPLACE_EXISTING); - } + project.javaexec(spec -> { + spec.setArgs(args); + spec.setClasspath(remapAction.getClasspath()); + spec.workingDir(workingDir.toFile()); + spec.getMainClass().set(remapAction.getMainClass()); + + // if running with INFO or DEBUG logging + if (project.getGradle().getStartParameter().getShowStacktrace() != ShowStacktrace.INTERNAL_EXCEPTIONS + || project.getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) < 0) { + spec.setStandardOutput(System.out); + spec.setErrorOutput(System.err); + } else { + spec.setStandardOutput(NullOutputStream.NULL_OUTPUT_STREAM); + spec.setErrorOutput(NullOutputStream.NULL_OUTPUT_STREAM); + } + }).rethrowFailure().assertNormalExitValue(); project.getLogger().lifecycle(":remapped minecraft (" + remapAction + ", " + side + ", official -> mojang) in " + stopwatch.stop()); -// Files.deleteIfExists(stripped); + Files.deleteIfExists(stripped); Path tmp = tmpFile(); Files.deleteIfExists(tmp); Files.copy(output, tmp); -// Files.deleteIfExists(output); + Files.deleteIfExists(output); return tmp; } diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/AdaptiveCoding.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/AdaptiveCoding.java new file mode 100644 index 000000000..d1aff1307 --- /dev/null +++ b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/AdaptiveCoding.java @@ -0,0 +1,297 @@ +/* + * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package net.fabricmc.shade.com.sun.java.util.jar.pack; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Adaptive coding. + * See the section "Adaptive Encodings" in the Pack200 spec. + * @author John Rose + */ +class AdaptiveCoding implements CodingMethod { + CodingMethod headCoding; + int headLength; + CodingMethod tailCoding; + + public AdaptiveCoding(int headLength, CodingMethod headCoding, CodingMethod tailCoding) { + assert(isCodableLength(headLength)); + this.headLength = headLength; + this.headCoding = headCoding; + this.tailCoding = tailCoding; + } + + public void setHeadCoding(CodingMethod headCoding) { + this.headCoding = headCoding; + } + public void setHeadLength(int headLength) { + assert(isCodableLength(headLength)); + this.headLength = headLength; + } + public void setTailCoding(CodingMethod tailCoding) { + this.tailCoding = tailCoding; + } + + public boolean isTrivial() { + return headCoding == tailCoding; + } + + // CodingMethod methods. + public void writeArrayTo(OutputStream out, int[] a, int start, int end) throws IOException { + writeArray(this, out, a, start, end); + } + // writeArrayTo must be coded iteratively, not recursively: + private static void writeArray(AdaptiveCoding run, OutputStream out, int[] a, int start, int end) throws IOException { + for (;;) { + int mid = start+run.headLength; + assert(mid <= end); + run.headCoding.writeArrayTo(out, a, start, mid); + start = mid; + if (run.tailCoding instanceof AdaptiveCoding) { + run = (AdaptiveCoding) run.tailCoding; + continue; + } + break; + } + run.tailCoding.writeArrayTo(out, a, start, end); + } + + public void readArrayFrom(InputStream in, int[] a, int start, int end) throws IOException { + readArray(this, in, a, start, end); + } + private static void readArray(AdaptiveCoding run, InputStream in, int[] a, int start, int end) throws IOException { + for (;;) { + int mid = start+run.headLength; + assert(mid <= end); + run.headCoding.readArrayFrom(in, a, start, mid); + start = mid; + if (run.tailCoding instanceof AdaptiveCoding) { + run = (AdaptiveCoding) run.tailCoding; + continue; + } + break; + } + run.tailCoding.readArrayFrom(in, a, start, end); + } + + public static final int KX_MIN = 0; + public static final int KX_MAX = 3; + public static final int KX_LG2BASE = 4; + public static final int KX_BASE = 16; + + public static final int KB_MIN = 0x00; + public static final int KB_MAX = 0xFF; + public static final int KB_OFFSET = 1; + public static final int KB_DEFAULT = 3; + + static int getKXOf(int K) { + for (int KX = KX_MIN; KX <= KX_MAX; KX++) { + if (((K - KB_OFFSET) & ~KB_MAX) == 0) + return KX; + K >>>= KX_LG2BASE; + } + return -1; + } + + static int getKBOf(int K) { + int KX = getKXOf(K); + if (KX < 0) return -1; + K >>>= (KX * KX_LG2BASE); + return K-1; + } + + static int decodeK(int KX, int KB) { + assert(KX_MIN <= KX && KX <= KX_MAX); + assert(KB_MIN <= KB && KB <= KB_MAX); + return (KB+KB_OFFSET) << (KX * KX_LG2BASE); + } + + static int getNextK(int K) { + if (K <= 0) return 1; // 1st K value + int KX = getKXOf(K); + if (KX < 0) return Integer.MAX_VALUE; + // This is the increment we expect to apply: + int unit = 1 << (KX * KX_LG2BASE); + int mask = KB_MAX << (KX * KX_LG2BASE); + int K1 = K + unit; + K1 &= ~(unit-1); // cut off stray low-order bits + if (((K1 - unit) & ~mask) == 0) { + assert(getKXOf(K1) == KX); + return K1; + } + if (KX == KX_MAX) return Integer.MAX_VALUE; + KX += 1; + int mask2 = KB_MAX << (KX * KX_LG2BASE); + K1 |= (mask & ~mask2); + K1 += unit; + assert(getKXOf(K1) == KX); + return K1; + } + + // Is K of the form ((KB:[0..255])+1) * 16^(KX:{0..3])? + public static boolean isCodableLength(int K) { + int KX = getKXOf(K); + if (KX < 0) return false; + int unit = 1 << (KX * KX_LG2BASE); + int mask = KB_MAX << (KX * KX_LG2BASE); + return ((K - unit) & ~mask) == 0; + } + + public byte[] getMetaCoding(Coding dflt) { + //assert(!isTrivial()); // can happen + // See the isCodableLength restriction in CodingChooser. + ByteArrayOutputStream bytes = new ByteArrayOutputStream(10); + try { + makeMetaCoding(this, dflt, bytes); + } catch (IOException ee) { + throw new RuntimeException(ee); + } + return bytes.toByteArray(); + } + private static void makeMetaCoding(AdaptiveCoding run, Coding dflt, + ByteArrayOutputStream bytes) + throws IOException { + for (;;) { + CodingMethod headCoding = run.headCoding; + int headLength = run.headLength; + CodingMethod tailCoding = run.tailCoding; + int K = headLength; + assert(isCodableLength(K)); + int ADef = (headCoding == dflt)?1:0; + int BDef = (tailCoding == dflt)?1:0; + if (ADef+BDef > 1) BDef = 0; // arbitrary choice + int ABDef = 1*ADef + 2*BDef; + assert(ABDef < 3); + int KX = getKXOf(K); + int KB = getKBOf(K); + assert(decodeK(KX, KB) == K); + int KBFlag = (KB != KB_DEFAULT)?1:0; + bytes.write(Constants._meta_run + KX + 4*KBFlag + 8*ABDef); + if (KBFlag != 0) bytes.write(KB); + if (ADef == 0) bytes.write(headCoding.getMetaCoding(dflt)); + if (tailCoding instanceof AdaptiveCoding) { + run = (AdaptiveCoding) tailCoding; + continue; // tail call, to avoid deep stack recursion + } + if (BDef == 0) bytes.write(tailCoding.getMetaCoding(dflt)); + break; + } + } + public static int parseMetaCoding(byte[] bytes, int pos, Coding dflt, CodingMethod res[]) { + int op = bytes[pos++] & 0xFF; + if (op < Constants._meta_run || op >= Constants._meta_pop) return pos-1; // backup + AdaptiveCoding prevc = null; + for (boolean keepGoing = true; keepGoing; ) { + keepGoing = false; + assert(op >= Constants._meta_run); + op -= Constants._meta_run; + int KX = op % 4; + int KBFlag = (op / 4) % 2; + int ABDef = (op / 8); + assert(ABDef < 3); + int ADef = (ABDef & 1); + int BDef = (ABDef & 2); + CodingMethod[] ACode = {dflt}, BCode = {dflt}; + int KB = KB_DEFAULT; + if (KBFlag != 0) + KB = bytes[pos++] & 0xFF; + if (ADef == 0) { + pos = BandStructure.parseMetaCoding(bytes, pos, dflt, ACode); + } + if (BDef == 0 && + ((op = bytes[pos] & 0xFF) >= Constants._meta_run) && op < Constants._meta_pop) { + pos++; + keepGoing = true; + } else if (BDef == 0) { + pos = BandStructure.parseMetaCoding(bytes, pos, dflt, BCode); + } + AdaptiveCoding newc = new AdaptiveCoding(decodeK(KX, KB), + ACode[0], BCode[0]); + if (prevc == null) { + res[0] = newc; + } else { + prevc.tailCoding = newc; + } + prevc = newc; + } + return pos; + } + + private String keyString(CodingMethod m) { + if (m instanceof Coding) + return ((Coding)m).keyString(); + return m.toString(); + } + public String toString() { + StringBuilder res = new StringBuilder(20); + AdaptiveCoding run = this; + res.append("run("); + for (;;) { + res.append(run.headLength).append("*"); + res.append(keyString(run.headCoding)); + if (run.tailCoding instanceof AdaptiveCoding) { + run = (AdaptiveCoding) run.tailCoding; + res.append(" "); + continue; + } + break; + } + res.append(" **").append(keyString(run.tailCoding)); + res.append(")"); + return res.toString(); + } + +/* + public static void main(String av[]) { + int[][] samples = { + {1,2,3,4,5}, + {254,255,256,256+1*16,256+2*16}, + {0xfd,0xfe,0xff,0x100,0x110,0x120,0x130}, + {0xfd0,0xfe0,0xff0,0x1000,0x1100,0x1200,0x1300}, + {0xfd00,0xfe00,0xff00,0x10000,0x11000,0x12000,0x13000}, + {0xfd000,0xfe000,0xff000,0x100000} + }; + for (int i = 0; i < samples.length; i++) { + for (int j = 0; j < samples[i].length; j++) { + int K = samples[i][j]; + int KX = getKXOf(K); + int KB = getKBOf(K); + System.out.println("K="+Integer.toHexString(K)+ + " KX="+KX+" KB="+KB); + assert(isCodableLength(K)); + assert(K == decodeK(KX, KB)); + if (j == 0) continue; + int K1 = samples[i][j-1]; + assert(K == getNextK(K1)); + } + } + } +//*/ + +} diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Attribute.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Attribute.java new file mode 100644 index 000000000..48b0c328e --- /dev/null +++ b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Attribute.java @@ -0,0 +1,1695 @@ +/* + * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package net.fabricmc.shade.com.sun.java.util.jar.pack; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Represents an attribute in a class-file. + * Takes care to remember where constant pool indexes occur. + * Implements the "little language" of Pack200 for describing + * attribute layouts. + * @author John Rose + */ +class Attribute implements Comparable { + // Attribute instance fields. + + Layout def; // the name and format of this attr + byte[] bytes; // the actual bytes + Object fixups; // reference relocations, if any are required + + public String name() { return def.name(); } + public Layout layout() { return def; } + public byte[] bytes() { return bytes; } + public int size() { return bytes.length; } + public ConstantPool.Entry getNameRef() { return def.getNameRef(); } + + private Attribute(Attribute old) { + this.def = old.def; + this.bytes = old.bytes; + this.fixups = old.fixups; + } + + public Attribute(Layout def, byte[] bytes, Object fixups) { + this.def = def; + this.bytes = bytes; + this.fixups = fixups; + Fixups.setBytes(fixups, bytes); + } + public Attribute(Layout def, byte[] bytes) { + this(def, bytes, null); + } + + public Attribute addContent(byte[] bytes, Object fixups) { + assert(isCanonical()); + if (bytes.length == 0 && fixups == null) + return this; + Attribute res = new Attribute(this); + res.bytes = bytes; + res.fixups = fixups; + Fixups.setBytes(fixups, bytes); + return res; + } + public Attribute addContent(byte[] bytes) { + return addContent(bytes, null); + } + + public void finishRefs(ConstantPool.Index ix) { + if (fixups != null) { + Fixups.finishRefs(fixups, bytes, ix); + fixups = null; + } + } + + public boolean isCanonical() { + return this == def.canon; + } + + @Override + public int compareTo(Attribute that) { + return this.def.compareTo(that.def); + } + + private static final Map, List> canonLists = new HashMap<>(); + private static final Map attributes = new HashMap<>(); + private static final Map standardDefs = new HashMap<>(); + + // Canonicalized lists of trivial attrs (Deprecated, etc.) + // are used by trimToSize, in order to reduce footprint + // of some common cases. (Note that Code attributes are + // always zero size.) + public static List getCanonList(List al) { + synchronized (canonLists) { + List cl = canonLists.get(al); + if (cl == null) { + cl = new ArrayList<>(al.size()); + cl.addAll(al); + cl = Collections.unmodifiableList(cl); + canonLists.put(al, cl); + } + return cl; + } + } + + // Find the canonical empty attribute with the given ctype, name, layout. + public static Attribute find(int ctype, String name, String layout) { + Layout key = Layout.makeKey(ctype, name, layout); + synchronized (attributes) { + Attribute a = attributes.get(key); + if (a == null) { + a = new Layout(ctype, name, layout).canonicalInstance(); + attributes.put(key, a); + } + return a; + } + } + + public static Layout keyForLookup(int ctype, String name) { + return Layout.makeKey(ctype, name); + } + + // Find canonical empty attribute with given ctype and name, + // and with the standard layout. + public static Attribute lookup(Map defs, int ctype, + String name) { + if (defs == null) { + defs = standardDefs; + } + return defs.get(Layout.makeKey(ctype, name)); + } + + public static Attribute define(Map defs, int ctype, + String name, String layout) { + Attribute a = find(ctype, name, layout); + defs.put(Layout.makeKey(ctype, name), a); + return a; + } + + static { + Map sd = standardDefs; + define(sd, Constants.ATTR_CONTEXT_CLASS, "Signature", "RSH"); + define(sd, Constants.ATTR_CONTEXT_CLASS, "Synthetic", ""); + define(sd, Constants.ATTR_CONTEXT_CLASS, "Deprecated", ""); + define(sd, Constants.ATTR_CONTEXT_CLASS, "SourceFile", "RUH"); + define(sd, Constants.ATTR_CONTEXT_CLASS, "EnclosingMethod", "RCHRDNH"); + define(sd, Constants.ATTR_CONTEXT_CLASS, "InnerClasses", "NH[RCHRCNHRUNHFH]"); + define(sd, Constants.ATTR_CONTEXT_CLASS, "BootstrapMethods", "NH[RMHNH[KLH]]"); + + define(sd, Constants.ATTR_CONTEXT_FIELD, "Signature", "RSH"); + define(sd, Constants.ATTR_CONTEXT_FIELD, "Synthetic", ""); + define(sd, Constants.ATTR_CONTEXT_FIELD, "Deprecated", ""); + define(sd, Constants.ATTR_CONTEXT_FIELD, "ConstantValue", "KQH"); + + define(sd, Constants.ATTR_CONTEXT_METHOD, "Signature", "RSH"); + define(sd, Constants.ATTR_CONTEXT_METHOD, "Synthetic", ""); + define(sd, Constants.ATTR_CONTEXT_METHOD, "Deprecated", ""); + define(sd, Constants.ATTR_CONTEXT_METHOD, "Exceptions", "NH[RCH]"); + define(sd, Constants.ATTR_CONTEXT_METHOD, "MethodParameters", "NB[RUNHFH]"); + //define(sd, ATTR_CONTEXT_METHOD, "Code", "HHNI[B]NH[PHPOHPOHRCNH]NH[RUHNI[B]]"); + + define(sd, Constants.ATTR_CONTEXT_CODE, "StackMapTable", + ("[NH[(1)]]" + + "[TB" + + "(64-127)[(2)]" + + "(247)[(1)(2)]" + + "(248-251)[(1)]" + + "(252)[(1)(2)]" + + "(253)[(1)(2)(2)]" + + "(254)[(1)(2)(2)(2)]" + + "(255)[(1)NH[(2)]NH[(2)]]" + + "()[]" + + "]" + + "[H]" + + "[TB(7)[RCH](8)[PH]()[]]")); + + define(sd, Constants.ATTR_CONTEXT_CODE, "LineNumberTable", "NH[PHH]"); + define(sd, Constants.ATTR_CONTEXT_CODE, "LocalVariableTable", "NH[PHOHRUHRSHH]"); + define(sd, Constants.ATTR_CONTEXT_CODE, "LocalVariableTypeTable", "NH[PHOHRUHRSHH]"); + //define(sd, ATTR_CONTEXT_CODE, "CharacterRangeTable", "NH[PHPOHIIH]"); + //define(sd, ATTR_CONTEXT_CODE, "CoverageTable", "NH[PHHII]"); + + // Note: Code and InnerClasses are special-cased elsewhere. + // Their layout specs. are given here for completeness. + // The Code spec is incomplete, in that it does not distinguish + // bytecode bytes or locate CP references. + // The BootstrapMethods attribute is also special-cased + // elsewhere as an appendix to the local constant pool. + } + + // Metadata. + // + // We define metadata using similar layouts + // for all five kinds of metadata attributes and 2 type metadata attributes + // + // Regular annotations are a counted list of [RSHNH[RUH(1)]][...] + // pack.method.attribute.RuntimeVisibleAnnotations=[NH[(1)]][RSHNH[RUH(1)]][TB...] + // + // Parameter annotations are a counted list of regular annotations. + // pack.method.attribute.RuntimeVisibleParameterAnnotations=[NB[(1)]][NH[(1)]][RSHNH[RUH(1)]][TB...] + // + // RuntimeInvisible annotations are defined similarly... + // Non-method annotations are defined similarly... + // + // Annotation are a simple tagged value [TB...] + // pack.attribute.method.AnnotationDefault=[TB...] + + static { + String mdLayouts[] = { + Attribute.normalizeLayoutString + ("" + +"\n # parameter_annotations :=" + +"\n [ NB[(1)] ] # forward call to annotations" + ), + Attribute.normalizeLayoutString + ("" + +"\n # annotations :=" + +"\n [ NH[(1)] ] # forward call to annotation" + +"\n " + ), + Attribute.normalizeLayoutString + ("" + +"\n # annotation :=" + +"\n [RSH" + +"\n NH[RUH (1)] # forward call to value" + +"\n ]" + ), + Attribute.normalizeLayoutString + ("" + +"\n # value :=" + +"\n [TB # Callable 2 encodes one tagged value." + +"\n (\\B,\\C,\\I,\\S,\\Z)[KIH]" + +"\n (\\D)[KDH]" + +"\n (\\F)[KFH]" + +"\n (\\J)[KJH]" + +"\n (\\c)[RSH]" + +"\n (\\e)[RSH RUH]" + +"\n (\\s)[RUH]" + +"\n (\\[)[NH[(0)]] # backward self-call to value" + +"\n (\\@)[RSH NH[RUH (0)]] # backward self-call to value" + +"\n ()[] ]" + ) + }; + /* + * RuntimeVisibleTypeAnnotation and RuntimeInvisibleTypeAnnotatation are + * similar to RuntimeVisibleAnnotation and RuntimeInvisibleAnnotation, + * a type-annotation union and a type-path structure precedes the + * annotation structure + */ + String typeLayouts[] = { + Attribute.normalizeLayoutString + ("" + +"\n # type-annotations :=" + +"\n [ NH[(1)(2)(3)] ] # forward call to type-annotations" + ), + Attribute.normalizeLayoutString + ( "" + +"\n # type-annotation :=" + +"\n [TB" + +"\n (0-1) [B] # {CLASS, METHOD}_TYPE_PARAMETER" + +"\n (16) [FH] # CLASS_EXTENDS" + +"\n (17-18) [BB] # {CLASS, METHOD}_TYPE_PARAMETER_BOUND" + +"\n (19-21) [] # FIELD, METHOD_RETURN, METHOD_RECEIVER" + +"\n (22) [B] # METHOD_FORMAL_PARAMETER" + +"\n (23) [H] # THROWS" + +"\n (64-65) [NH[PHOHH]] # LOCAL_VARIABLE, RESOURCE_VARIABLE" + +"\n (66) [H] # EXCEPTION_PARAMETER" + +"\n (67-70) [PH] # INSTANCEOF, NEW, {CONSTRUCTOR, METHOD}_REFERENCE_RECEIVER" + +"\n (71-75) [PHB] # CAST, {CONSTRUCTOR,METHOD}_INVOCATION_TYPE_ARGUMENT, {CONSTRUCTOR, METHOD}_REFERENCE_TYPE_ARGUMENT" + +"\n ()[] ]" + ), + Attribute.normalizeLayoutString + ("" + +"\n # type-path" + +"\n [ NB[BB] ]" + ) + }; + Map sd = standardDefs; + String defaultLayout = mdLayouts[3]; + String annotationsLayout = mdLayouts[1] + mdLayouts[2] + mdLayouts[3]; + String paramsLayout = mdLayouts[0] + annotationsLayout; + String typesLayout = typeLayouts[0] + typeLayouts[1] + + typeLayouts[2] + mdLayouts[2] + mdLayouts[3]; + + for (int ctype = 0; ctype < Constants.ATTR_CONTEXT_LIMIT; ctype++) { + if (ctype != Constants.ATTR_CONTEXT_CODE) { + define(sd, ctype, + "RuntimeVisibleAnnotations", annotationsLayout); + define(sd, ctype, + "RuntimeInvisibleAnnotations", annotationsLayout); + + if (ctype == Constants.ATTR_CONTEXT_METHOD) { + define(sd, ctype, + "RuntimeVisibleParameterAnnotations", paramsLayout); + define(sd, ctype, + "RuntimeInvisibleParameterAnnotations", paramsLayout); + define(sd, ctype, + "AnnotationDefault", defaultLayout); + } + } + define(sd, ctype, + "RuntimeVisibleTypeAnnotations", typesLayout); + define(sd, ctype, + "RuntimeInvisibleTypeAnnotations", typesLayout); + } + } + + public static String contextName(int ctype) { + switch (ctype) { + case Constants.ATTR_CONTEXT_CLASS: return "class"; + case Constants.ATTR_CONTEXT_FIELD: return "field"; + case Constants.ATTR_CONTEXT_METHOD: return "method"; + case Constants.ATTR_CONTEXT_CODE: return "code"; + } + return null; + } + + /** Base class for any attributed object (Class, Field, Method, Code). + * Flags are included because they are used to help transmit the + * presence of attributes. That is, flags are a mix of modifier + * bits and attribute indicators. + */ + public abstract static + class Holder { + + // We need this abstract method to interpret embedded CP refs. + protected abstract ConstantPool.Entry[] getCPMap(); + + protected int flags; // defined here for convenience + protected List attributes; + + public int attributeSize() { + return (attributes == null) ? 0 : attributes.size(); + } + + public void trimToSize() { + if (attributes == null) { + return; + } + if (attributes.isEmpty()) { + attributes = null; + return; + } + if (attributes instanceof ArrayList) { + ArrayList al = (ArrayList)attributes; + al.trimToSize(); + boolean allCanon = true; + for (Attribute a : al) { + if (!a.isCanonical()) { + allCanon = false; + } + if (a.fixups != null) { + assert(!a.isCanonical()); + a.fixups = Fixups.trimToSize(a.fixups); + } + } + if (allCanon) { + // Replace private writable attribute list + // with only trivial entries by public unique + // immutable attribute list with the same entries. + attributes = getCanonList(al); + } + } + } + + public void addAttribute(Attribute a) { + if (attributes == null) + attributes = new ArrayList<>(3); + else if (!(attributes instanceof ArrayList)) + attributes = new ArrayList<>(attributes); // unfreeze it + attributes.add(a); + } + + public Attribute removeAttribute(Attribute a) { + if (attributes == null) return null; + if (!attributes.contains(a)) return null; + if (!(attributes instanceof ArrayList)) + attributes = new ArrayList<>(attributes); // unfreeze it + attributes.remove(a); + return a; + } + + public Attribute getAttribute(int n) { + return attributes.get(n); + } + + protected void visitRefs(int mode, Collection refs) { + if (attributes == null) return; + for (Attribute a : attributes) { + a.visitRefs(this, mode, refs); + } + } + + static final List noAttributes = Arrays.asList(new Attribute[0]); + + public List getAttributes() { + if (attributes == null) + return noAttributes; + return attributes; + } + + public void setAttributes(List attrList) { + if (attrList.isEmpty()) + attributes = null; + else + attributes = attrList; + } + + public Attribute getAttribute(String attrName) { + if (attributes == null) return null; + for (Attribute a : attributes) { + if (a.name().equals(attrName)) + return a; + } + return null; + } + + public Attribute getAttribute(Layout attrDef) { + if (attributes == null) return null; + for (Attribute a : attributes) { + if (a.layout() == attrDef) + return a; + } + return null; + } + + public Attribute removeAttribute(String attrName) { + return removeAttribute(getAttribute(attrName)); + } + + public Attribute removeAttribute(Layout attrDef) { + return removeAttribute(getAttribute(attrDef)); + } + + public void strip(String attrName) { + removeAttribute(getAttribute(attrName)); + } + } + + // Lightweight interface to hide details of band structure. + // Also used for testing. + public abstract static + class ValueStream { + public int getInt(int bandIndex) { throw undef(); } + public void putInt(int bandIndex, int value) { throw undef(); } + public ConstantPool.Entry getRef(int bandIndex) { throw undef(); } + public void putRef(int bandIndex, ConstantPool.Entry ref) { throw undef(); } + // Note: decodeBCI goes w/ getInt/Ref; encodeBCI goes w/ putInt/Ref + public int decodeBCI(int bciCode) { throw undef(); } + public int encodeBCI(int bci) { throw undef(); } + public void noteBackCall(int whichCallable) { /* ignore by default */ } + private RuntimeException undef() { + return new UnsupportedOperationException("ValueStream method"); + } + } + + // Element kinds: + static final byte EK_INT = 1; // B H I SH etc. + static final byte EK_BCI = 2; // PH POH etc. + static final byte EK_BCO = 3; // OH etc. + static final byte EK_FLAG = 4; // FH etc. + static final byte EK_REPL = 5; // NH[...] etc. + static final byte EK_REF = 6; // RUH, RUNH, KQH, etc. + static final byte EK_UN = 7; // TB(...)[...] etc. + static final byte EK_CASE = 8; // (...)[...] etc. + static final byte EK_CALL = 9; // (0), (1), etc. + static final byte EK_CBLE = 10; // [...][...] etc. + static final byte EF_SIGN = 1<<0; // INT is signed + static final byte EF_DELTA = 1<<1; // BCI/BCI value is diff'ed w/ previous + static final byte EF_NULL = 1<<2; // null REF is expected/allowed + static final byte EF_BACK = 1<<3; // call, callable, case is backward + static final int NO_BAND_INDEX = -1; + + /** A "class" of attributes, characterized by a context-type, name + * and format. The formats are specified in a "little language". + */ + public static + class Layout implements Comparable { + int ctype; // attribute context type, e.g., ATTR_CONTEXT_CODE + String name; // name of attribute + boolean hasRefs; // this kind of attr contains CP refs? + String layout; // layout specification + int bandCount; // total number of elems + Element[] elems; // tokenization of layout + Attribute canon; // canonical instance of this layout + + public int ctype() { return ctype; } + public String name() { return name; } + public String layout() { return layout; } + public Attribute canonicalInstance() { return canon; } + + public ConstantPool.Entry getNameRef() { + return ConstantPool.getUtf8Entry(name()); + } + + public boolean isEmpty() { + return layout.isEmpty(); + } + + public Layout(int ctype, String name, String layout) { + this.ctype = ctype; + this.name = name.intern(); + this.layout = layout.intern(); + assert(ctype < Constants.ATTR_CONTEXT_LIMIT); + boolean hasCallables = layout.startsWith("["); + try { + if (!hasCallables) { + this.elems = tokenizeLayout(this, -1, layout); + } else { + String[] bodies = splitBodies(layout); + // Make the callables now, so they can be linked immediately. + Element[] lelems = new Element[bodies.length]; + this.elems = lelems; + for (int i = 0; i < lelems.length; i++) { + Element ce = this.new Element(); + ce.kind = EK_CBLE; + ce.removeBand(); + ce.bandIndex = NO_BAND_INDEX; + ce.layout = bodies[i]; + lelems[i] = ce; + } + // Next fill them in. + for (int i = 0; i < lelems.length; i++) { + Element ce = lelems[i]; + ce.body = tokenizeLayout(this, i, bodies[i]); + } + //System.out.println(Arrays.asList(elems)); + } + } catch (StringIndexOutOfBoundsException ee) { + // simplest way to catch syntax errors... + throw new RuntimeException("Bad attribute layout: "+layout, ee); + } + // Some uses do not make a fresh one for each occurrence. + // For example, if layout == "", we only need one attr to share. + canon = new Attribute(this, Constants.noBytes); + } + private Layout() {} + static Layout makeKey(int ctype, String name, String layout) { + Layout def = new Layout(); + def.ctype = ctype; + def.name = name.intern(); + def.layout = layout.intern(); + assert(ctype < Constants.ATTR_CONTEXT_LIMIT); + return def; + } + static Layout makeKey(int ctype, String name) { + return makeKey(ctype, name, ""); + } + + public Attribute addContent(byte[] bytes, Object fixups) { + return canon.addContent(bytes, fixups); + } + public Attribute addContent(byte[] bytes) { + return canon.addContent(bytes, null); + } + + @Override + public boolean equals(Object x) { + return ( x != null) && ( x.getClass() == Layout.class ) && + equals((Layout)x); + } + public boolean equals(Layout that) { + return this.name.equals(that.name) + && this.layout.equals(that.layout) + && this.ctype == that.ctype; + } + @Override + public int hashCode() { + return (((17 + name.hashCode()) + * 37 + layout.hashCode()) + * 37 + ctype); + } + @Override + public int compareTo(Layout that) { + int r; + r = this.name.compareTo(that.name); + if (r != 0) return r; + r = this.layout.compareTo(that.layout); + if (r != 0) return r; + return this.ctype - that.ctype; + } + @Override + public String toString() { + String str = contextName(ctype)+"."+name+"["+layout+"]"; + // If -ea, print out more informative strings! + assert((str = stringForDebug()) != null); + return str; + } + private String stringForDebug() { + return contextName(ctype)+"."+name+Arrays.asList(elems); + } + + public + class Element { + String layout; // spelling in the little language + byte flags; // EF_SIGN, etc. + byte kind; // EK_UINT, etc. + byte len; // scalar length of element + byte refKind; // CONSTANT_String, etc. + int bandIndex; // which band does this element govern? + int value; // extra parameter + Element[] body; // extra data (for replications, unions, calls) + + boolean flagTest(byte mask) { return (flags & mask) != 0; } + + Element() { + bandIndex = bandCount++; + } + + void removeBand() { + --bandCount; + assert(bandIndex == bandCount); + bandIndex = NO_BAND_INDEX; + } + + public boolean hasBand() { + return bandIndex >= 0; + } + public String toString() { + String str = layout; + // If -ea, print out more informative strings! + assert((str = stringForDebug()) != null); + return str; + } + private String stringForDebug() { + Element[] lbody = this.body; + switch (kind) { + case EK_CALL: + lbody = null; + break; + case EK_CASE: + if (flagTest(EF_BACK)) + lbody = null; + break; + } + return layout + + (!hasBand()?"":"#"+bandIndex) + + "<"+ (flags==0?"":""+flags)+kind+len + + (refKind==0?"":""+refKind) + ">" + + (value==0?"":"("+value+")") + + (lbody==null?"": ""+Arrays.asList(lbody)); + } + } + + public boolean hasCallables() { + return (elems.length > 0 && elems[0].kind == EK_CBLE); + } + private static final Element[] noElems = {}; + public Element[] getCallables() { + if (hasCallables()) { + Element[] nelems = Arrays.copyOf(elems, elems.length); + return nelems; + } else + return noElems; // no callables at all + } + public Element[] getEntryPoint() { + if (hasCallables()) + return elems[0].body; // body of first callable + else { + Element[] nelems = Arrays.copyOf(elems, elems.length); + return nelems; // no callables; whole body + } + } + + /** Return a sequence of tokens from the given attribute bytes. + * Sequence elements will be 1-1 correspondent with my layout tokens. + */ + public void parse(Holder holder, + byte[] bytes, int pos, int len, ValueStream out) { + int end = parseUsing(getEntryPoint(), + holder, bytes, pos, len, out); + if (end != pos + len) + throw new InternalError("layout parsed "+(end-pos)+" out of "+len+" bytes"); + } + /** Given a sequence of tokens, return the attribute bytes. + * Sequence elements must be 1-1 correspondent with my layout tokens. + * The returned object is a cookie for Fixups.finishRefs, which + * must be used to harden any references into integer indexes. + */ + public Object unparse(ValueStream in, ByteArrayOutputStream out) { + Object[] fixups = { null }; + unparseUsing(getEntryPoint(), fixups, in, out); + return fixups[0]; // return ref-bearing cookie, if any + } + + public String layoutForClassVersion(Package.Version vers) { + if (vers.lessThan(Constants.JAVA6_MAX_CLASS_VERSION)) { + // Disallow layout syntax in the oldest protocol version. + return expandCaseDashNotation(layout); + } + return layout; + } + } + + public static + class FormatException extends IOException { + private static final long serialVersionUID = -2542243830788066513L; + + private int ctype; + private String name; + String layout; + public FormatException(String message, + int ctype, String name, String layout) { + super(Constants.ATTR_CONTEXT_NAME[ctype]+ " attribute \"" + name + "\"" + + (message == null? "" : (": " + message))); + this.ctype = ctype; + this.name = name; + this.layout = layout; + } + public FormatException(String message, + int ctype, String name) { + this(message, ctype, name, null); + } + } + + void visitRefs(Holder holder, int mode, final Collection refs) { + if (mode == Constants.VRM_CLASSIC) { + refs.add(getNameRef()); + } + // else the name is owned by the layout, and is processed elsewhere + if (bytes.length == 0) return; // quick exit + if (!def.hasRefs) return; // quick exit + if (fixups != null) { + Fixups.visitRefs(fixups, refs); + return; + } + // References (to a local cpMap) are embedded in the bytes. + def.parse(holder, bytes, 0, bytes.length, + new ValueStream() { + @Override + public void putInt(int bandIndex, int value) { + } + @Override + public void putRef(int bandIndex, ConstantPool.Entry ref) { + refs.add(ref); + } + @Override + public int encodeBCI(int bci) { + return bci; + } + }); + } + + public void parse(Holder holder, byte[] bytes, int pos, int len, ValueStream out) { + def.parse(holder, bytes, pos, len, out); + } + public Object unparse(ValueStream in, ByteArrayOutputStream out) { + return def.unparse(in, out); + } + + @Override + public String toString() { + return def + +"{"+(bytes == null ? -1 : size())+"}" + +(fixups == null? "": fixups.toString()); + } + + /** Remove any informal "pretty printing" from the layout string. + * Removes blanks and control chars. + * Removes '#' comments (to end of line). + * Replaces '\c' by the decimal code of the character c. + * Replaces '0xNNN' by the decimal code of the hex number NNN. + */ + public static + String normalizeLayoutString(String layout) { + StringBuilder buf = new StringBuilder(); + for (int i = 0, len = layout.length(); i < len; ) { + char ch = layout.charAt(i++); + if (ch <= ' ') { + // Skip whitespace and control chars + continue; + } else if (ch == '#') { + // Skip to end of line. + int end1 = layout.indexOf('\n', i); + int end2 = layout.indexOf('\r', i); + if (end1 < 0) end1 = len; + if (end2 < 0) end2 = len; + i = Math.min(end1, end2); + } else if (ch == '\\') { + // Map a character reference to its decimal code. + buf.append((int) layout.charAt(i++)); + } else if (ch == '0' && layout.startsWith("0x", i-1)) { + // Map a hex numeral to its decimal code. + int start = i-1; + int end = start+2; + while (end < len) { + int dig = layout.charAt(end); + if ((dig >= '0' && dig <= '9') || + (dig >= 'a' && dig <= 'f')) + ++end; + else + break; + } + if (end > start) { + String num = layout.substring(start, end); + buf.append(Integer.decode(num)); + i = end; + } else { + buf.append(ch); + } + } else { + buf.append(ch); + } + } + String result = buf.toString(); + if (false && !result.equals(layout)) { + Utils.log.info("Normalizing layout string"); + Utils.log.info(" From: "+layout); + Utils.log.info(" To: "+result); + } + return result; + } + + /// Subroutines for parsing and unparsing: + + /** Parse the attribute layout language. +
+  attribute_layout:
+        ( layout_element )* | ( callable )+
+  layout_element:
+        ( integral | replication | union | call | reference )
+
+  callable:
+        '[' body ']'
+  body:
+        ( layout_element )+
+
+  integral:
+        ( unsigned_int | signed_int | bc_index | bc_offset | flag )
+  unsigned_int:
+        uint_type
+  signed_int:
+        'S' uint_type
+  any_int:
+        ( unsigned_int | signed_int )
+  bc_index:
+        ( 'P' uint_type | 'PO' uint_type )
+  bc_offset:
+        'O' any_int
+  flag:
+        'F' uint_type
+  uint_type:
+        ( 'B' | 'H' | 'I' | 'V' )
+
+  replication:
+        'N' uint_type '[' body ']'
+
+  union:
+        'T' any_int (union_case)* '(' ')' '[' (body)? ']'
+  union_case:
+        '(' union_case_tag (',' union_case_tag)* ')' '[' (body)? ']'
+  union_case_tag:
+        ( numeral | numeral '-' numeral )
+  call:
+        '(' numeral ')'
+
+  reference:
+        reference_type ( 'N' )? uint_type
+  reference_type:
+        ( constant_ref | schema_ref | utf8_ref | untyped_ref )
+  constant_ref:
+        ( 'KI' | 'KJ' | 'KF' | 'KD' | 'KS' | 'KQ' | 'KM' | 'KT' | 'KL' )
+  schema_ref:
+        ( 'RC' | 'RS' | 'RD' | 'RF' | 'RM' | 'RI' | 'RY' | 'RB' | 'RN' )
+  utf8_ref:
+        'RU'
+  untyped_ref:
+        'RQ'
+
+  numeral:
+        '(' ('-')? (digit)+ ')'
+  digit:
+        ( '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' )
+ 
+ */ + static //private + Layout.Element[] tokenizeLayout(Layout self, int curCble, String layout) { + List col = new ArrayList<>(layout.length()); + tokenizeLayout(self, curCble, layout, col); + Layout.Element[] res = new Layout.Element[col.size()]; + col.toArray(res); + return res; + } + static //private + void tokenizeLayout(Layout self, int curCble, String layout, List col) { + boolean prevBCI = false; + for (int len = layout.length(), i = 0; i < len; ) { + int start = i; + int body; + Layout.Element e = self.new Element(); + byte kind; + //System.out.println("at "+i+": ..."+layout.substring(i)); + // strip a prefix + switch (layout.charAt(i++)) { + /// layout_element: integral + case 'B': case 'H': case 'I': case 'V': // unsigned_int + kind = EK_INT; + --i; // reparse + i = tokenizeUInt(e, layout, i); + break; + case 'S': // signed_int + kind = EK_INT; + --i; // reparse + i = tokenizeSInt(e, layout, i); + break; + case 'P': // bc_index + kind = EK_BCI; + if (layout.charAt(i++) == 'O') { + // bc_index: 'PO' tokenizeUInt + e.flags |= EF_DELTA; + // must follow P or PO: + if (!prevBCI) + { i = -i; continue; } // fail + i++; // move forward + } + --i; // reparse + i = tokenizeUInt(e, layout, i); + break; + case 'O': // bc_offset + kind = EK_BCO; + e.flags |= EF_DELTA; + // must follow P or PO: + if (!prevBCI) + { i = -i; continue; } // fail + i = tokenizeSInt(e, layout, i); + break; + case 'F': // flag + kind = EK_FLAG; + i = tokenizeUInt(e, layout, i); + break; + case 'N': // replication: 'N' uint '[' elem ... ']' + kind = EK_REPL; + i = tokenizeUInt(e, layout, i); + if (layout.charAt(i++) != '[') + { i = -i; continue; } // fail + i = skipBody(layout, body = i); + e.body = tokenizeLayout(self, curCble, + layout.substring(body, i++)); + break; + case 'T': // union: 'T' any_int union_case* '(' ')' '[' body ']' + kind = EK_UN; + i = tokenizeSInt(e, layout, i); + List cases = new ArrayList<>(); + for (;;) { + // Keep parsing cases until we hit the default case. + if (layout.charAt(i++) != '(') + { i = -i; break; } // fail + int beg = i; + i = layout.indexOf(')', i); + String cstr = layout.substring(beg, i++); + int cstrlen = cstr.length(); + if (layout.charAt(i++) != '[') + { i = -i; break; } // fail + // Check for duplication. + if (layout.charAt(i) == ']') + body = i; // missing body, which is legal here + else + i = skipBody(layout, body = i); + Layout.Element[] cbody + = tokenizeLayout(self, curCble, + layout.substring(body, i++)); + if (cstrlen == 0) { + Layout.Element ce = self.new Element(); + ce.body = cbody; + ce.kind = EK_CASE; + ce.removeBand(); + cases.add(ce); + break; // done with the whole union + } else { + // Parse a case string. + boolean firstCaseNum = true; + for (int cp = 0, endp;; cp = endp+1) { + // Look for multiple case tags: + endp = cstr.indexOf(',', cp); + if (endp < 0) endp = cstrlen; + String cstr1 = cstr.substring(cp, endp); + if (cstr1.isEmpty()) + cstr1 = "empty"; // will fail parse + int value0, value1; + // Check for a case range (new in 1.6). + int dash = findCaseDash(cstr1, 0); + if (dash >= 0) { + value0 = parseIntBefore(cstr1, dash); + value1 = parseIntAfter(cstr1, dash); + if (value0 >= value1) + { i = -i; break; } // fail + } else { + value0 = value1 = Integer.parseInt(cstr1); + } + // Add a case for each value in value0..value1 + for (;; value0++) { + Layout.Element ce = self.new Element(); + ce.body = cbody; // all cases share one body + ce.kind = EK_CASE; + ce.removeBand(); + if (!firstCaseNum) + // "backward case" repeats a body + ce.flags |= EF_BACK; + firstCaseNum = false; + ce.value = value0; + cases.add(ce); + if (value0 == value1) break; + } + if (endp == cstrlen) { + break; // done with this case + } + } + } + } + e.body = new Layout.Element[cases.size()]; + cases.toArray(e.body); + e.kind = kind; + for (int j = 0; j < e.body.length-1; j++) { + Layout.Element ce = e.body[j]; + if (matchCase(e, ce.value) != ce) { + // Duplicate tag. + { i = -i; break; } // fail + } + } + break; + case '(': // call: '(' '-'? digit+ ')' + kind = EK_CALL; + e.removeBand(); + i = layout.indexOf(')', i); + String cstr = layout.substring(start+1, i++); + int offset = Integer.parseInt(cstr); + int target = curCble + offset; + if (!(offset+"").equals(cstr) || + self.elems == null || + target < 0 || + target >= self.elems.length) + { i = -i; continue; } // fail + Layout.Element ce = self.elems[target]; + assert(ce.kind == EK_CBLE); + e.value = target; + e.body = new Layout.Element[]{ ce }; + // Is it a (recursive) backward call? + if (offset <= 0) { + // Yes. Mark both caller and callee backward. + e.flags |= EF_BACK; + ce.flags |= EF_BACK; + } + break; + case 'K': // reference_type: constant_ref + kind = EK_REF; + switch (layout.charAt(i++)) { + case 'I': e.refKind = Constants.CONSTANT_Integer; break; + case 'J': e.refKind = Constants.CONSTANT_Long; break; + case 'F': e.refKind = Constants.CONSTANT_Float; break; + case 'D': e.refKind = Constants.CONSTANT_Double; break; + case 'S': e.refKind = Constants.CONSTANT_String; break; + case 'Q': e.refKind = Constants.CONSTANT_FieldSpecific; break; + + // new in 1.7: + case 'M': e.refKind = Constants.CONSTANT_MethodHandle; break; + case 'T': e.refKind = Constants.CONSTANT_MethodType; break; + case 'L': e.refKind = Constants.CONSTANT_LoadableValue; break; + default: { i = -i; continue; } // fail + } + break; + case 'R': // schema_ref + kind = EK_REF; + switch (layout.charAt(i++)) { + case 'C': e.refKind = Constants.CONSTANT_Class; break; + case 'S': e.refKind = Constants.CONSTANT_Signature; break; + case 'D': e.refKind = Constants.CONSTANT_NameandType; break; + case 'F': e.refKind = Constants.CONSTANT_Fieldref; break; + case 'M': e.refKind = Constants.CONSTANT_Methodref; break; + case 'I': e.refKind = Constants.CONSTANT_InterfaceMethodref; break; + + case 'U': e.refKind = Constants.CONSTANT_Utf8; break; //utf8_ref + case 'Q': e.refKind = Constants.CONSTANT_All; break; //untyped_ref + + // new in 1.7: + case 'Y': e.refKind = Constants.CONSTANT_InvokeDynamic; break; + case 'B': e.refKind = Constants.CONSTANT_BootstrapMethod; break; + case 'N': e.refKind = Constants.CONSTANT_AnyMember; break; + + default: { i = -i; continue; } // fail + } + break; + default: { i = -i; continue; } // fail + } + + // further parsing of refs + if (kind == EK_REF) { + // reference: reference_type -><- ( 'N' )? tokenizeUInt + if (layout.charAt(i++) == 'N') { + e.flags |= EF_NULL; + i++; // move forward + } + --i; // reparse + i = tokenizeUInt(e, layout, i); + self.hasRefs = true; + } + + prevBCI = (kind == EK_BCI); + + // store the new element + e.kind = kind; + e.layout = layout.substring(start, i); + col.add(e); + } + } + static //private + String[] splitBodies(String layout) { + List bodies = new ArrayList<>(); + // Parse several independent layout bodies: "[foo][bar]...[baz]" + for (int i = 0; i < layout.length(); i++) { + if (layout.charAt(i++) != '[') + layout.charAt(-i); // throw error + int body; + i = skipBody(layout, body = i); + bodies.add(layout.substring(body, i)); + } + String[] res = new String[bodies.size()]; + bodies.toArray(res); + return res; + } + private static + int skipBody(String layout, int i) { + assert(layout.charAt(i-1) == '['); + if (layout.charAt(i) == ']') + // No empty bodies, please. + return -i; + // skip balanced [...[...]...] + for (int depth = 1; depth > 0; ) { + switch (layout.charAt(i++)) { + case '[': depth++; break; + case ']': depth--; break; + } + } + --i; // get before bracket + assert(layout.charAt(i) == ']'); + return i; // return closing bracket + } + private static + int tokenizeUInt(Layout.Element e, String layout, int i) { + switch (layout.charAt(i++)) { + case 'V': e.len = 0; break; + case 'B': e.len = 1; break; + case 'H': e.len = 2; break; + case 'I': e.len = 4; break; + default: return -i; + } + return i; + } + private static + int tokenizeSInt(Layout.Element e, String layout, int i) { + if (layout.charAt(i) == 'S') { + e.flags |= EF_SIGN; + ++i; + } + return tokenizeUInt(e, layout, i); + } + + private static + boolean isDigit(char c) { + return c >= '0' && c <= '9'; + } + + /** Find an occurrence of hyphen '-' between two numerals. */ + static //private + int findCaseDash(String layout, int fromIndex) { + if (fromIndex <= 0) fromIndex = 1; // minimum dash pos + int lastDash = layout.length() - 2; // maximum dash pos + for (;;) { + int dash = layout.indexOf('-', fromIndex); + if (dash < 0 || dash > lastDash) return -1; + if (isDigit(layout.charAt(dash-1))) { + char afterDash = layout.charAt(dash+1); + if (afterDash == '-' && dash+2 < layout.length()) + afterDash = layout.charAt(dash+2); + if (isDigit(afterDash)) { + // matched /[0-9]--?[0-9]/; return position of dash + return dash; + } + } + fromIndex = dash+1; + } + } + static + int parseIntBefore(String layout, int dash) { + int end = dash; + int beg = end; + while (beg > 0 && isDigit(layout.charAt(beg-1))) { + --beg; + } + if (beg == end) return Integer.parseInt("empty"); + // skip backward over a sign + if (beg >= 1 && layout.charAt(beg-1) == '-') --beg; + assert(beg == 0 || !isDigit(layout.charAt(beg-1))); + return Integer.parseInt(layout.substring(beg, end)); + } + static + int parseIntAfter(String layout, int dash) { + int beg = dash+1; + int end = beg; + int limit = layout.length(); + if (end < limit && layout.charAt(end) == '-') ++end; + while (end < limit && isDigit(layout.charAt(end))) { + ++end; + } + if (beg == end) return Integer.parseInt("empty"); + return Integer.parseInt(layout.substring(beg, end)); + } + /** For compatibility with 1.5 pack, expand 1-5 into 1,2,3,4,5. */ + static + String expandCaseDashNotation(String layout) { + int dash = findCaseDash(layout, 0); + if (dash < 0) return layout; // no dashes (the common case) + StringBuilder result = new StringBuilder(layout.length() * 3); + int sofar = 0; // how far have we processed the layout? + for (;;) { + // for each dash, collect everything up to the dash + result.append(layout, sofar, dash); + sofar = dash+1; // skip the dash + // then collect intermediate values + int value0 = parseIntBefore(layout, dash); + int value1 = parseIntAfter(layout, dash); + assert(value0 < value1); + result.append(","); // close off value0 numeral + for (int i = value0+1; i < value1; i++) { + result.append(i); + result.append(","); // close off i numeral + } + dash = findCaseDash(layout, sofar); + if (dash < 0) break; + } + result.append(layout, sofar, layout.length()); // collect the rest + return result.toString(); + } + static { + assert(expandCaseDashNotation("1-5").equals("1,2,3,4,5")); + assert(expandCaseDashNotation("-2--1").equals("-2,-1")); + assert(expandCaseDashNotation("-2-1").equals("-2,-1,0,1")); + assert(expandCaseDashNotation("-1-0").equals("-1,0")); + } + + // Parse attribute bytes, putting values into bands. Returns new pos. + // Used when reading a class file (local refs resolved with local cpMap). + // Also used for ad hoc scanning. + static + int parseUsing(Layout.Element[] elems, Holder holder, + byte[] bytes, int pos, int len, ValueStream out) { + int prevBCI = 0; + int prevRBCI = 0; + int end = pos + len; + int[] buf = { 0 }; // for calls to parseInt, holds 2nd result + for (int i = 0; i < elems.length; i++) { + Layout.Element e = elems[i]; + int bandIndex = e.bandIndex; + int value; + int BCI, RBCI; + switch (e.kind) { + case EK_INT: + pos = parseInt(e, bytes, pos, buf); + value = buf[0]; + out.putInt(bandIndex, value); + break; + case EK_BCI: // PH, POH + pos = parseInt(e, bytes, pos, buf); + BCI = buf[0]; + RBCI = out.encodeBCI(BCI); + if (!e.flagTest(EF_DELTA)) { + // PH: transmit R(bci), store bci + value = RBCI; + } else { + // POH: transmit D(R(bci)), store bci + value = RBCI - prevRBCI; + } + prevBCI = BCI; + prevRBCI = RBCI; + out.putInt(bandIndex, value); + break; + case EK_BCO: // OH + assert(e.flagTest(EF_DELTA)); + // OH: transmit D(R(bci)), store D(bci) + pos = parseInt(e, bytes, pos, buf); + BCI = prevBCI + buf[0]; + RBCI = out.encodeBCI(BCI); + value = RBCI - prevRBCI; + prevBCI = BCI; + prevRBCI = RBCI; + out.putInt(bandIndex, value); + break; + case EK_FLAG: + pos = parseInt(e, bytes, pos, buf); + value = buf[0]; + out.putInt(bandIndex, value); + break; + case EK_REPL: + pos = parseInt(e, bytes, pos, buf); + value = buf[0]; + out.putInt(bandIndex, value); + for (int j = 0; j < value; j++) { + pos = parseUsing(e.body, holder, bytes, pos, end-pos, out); + } + break; // already transmitted the scalar value + case EK_UN: + pos = parseInt(e, bytes, pos, buf); + value = buf[0]; + out.putInt(bandIndex, value); + Layout.Element ce = matchCase(e, value); + pos = parseUsing(ce.body, holder, bytes, pos, end-pos, out); + + break; // already transmitted the scalar value + case EK_CALL: + // Adjust band offset if it is a backward call. + assert(e.body.length == 1); + assert(e.body[0].kind == EK_CBLE); + if (e.flagTest(EF_BACK)) + out.noteBackCall(e.value); + pos = parseUsing(e.body[0].body, holder, bytes, pos, end-pos, out); + break; // no additional scalar value to transmit + case EK_REF: + pos = parseInt(e, bytes, pos, buf); + int localRef = buf[0]; + ConstantPool.Entry globalRef; + if (localRef == 0) { + globalRef = null; // N.B. global null reference is -1 + } else { + ConstantPool.Entry[] cpMap = holder.getCPMap(); + globalRef = (localRef >= 0 && localRef < cpMap.length + ? cpMap[localRef] + : null); + byte tag = e.refKind; + if (globalRef != null && tag == Constants.CONSTANT_Signature + && globalRef.getTag() == Constants.CONSTANT_Utf8) { + // Cf. ClassReader.readSignatureRef. + String typeName = globalRef.stringValue(); + globalRef = ConstantPool.getSignatureEntry(typeName); + } + String got = (globalRef == null + ? "invalid CP index" + : "type=" + ConstantPool.tagName(globalRef.tag)); + if (globalRef == null || !globalRef.tagMatches(tag)) { + throw new IllegalArgumentException( + "Bad constant, expected type=" + + ConstantPool.tagName(tag) + " got " + got); + } + } + out.putRef(bandIndex, globalRef); + break; + default: assert(false); + } + } + return pos; + } + + static + Layout.Element matchCase(Layout.Element e, int value) { + assert(e.kind == EK_UN); + int lastj = e.body.length-1; + for (int j = 0; j < lastj; j++) { + Layout.Element ce = e.body[j]; + assert(ce.kind == EK_CASE); + if (value == ce.value) + return ce; + } + return e.body[lastj]; + } + + private static + int parseInt(Layout.Element e, byte[] bytes, int pos, int[] buf) { + int value = 0; + int loBits = e.len * 8; + // Read in big-endian order: + for (int bitPos = loBits; (bitPos -= 8) >= 0; ) { + value += (bytes[pos++] & 0xFF) << bitPos; + } + if (loBits < 32 && e.flagTest(EF_SIGN)) { + // sign-extend subword value + int hiBits = 32 - loBits; + value = (value << hiBits) >> hiBits; + } + buf[0] = value; + return pos; + } + + // Format attribute bytes, drawing values from bands. + // Used when emptying attribute bands into a package model. + // (At that point CP refs. are not yet assigned indexes.) + static + void unparseUsing(Layout.Element[] elems, Object[] fixups, + ValueStream in, ByteArrayOutputStream out) { + int prevBCI = 0; + int prevRBCI = 0; + for (int i = 0; i < elems.length; i++) { + Layout.Element e = elems[i]; + int bandIndex = e.bandIndex; + int value; + int BCI, RBCI; // "RBCI" is R(BCI), BCI's coded representation + switch (e.kind) { + case EK_INT: + value = in.getInt(bandIndex); + unparseInt(e, value, out); + break; + case EK_BCI: // PH, POH + value = in.getInt(bandIndex); + if (!e.flagTest(EF_DELTA)) { + // PH: transmit R(bci), store bci + RBCI = value; + } else { + // POH: transmit D(R(bci)), store bci + RBCI = prevRBCI + value; + } + assert(prevBCI == in.decodeBCI(prevRBCI)); + BCI = in.decodeBCI(RBCI); + unparseInt(e, BCI, out); + prevBCI = BCI; + prevRBCI = RBCI; + break; + case EK_BCO: // OH + value = in.getInt(bandIndex); + assert(e.flagTest(EF_DELTA)); + // OH: transmit D(R(bci)), store D(bci) + assert(prevBCI == in.decodeBCI(prevRBCI)); + RBCI = prevRBCI + value; + BCI = in.decodeBCI(RBCI); + unparseInt(e, BCI - prevBCI, out); + prevBCI = BCI; + prevRBCI = RBCI; + break; + case EK_FLAG: + value = in.getInt(bandIndex); + unparseInt(e, value, out); + break; + case EK_REPL: + value = in.getInt(bandIndex); + unparseInt(e, value, out); + for (int j = 0; j < value; j++) { + unparseUsing(e.body, fixups, in, out); + } + break; + case EK_UN: + value = in.getInt(bandIndex); + unparseInt(e, value, out); + Layout.Element ce = matchCase(e, value); + unparseUsing(ce.body, fixups, in, out); + break; + case EK_CALL: + assert(e.body.length == 1); + assert(e.body[0].kind == EK_CBLE); + unparseUsing(e.body[0].body, fixups, in, out); + break; + case EK_REF: + ConstantPool.Entry globalRef = in.getRef(bandIndex); + int localRef; + if (globalRef != null) { + // It's a one-element array, really an lvalue. + fixups[0] = Fixups.addRefWithLoc(fixups[0], out.size(), globalRef); + localRef = 0; // placeholder for fixups + } else { + localRef = 0; // fixed null value + } + unparseInt(e, localRef, out); + break; + default: assert(false); continue; + } + } + } + + private static + void unparseInt(Layout.Element e, int value, ByteArrayOutputStream out) { + int loBits = e.len * 8; + if (loBits == 0) { + // It is not stored at all ('V' layout). + return; + } + if (loBits < 32) { + int hiBits = 32 - loBits; + int codedValue; + if (e.flagTest(EF_SIGN)) + codedValue = (value << hiBits) >> hiBits; + else + codedValue = (value << hiBits) >>> hiBits; + if (codedValue != value) + throw new InternalError("cannot code in "+e.len+" bytes: "+value); + } + // Write in big-endian order: + for (int bitPos = loBits; (bitPos -= 8) >= 0; ) { + out.write((byte)(value >>> bitPos)); + } + } + +/* + /// Testing. + public static void main(String av[]) { + int maxVal = 12; + int iters = 0; + boolean verbose; + int ap = 0; + while (ap < av.length) { + if (!av[ap].startsWith("-")) break; + if (av[ap].startsWith("-m")) + maxVal = Integer.parseInt(av[ap].substring(2)); + else if (av[ap].startsWith("-i")) + iters = Integer.parseInt(av[ap].substring(2)); + else + throw new RuntimeException("Bad option: "+av[ap]); + ap++; + } + verbose = (iters == 0); + if (iters <= 0) iters = 1; + if (ap == av.length) { + av = new String[] { + "HH", // ClassFile.version + "RUH", // SourceFile + "RCHRDNH", // EnclosingMethod + "KQH", // ConstantValue + "NH[RCH]", // Exceptions + "NH[PHH]", // LineNumberTable + "NH[PHOHRUHRSHH]", // LocalVariableTable + "NH[PHPOHIIH]", // CharacterRangeTable + "NH[PHHII]", // CoverageTable + "NH[RCHRCNHRUNHFH]", // InnerClasses + "NH[RMHNH[KLH]]", // BootstrapMethods + "HHNI[B]NH[PHPOHPOHRCNH]NH[RUHNI[B]]", // Code + "=AnnotationDefault", + // Like metadata, but with a compact tag set: + "[NH[(1)]]" + +"[NH[(1)]]" + +"[RSHNH[RUH(1)]]" + +"[TB(0,1,3)[KIH](2)[KDH](5)[KFH](4)[KJH](7)[RSH](8)[RSHRUH](9)[RUH](10)[(-1)](6)[NH[(0)]]()[]]", + "" + }; + ap = 0; + } + Utils.currentInstance.set(new PackerImpl()); + final int[][] counts = new int[2][3]; // int bci ref + final Entry[] cpMap = new Entry[maxVal+1]; + for (int i = 0; i < cpMap.length; i++) { + if (i == 0) continue; // 0 => null + cpMap[i] = ConstantPool.getLiteralEntry(new Integer(i)); + } + Package.Class cls = new Package().new Class(""); + cls.cpMap = cpMap; + class TestValueStream extends ValueStream { + java.util.Random rand = new java.util.Random(0); + ArrayList history = new ArrayList(); + int ckidx = 0; + int maxVal; + boolean verbose; + void reset() { history.clear(); ckidx = 0; } + public int getInt(int bandIndex) { + counts[0][0]++; + int value = rand.nextInt(maxVal+1); + history.add(new Integer(bandIndex)); + history.add(new Integer(value)); + return value; + } + public void putInt(int bandIndex, int token) { + counts[1][0]++; + if (verbose) + System.out.print(" "+bandIndex+":"+token); + // Make sure this put parallels a previous get: + int check0 = ((Integer)history.get(ckidx+0)).intValue(); + int check1 = ((Integer)history.get(ckidx+1)).intValue(); + if (check0 != bandIndex || check1 != token) { + if (!verbose) + System.out.println(history.subList(0, ckidx)); + System.out.println(" *** Should be "+check0+":"+check1); + throw new RuntimeException("Failed test!"); + } + ckidx += 2; + } + public Entry getRef(int bandIndex) { + counts[0][2]++; + int value = getInt(bandIndex); + if (value < 0 || value > maxVal) { + System.out.println(" *** Unexpected ref code "+value); + return ConstantPool.getLiteralEntry(new Integer(value)); + } + return cpMap[value]; + } + public void putRef(int bandIndex, Entry ref) { + counts[1][2]++; + if (ref == null) { + putInt(bandIndex, 0); + return; + } + Number refValue = null; + if (ref instanceof ConstantPool.NumberEntry) + refValue = ((ConstantPool.NumberEntry)ref).numberValue(); + int value; + if (!(refValue instanceof Integer)) { + System.out.println(" *** Unexpected ref "+ref); + value = -1; + } else { + value = ((Integer)refValue).intValue(); + } + putInt(bandIndex, value); + } + public int encodeBCI(int bci) { + counts[1][1]++; + // move LSB to MSB of low byte + int code = (bci >> 8) << 8; // keep high bits + code += (bci & 0xFE) >> 1; + code += (bci & 0x01) << 7; + return code ^ (8<<8); // mark it clearly as coded + } + public int decodeBCI(int bciCode) { + counts[0][1]++; + bciCode ^= (8<<8); // remove extra mark + int bci = (bciCode >> 8) << 8; // keep high bits + bci += (bciCode & 0x7F) << 1; + bci += (bciCode & 0x80) >> 7; + return bci; + } + } + TestValueStream tts = new TestValueStream(); + tts.maxVal = maxVal; + tts.verbose = verbose; + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + for (int i = 0; i < (1 << 30); i = (i + 1) * 5) { + int ei = tts.encodeBCI(i); + int di = tts.decodeBCI(ei); + if (di != i) System.out.println("i="+Integer.toHexString(i)+ + " ei="+Integer.toHexString(ei)+ + " di="+Integer.toHexString(di)); + } + while (iters-- > 0) { + for (int i = ap; i < av.length; i++) { + String layout = av[i]; + if (layout.startsWith("=")) { + String name = layout.substring(1); + for (Attribute a : standardDefs.values()) { + if (a.name().equals(name)) { + layout = a.layout().layout(); + break; + } + } + if (layout.startsWith("=")) { + System.out.println("Could not find "+name+" in "+standardDefs.values()); + } + } + Layout self = new Layout(0, "Foo", layout); + if (verbose) { + System.out.print("/"+layout+"/ => "); + System.out.println(Arrays.asList(self.elems)); + } + buf.reset(); + tts.reset(); + Object fixups = self.unparse(tts, buf); + byte[] bytes = buf.toByteArray(); + // Attach the references to the byte array. + Fixups.setBytes(fixups, bytes); + // Patch the references to their frozen values. + Fixups.finishRefs(fixups, bytes, new Index("test", cpMap)); + if (verbose) { + System.out.print(" bytes: {"); + for (int j = 0; j < bytes.length; j++) { + System.out.print(" "+bytes[j]); + } + System.out.println("}"); + } + if (verbose) { + System.out.print(" parse: {"); + } + self.parse(cls, bytes, 0, bytes.length, tts); + if (verbose) { + System.out.println("}"); + } + } + } + for (int j = 0; j <= 1; j++) { + System.out.print("values "+(j==0?"read":"written")+": {"); + for (int k = 0; k < counts[j].length; k++) { + System.out.print(" "+counts[j][k]); + } + System.out.println(" }"); + } + } +//*/ +} diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/BandStructure.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/BandStructure.java new file mode 100644 index 000000000..d099b6428 --- /dev/null +++ b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/BandStructure.java @@ -0,0 +1,2759 @@ +/* + * Copyright (c) 2001, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package net.fabricmc.shade.com.sun.java.util.jar.pack; + +import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.EOFException; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FilterInputStream; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import net.fabricmc.shade.java.util.jar.Pack200; + +import java.util.LinkedList; + +/** + * Define the structure and ordering of "bands" in a packed file. + * @author John Rose + */ +@SuppressWarnings({"removal"}) +abstract +class BandStructure { + static final int MAX_EFFORT = 9; + static final int MIN_EFFORT = 1; + static final int DEFAULT_EFFORT = 5; + + // Inherit options from Pack200: + PropMap p200 = Utils.currentPropMap(); + + int verbose = p200.getInteger(Utils.DEBUG_VERBOSE); + int effort = p200.getInteger(Pack200.Packer.EFFORT); + { if (effort == 0) effort = DEFAULT_EFFORT; } + boolean optDumpBands = p200.getBoolean(Utils.COM_PREFIX+"dump.bands"); + boolean optDebugBands = p200.getBoolean(Utils.COM_PREFIX+"debug.bands"); + + // Various heuristic options. + boolean optVaryCodings = !p200.getBoolean(Utils.COM_PREFIX+"no.vary.codings"); + boolean optBigStrings = !p200.getBoolean(Utils.COM_PREFIX+"no.big.strings"); + + protected abstract ConstantPool.Index getCPIndex(byte tag); + + // Local copy of highest class version. + private Package.Version highestClassVersion = null; + + /** Call this exactly once, early, to specify the archive major version. */ + public void initHighestClassVersion(Package.Version highestClassVersion) throws IOException { + if (this.highestClassVersion != null) { + throw new IOException( + "Highest class major version is already initialized to " + + this.highestClassVersion + "; new setting is " + highestClassVersion); + } + this.highestClassVersion = highestClassVersion; + adjustToClassVersion(); + } + + public Package.Version getHighestClassVersion() { + return highestClassVersion; + } + + private final boolean isReader = this instanceof PackageReader; + + protected BandStructure() {} + + static final Coding BYTE1 = Coding.of(1,256); + + static final Coding CHAR3 = Coding.of(3,128); + // Note: Tried sharper (3,16) with no post-zip benefit. + + // This is best used with BCI values: + static final Coding BCI5 = Coding.of(5,4); // mostly 1-byte offsets + static final Coding BRANCH5 = Coding.of(5,4,2); // mostly forward branches + + static final Coding UNSIGNED5 = Coding.of(5,64); + static final Coding UDELTA5 = UNSIGNED5.getDeltaCoding(); + // "sharp" (5,64) zips 0.4% better than "medium" (5,128) + // It zips 1.1% better than "flat" (5,192) + + static final Coding SIGNED5 = Coding.of(5,64,1); //sharp + static final Coding DELTA5 = SIGNED5.getDeltaCoding(); + // Note: Tried (5,128,2) and (5,192,2) with no benefit. + + static final Coding MDELTA5 = Coding.of(5,64,2).getDeltaCoding(); + + private static final Coding[] basicCodings = { + // Table of "Canonical BHSD Codings" from Pack200 spec. + null, // _meta_default + + // Fixed-length codings: + Coding.of(1,256,0), + Coding.of(1,256,1), + Coding.of(1,256,0).getDeltaCoding(), + Coding.of(1,256,1).getDeltaCoding(), + Coding.of(2,256,0), + Coding.of(2,256,1), + Coding.of(2,256,0).getDeltaCoding(), + Coding.of(2,256,1).getDeltaCoding(), + Coding.of(3,256,0), + Coding.of(3,256,1), + Coding.of(3,256,0).getDeltaCoding(), + Coding.of(3,256,1).getDeltaCoding(), + Coding.of(4,256,0), + Coding.of(4,256,1), + Coding.of(4,256,0).getDeltaCoding(), + Coding.of(4,256,1).getDeltaCoding(), + + // Full-range variable-length codings: + Coding.of(5, 4,0), + Coding.of(5, 4,1), + Coding.of(5, 4,2), + Coding.of(5, 16,0), + Coding.of(5, 16,1), + Coding.of(5, 16,2), + Coding.of(5, 32,0), + Coding.of(5, 32,1), + Coding.of(5, 32,2), + Coding.of(5, 64,0), + Coding.of(5, 64,1), + Coding.of(5, 64,2), + Coding.of(5,128,0), + Coding.of(5,128,1), + Coding.of(5,128,2), + + Coding.of(5, 4,0).getDeltaCoding(), + Coding.of(5, 4,1).getDeltaCoding(), + Coding.of(5, 4,2).getDeltaCoding(), + Coding.of(5, 16,0).getDeltaCoding(), + Coding.of(5, 16,1).getDeltaCoding(), + Coding.of(5, 16,2).getDeltaCoding(), + Coding.of(5, 32,0).getDeltaCoding(), + Coding.of(5, 32,1).getDeltaCoding(), + Coding.of(5, 32,2).getDeltaCoding(), + Coding.of(5, 64,0).getDeltaCoding(), + Coding.of(5, 64,1).getDeltaCoding(), + Coding.of(5, 64,2).getDeltaCoding(), + Coding.of(5,128,0).getDeltaCoding(), + Coding.of(5,128,1).getDeltaCoding(), + Coding.of(5,128,2).getDeltaCoding(), + + // Variable length subrange codings: + Coding.of(2,192,0), + Coding.of(2,224,0), + Coding.of(2,240,0), + Coding.of(2,248,0), + Coding.of(2,252,0), + + Coding.of(2, 8,0).getDeltaCoding(), + Coding.of(2, 8,1).getDeltaCoding(), + Coding.of(2, 16,0).getDeltaCoding(), + Coding.of(2, 16,1).getDeltaCoding(), + Coding.of(2, 32,0).getDeltaCoding(), + Coding.of(2, 32,1).getDeltaCoding(), + Coding.of(2, 64,0).getDeltaCoding(), + Coding.of(2, 64,1).getDeltaCoding(), + Coding.of(2,128,0).getDeltaCoding(), + Coding.of(2,128,1).getDeltaCoding(), + Coding.of(2,192,0).getDeltaCoding(), + Coding.of(2,192,1).getDeltaCoding(), + Coding.of(2,224,0).getDeltaCoding(), + Coding.of(2,224,1).getDeltaCoding(), + Coding.of(2,240,0).getDeltaCoding(), + Coding.of(2,240,1).getDeltaCoding(), + Coding.of(2,248,0).getDeltaCoding(), + Coding.of(2,248,1).getDeltaCoding(), + + Coding.of(3,192,0), + Coding.of(3,224,0), + Coding.of(3,240,0), + Coding.of(3,248,0), + Coding.of(3,252,0), + + Coding.of(3, 8,0).getDeltaCoding(), + Coding.of(3, 8,1).getDeltaCoding(), + Coding.of(3, 16,0).getDeltaCoding(), + Coding.of(3, 16,1).getDeltaCoding(), + Coding.of(3, 32,0).getDeltaCoding(), + Coding.of(3, 32,1).getDeltaCoding(), + Coding.of(3, 64,0).getDeltaCoding(), + Coding.of(3, 64,1).getDeltaCoding(), + Coding.of(3,128,0).getDeltaCoding(), + Coding.of(3,128,1).getDeltaCoding(), + Coding.of(3,192,0).getDeltaCoding(), + Coding.of(3,192,1).getDeltaCoding(), + Coding.of(3,224,0).getDeltaCoding(), + Coding.of(3,224,1).getDeltaCoding(), + Coding.of(3,240,0).getDeltaCoding(), + Coding.of(3,240,1).getDeltaCoding(), + Coding.of(3,248,0).getDeltaCoding(), + Coding.of(3,248,1).getDeltaCoding(), + + Coding.of(4,192,0), + Coding.of(4,224,0), + Coding.of(4,240,0), + Coding.of(4,248,0), + Coding.of(4,252,0), + + Coding.of(4, 8,0).getDeltaCoding(), + Coding.of(4, 8,1).getDeltaCoding(), + Coding.of(4, 16,0).getDeltaCoding(), + Coding.of(4, 16,1).getDeltaCoding(), + Coding.of(4, 32,0).getDeltaCoding(), + Coding.of(4, 32,1).getDeltaCoding(), + Coding.of(4, 64,0).getDeltaCoding(), + Coding.of(4, 64,1).getDeltaCoding(), + Coding.of(4,128,0).getDeltaCoding(), + Coding.of(4,128,1).getDeltaCoding(), + Coding.of(4,192,0).getDeltaCoding(), + Coding.of(4,192,1).getDeltaCoding(), + Coding.of(4,224,0).getDeltaCoding(), + Coding.of(4,224,1).getDeltaCoding(), + Coding.of(4,240,0).getDeltaCoding(), + Coding.of(4,240,1).getDeltaCoding(), + Coding.of(4,248,0).getDeltaCoding(), + Coding.of(4,248,1).getDeltaCoding(), + + null + }; + private static final Map basicCodingIndexes; + static { + assert(basicCodings[Constants._meta_default] == null); + assert(basicCodings[Constants._meta_canon_min] != null); + assert(basicCodings[Constants._meta_canon_max] != null); + Map map = new HashMap<>(); + for (int i = 0; i < basicCodings.length; i++) { + Coding c = basicCodings[i]; + if (c == null) continue; + assert(i >= Constants._meta_canon_min); + assert(i <= Constants._meta_canon_max); + map.put(c, i); + } + basicCodingIndexes = map; + } + public static Coding codingForIndex(int i) { + return i < basicCodings.length ? basicCodings[i] : null; + } + public static int indexOf(Coding c) { + Integer i = basicCodingIndexes.get(c); + if (i == null) return 0; + return i.intValue(); + } + public static Coding[] getBasicCodings() { + return basicCodings.clone(); + } + + protected byte[] bandHeaderBytes; // used for input only + protected int bandHeaderBytePos; // BHB read pointer, for input only + protected int bandHeaderBytePos0; // for debug + + protected CodingMethod getBandHeader(int XB, Coding regularCoding) { + CodingMethod[] res = {null}; + // push back XB onto the band header bytes + bandHeaderBytes[--bandHeaderBytePos] = (byte) XB; + bandHeaderBytePos0 = bandHeaderBytePos; + // scan forward through XB and any additional band header bytes + bandHeaderBytePos = parseMetaCoding(bandHeaderBytes, + bandHeaderBytePos, + regularCoding, + res); + return res[0]; + } + + public static int parseMetaCoding(byte[] bytes, int pos, Coding dflt, CodingMethod[] res) { + if ((bytes[pos] & 0xFF) == Constants._meta_default) { + res[0] = dflt; + return pos+1; + } + int pos2; + pos2 = Coding.parseMetaCoding(bytes, pos, dflt, res); + if (pos2 > pos) return pos2; + pos2 = PopulationCoding.parseMetaCoding(bytes, pos, dflt, res); + if (pos2 > pos) return pos2; + pos2 = AdaptiveCoding.parseMetaCoding(bytes, pos, dflt, res); + if (pos2 > pos) return pos2; + throw new RuntimeException("Bad meta-coding op "+(bytes[pos]&0xFF)); + } + + static final int SHORT_BAND_HEURISTIC = 100; + + public static final int NO_PHASE = 0; + + // package writing phases: + public static final int COLLECT_PHASE = 1; // collect data before write + public static final int FROZEN_PHASE = 3; // no longer collecting + public static final int WRITE_PHASE = 5; // ready to write bytes + + // package reading phases: + public static final int EXPECT_PHASE = 2; // gather expected counts + public static final int READ_PHASE = 4; // ready to read bytes + public static final int DISBURSE_PHASE = 6; // pass out data after read + + public static final int DONE_PHASE = 8; // done writing or reading + + static boolean phaseIsRead(int p) { + return (p % 2) == 0; + } + static int phaseCmp(int p0, int p1) { + assert((p0 % 2) == (p1 % 2) || (p0 % 8) == 0 || (p1 % 8) == 0); + return p0 - p1; + } + + /** The packed file is divided up into a number of segments. + * Most segments are typed as ValueBand, strongly-typed sequences + * of integer values, all interpreted in a single way. + * A few segments are ByteBands, which hetergeneous sequences + * of bytes. + * + * The two phases for writing a packed file are COLLECT and WRITE. + * 1. When writing a packed file, each band collects + * data in an ad-hoc order. + * 2. At the end, each band is assigned a coding scheme, + * and then all the bands are written in their global order. + * + * The three phases for reading a packed file are EXPECT, READ, + * and DISBURSE. + * 1. For each band, the expected number of integers is determined. + * 2. The data is actually read from the file into the band. + * 3. The band pays out its values as requested, in an ad hoc order. + * + * When the last phase of a band is done, it is marked so (DONE). + * Clearly, these phases must be properly ordered WRT each other. + */ + abstract class Band { + private int phase = NO_PHASE; + private final String name; + + private int valuesExpected; + + protected long outputSize = -1; // cache + + public final Coding regularCoding; + + public final int seqForDebug; + public int elementCountForDebug; + + + protected Band(String name, Coding regularCoding) { + this.name = name; + this.regularCoding = regularCoding; + this.seqForDebug = ++nextSeqForDebug; + if (verbose > 2) + Utils.log.fine("Band "+seqForDebug+" is "+name); + // caller must call init + } + + public Band init() { + // Cannot due this from the constructor, because constructor + // may wish to initialize some subclass variables. + // Set initial phase for reading or writing: + if (isReader) + readyToExpect(); + else + readyToCollect(); + return this; + } + + // common operations + boolean isReader() { return isReader; } + int phase() { return phase; } + String name() { return name; } + + /** Return -1 if data buffer not allocated, else max length. */ + public abstract int capacity(); + + /** Allocate data buffer to specified length. */ + protected abstract void setCapacity(int cap); + + /** Return current number of values in buffer, which must exist. */ + public abstract int length(); + + protected abstract int valuesRemainingForDebug(); + + public final int valuesExpected() { + return valuesExpected; + } + + /** Write out bytes, encoding the values. */ + public final void writeTo(OutputStream out) throws IOException { + assert(assertReadyToWriteTo(this, out)); + setPhase(WRITE_PHASE); + // subclasses continue by writing their contents to output + writeDataTo(out); + doneWriting(); + } + + abstract void chooseBandCodings() throws IOException; + + public final long outputSize() { + if (outputSize >= 0) { + long size = outputSize; + assert(size == computeOutputSize()); + return size; + } + return computeOutputSize(); + } + + protected abstract long computeOutputSize(); + + protected abstract void writeDataTo(OutputStream out) throws IOException; + + /** Expect a certain number of values. */ + void expectLength(int l) { + assert(assertPhase(this, EXPECT_PHASE)); + assert(valuesExpected == 0); // all at once + assert(l >= 0); + valuesExpected = l; + } + /** Expect more values. (Multiple calls accumulate.) */ + void expectMoreLength(int l) { + assert(assertPhase(this, EXPECT_PHASE)); + valuesExpected += l; + } + + + /// Phase change markers. + + private void readyToCollect() { // called implicitly by constructor + setCapacity(1); + setPhase(COLLECT_PHASE); + } + protected void doneWriting() { + assert(assertPhase(this, WRITE_PHASE)); + setPhase(DONE_PHASE); + } + private void readyToExpect() { // called implicitly by constructor + setPhase(EXPECT_PHASE); + } + /** Read in bytes, decoding the values. */ + public final void readFrom(InputStream in) throws IOException { + assert(assertReadyToReadFrom(this, in)); + setCapacity(valuesExpected()); + setPhase(READ_PHASE); + // subclasses continue by reading their contents from input: + readDataFrom(in); + readyToDisburse(); + } + protected abstract void readDataFrom(InputStream in) throws IOException; + protected void readyToDisburse() { + if (verbose > 1) Utils.log.fine("readyToDisburse "+this); + setPhase(DISBURSE_PHASE); + } + public void doneDisbursing() { + assert(assertPhase(this, DISBURSE_PHASE)); + setPhase(DONE_PHASE); + } + public final void doneWithUnusedBand() { + if (isReader) { + assert(assertPhase(this, EXPECT_PHASE)); + assert(valuesExpected() == 0); + // Fast forward: + setPhase(READ_PHASE); + setPhase(DISBURSE_PHASE); + setPhase(DONE_PHASE); + } else { + setPhase(FROZEN_PHASE); + } + } + + protected void setPhase(int newPhase) { + assert(assertPhaseChangeOK(this, phase, newPhase)); + this.phase = newPhase; + } + + protected int lengthForDebug = -1; // DEBUG ONLY + @Override + public String toString() { // DEBUG ONLY + int length = (lengthForDebug != -1 ? lengthForDebug : length()); + String str = name; + if (length != 0) + str += "[" + length + "]"; + if (elementCountForDebug != 0) + str += "(" + elementCountForDebug + ")"; + return str; + } + } + + class ValueBand extends Band { + private int[] values; // must be null in EXPECT phase + private int length; + private int valuesDisbursed; + + private CodingMethod bandCoding; + private byte[] metaCoding; + + protected ValueBand(String name, Coding regularCoding) { + super(name, regularCoding); + } + + @Override + public int capacity() { + return values == null ? -1 : values.length; + } + + /** Declare predicted or needed capacity. */ + @Override + protected void setCapacity(int cap) { + assert(length <= cap); + if (cap == -1) { values = null; return; } + values = realloc(values, cap); + } + + @Override + public int length() { + return length; + } + @Override + protected int valuesRemainingForDebug() { + return length - valuesDisbursed; + } + protected int valueAtForDebug(int i) { + return values[i]; + } + + void patchValue(int i, int value) { + // Only one use for this. + assert(this == archive_header_S); + assert(i == AH_ARCHIVE_SIZE_HI || i == AH_ARCHIVE_SIZE_LO); + assert(i < length); // must have already output a dummy + values[i] = value; + outputSize = -1; // decache + } + + protected void initializeValues(int[] values) { + assert(assertCanChangeLength(this)); + assert(length == 0); + this.values = values; + this.length = values.length; + } + + /** Collect one value, or store one decoded value. */ + protected void addValue(int x) { + assert(assertCanChangeLength(this)); + if (length == values.length) + setCapacity(length < 1000 ? length * 10 : length * 2); + values[length++] = x; + } + + private boolean canVaryCoding() { + if (!optVaryCodings) return false; + if (length == 0) return false; + // Can't read band_headers w/o the archive header: + if (this == archive_header_0) return false; + if (this == archive_header_S) return false; + if (this == archive_header_1) return false; + // BYTE1 bands can't vary codings, but the others can. + // All that's needed for the initial escape is at least + // 256 negative values or more than 256 non-negative values + return (regularCoding.min() <= -256 || regularCoding.max() >= 256); + } + + private boolean shouldVaryCoding() { + assert(canVaryCoding()); + if (effort < MAX_EFFORT && length < SHORT_BAND_HEURISTIC) + return false; + return true; + } + + @Override + protected void chooseBandCodings() throws IOException { + boolean canVary = canVaryCoding(); + if (!canVary || !shouldVaryCoding()) { + if (regularCoding.canRepresent(values, 0, length)) { + bandCoding = regularCoding; + } else { + assert(canVary); + if (verbose > 1) + Utils.log.fine("regular coding fails in band "+name()); + bandCoding = UNSIGNED5; + } + outputSize = -1; + } else { + int[] sizes = {0,0}; + bandCoding = chooseCoding(values, 0, length, + regularCoding, name(), + sizes); + outputSize = sizes[CodingChooser.BYTE_SIZE]; + if (outputSize == 0) // CodingChooser failed to size it. + outputSize = -1; + } + + // Compute and save the meta-coding bytes also. + if (bandCoding != regularCoding) { + metaCoding = bandCoding.getMetaCoding(regularCoding); + if (verbose > 1) { + Utils.log.fine("alternate coding "+this+" "+bandCoding); + } + } else if (canVary && + decodeEscapeValue(values[0], regularCoding) >= 0) { + // Need an explicit default. + metaCoding = defaultMetaCoding; + } else { + // Common case: Zero bytes of meta coding. + metaCoding = noMetaCoding; + } + if (metaCoding.length > 0 + && (verbose > 2 || verbose > 1 && metaCoding.length > 1)) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < metaCoding.length; i++) { + if (i == 1) sb.append(" /"); + sb.append(" ").append(metaCoding[i] & 0xFF); + } + Utils.log.fine(" meta-coding "+sb); + } + + assert((outputSize < 0) || + !(bandCoding instanceof Coding) || + (outputSize == ((Coding)bandCoding) + .getLength(values, 0, length))) + : (bandCoding+" : "+ + outputSize+" != "+ + ((Coding)bandCoding).getLength(values, 0, length) + +" ?= "+getCodingChooser().computeByteSize(bandCoding,values,0,length) + ); + + // Compute outputSize of the escape value X, if any. + if (metaCoding.length > 0) { + // First byte XB of meta-coding is treated specially, + // but any other bytes go into the band headers band. + // This must be done before any other output happens. + if (outputSize >= 0) + outputSize += computeEscapeSize(); // good cache + // Other bytes go into band_headers. + for (int i = 1; i < metaCoding.length; i++) { + band_headers.putByte(metaCoding[i] & 0xFF); + } + } + } + + @Override + protected long computeOutputSize() { + outputSize = getCodingChooser().computeByteSize(bandCoding, + values, 0, length); + assert(outputSize < Integer.MAX_VALUE); + outputSize += computeEscapeSize(); + return outputSize; + } + + protected int computeEscapeSize() { + if (metaCoding.length == 0) return 0; + int XB = metaCoding[0] & 0xFF; + int X = encodeEscapeValue(XB, regularCoding); + return regularCoding.setD(0).getLength(X); + } + + @Override + protected void writeDataTo(OutputStream out) throws IOException { + if (length == 0) return; // nothing to write + long len0 = 0; + if (out == outputCounter) { + len0 = outputCounter.getCount(); + } + if (metaCoding.length > 0) { + int XB = metaCoding[0] & 0xFF; + // We need an explicit band header, either because + // there is a non-default coding method, or because + // the first value would be parsed as an escape value. + int X = encodeEscapeValue(XB, regularCoding); + //System.out.println("X="+X+" XB="+XB+" in "+this); + regularCoding.setD(0).writeTo(out, X); + } + bandCoding.writeArrayTo(out, values, 0, length); + if (out == outputCounter) { + assert(outputSize == outputCounter.getCount() - len0) + : (outputSize+" != "+outputCounter.getCount()+"-"+len0); + } + if (optDumpBands) dumpBand(); + } + + @Override + protected void readDataFrom(InputStream in) throws IOException { + length = valuesExpected(); + if (length == 0) return; // nothing to read + if (verbose > 1) + Utils.log.fine("Reading band "+this); + if (!canVaryCoding()) { + bandCoding = regularCoding; + metaCoding = noMetaCoding; + } else { + assert(in.markSupported()); // input must be buffered + in.mark(Coding.B_MAX); + int X = regularCoding.setD(0).readFrom(in); + int XB = decodeEscapeValue(X, regularCoding); + if (XB < 0) { + // Do not consume this value. No alternate coding. + in.reset(); + bandCoding = regularCoding; + metaCoding = noMetaCoding; + } else if (XB == Constants._meta_default) { + bandCoding = regularCoding; + metaCoding = defaultMetaCoding; + } else { + if (verbose > 2) + Utils.log.fine("found X="+X+" => XB="+XB); + bandCoding = getBandHeader(XB, regularCoding); + // This is really used only by dumpBands. + int p0 = bandHeaderBytePos0; + int p1 = bandHeaderBytePos; + metaCoding = new byte[p1-p0]; + System.arraycopy(bandHeaderBytes, p0, + metaCoding, 0, metaCoding.length); + } + } + if (bandCoding != regularCoding) { + if (verbose > 1) + Utils.log.fine(name()+": irregular coding "+bandCoding); + } + bandCoding.readArrayFrom(in, values, 0, length); + if (optDumpBands) dumpBand(); + } + + @Override + public void doneDisbursing() { + super.doneDisbursing(); + values = null; // for GC + } + + private void dumpBand() throws IOException { + assert(optDumpBands); + try (PrintStream ps = new PrintStream(getDumpStream(this, ".txt"))) { + String irr = (bandCoding == regularCoding) ? "" : " irregular"; + ps.print("# length="+length+ + " size="+outputSize()+ + irr+" coding="+bandCoding); + if (metaCoding != noMetaCoding) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < metaCoding.length; i++) { + if (i == 1) sb.append(" /"); + sb.append(" ").append(metaCoding[i] & 0xFF); + } + ps.print(" //header: "+sb); + } + printArrayTo(ps, values, 0, length); + } + try (OutputStream ds = getDumpStream(this, ".bnd")) { + bandCoding.writeArrayTo(ds, values, 0, length); + } + } + + /** Disburse one value. */ + protected int getValue() { + assert(phase() == DISBURSE_PHASE); + // when debugging return a zero if lengths are zero + if (optDebugBands && length == 0 && valuesDisbursed == length) + return 0; + assert(valuesDisbursed <= length); + return values[valuesDisbursed++]; + } + + /** Reset for another pass over the same value set. */ + public void resetForSecondPass() { + assert(phase() == DISBURSE_PHASE); + assert(valuesDisbursed == length()); // 1st pass is complete + valuesDisbursed = 0; + } + } + + class ByteBand extends Band { + private ByteArrayOutputStream bytes; // input buffer + private ByteArrayOutputStream bytesForDump; + private InputStream in; + + public ByteBand(String name) { + super(name, BYTE1); + } + + @Override + public int capacity() { + return bytes == null ? -1 : Integer.MAX_VALUE; + } + @Override + protected void setCapacity(int cap) { + assert(bytes == null); // do this just once + bytes = new ByteArrayOutputStream(cap); + } + public void destroy() { + lengthForDebug = length(); + bytes = null; + } + + @Override + public int length() { + return bytes == null ? -1 : bytes.size(); + } + public void reset() { + bytes.reset(); + } + @Override + protected int valuesRemainingForDebug() { + return (bytes == null) ? -1 : ((ByteArrayInputStream)in).available(); + } + + @Override + protected void chooseBandCodings() throws IOException { + // No-op. + assert(decodeEscapeValue(regularCoding.min(), regularCoding) < 0); + assert(decodeEscapeValue(regularCoding.max(), regularCoding) < 0); + } + + @Override + protected long computeOutputSize() { + // do not cache + return bytes.size(); + } + + @Override + public void writeDataTo(OutputStream out) throws IOException { + if (length() == 0) return; + bytes.writeTo(out); + if (optDumpBands) dumpBand(); + destroy(); // done with the bits! + } + + private void dumpBand() throws IOException { + assert(optDumpBands); + try (OutputStream ds = getDumpStream(this, ".bnd")) { + if (bytesForDump != null) + bytesForDump.writeTo(ds); + else + bytes.writeTo(ds); + } + } + + @Override + public void readDataFrom(InputStream in) throws IOException { + int vex = valuesExpected(); + if (vex == 0) return; + if (verbose > 1) { + lengthForDebug = vex; + Utils.log.fine("Reading band "+this); + lengthForDebug = -1; + } + byte[] buf = new byte[Math.min(vex, 1<<14)]; + while (vex > 0) { + int nr = in.read(buf, 0, Math.min(vex, buf.length)); + if (nr < 0) throw new EOFException(); + bytes.write(buf, 0, nr); + vex -= nr; + } + if (optDumpBands) dumpBand(); + } + + @Override + public void readyToDisburse() { + in = new ByteArrayInputStream(bytes.toByteArray()); + super.readyToDisburse(); + } + + @Override + public void doneDisbursing() { + super.doneDisbursing(); + if (optDumpBands + && bytesForDump != null && bytesForDump.size() > 0) { + try { + dumpBand(); + } catch (IOException ee) { + throw new RuntimeException(ee); + } + } + in = null; // GC + bytes = null; // GC + bytesForDump = null; // GC + } + + // alternative to readFrom: + public void setInputStreamFrom(InputStream in) throws IOException { + assert(bytes == null); + assert(assertReadyToReadFrom(this, in)); + setPhase(READ_PHASE); + this.in = in; + if (optDumpBands) { + // Tap the stream. + bytesForDump = new ByteArrayOutputStream(); + this.in = new FilterInputStream(in) { + @Override + public int read() throws IOException { + int ch = in.read(); + if (ch >= 0) bytesForDump.write(ch); + return ch; + } + @Override + public int read(byte b[], int off, int len) throws IOException { + int nr = in.read(b, off, len); + if (nr >= 0) bytesForDump.write(b, off, nr); + return nr; + } + }; + } + super.readyToDisburse(); + } + + public OutputStream collectorStream() { + assert(phase() == COLLECT_PHASE); + assert(bytes != null); + return bytes; + } + + public InputStream getInputStream() { + assert(phase() == DISBURSE_PHASE); + assert(in != null); + return in; + } + public int getByte() throws IOException { + int b = getInputStream().read(); + if (b < 0) throw new EOFException(); + return b; + } + public void putByte(int b) throws IOException { + assert(b == (b & 0xFF)); + collectorStream().write(b); + } + @Override + public String toString() { + return "byte "+super.toString(); + } + } + + class IntBand extends ValueBand { + // The usual coding for bands is 7bit/5byte/delta. + public IntBand(String name, Coding regularCoding) { + super(name, regularCoding); + } + + public void putInt(int x) { + assert(phase() == COLLECT_PHASE); + addValue(x); + } + + public int getInt() { + return getValue(); + } + /** Return the sum of all values in this band. */ + public int getIntTotal() { + assert(phase() == DISBURSE_PHASE); + // assert that this is the whole pass; no other reads allowed + assert(valuesRemainingForDebug() == length()); + int total = 0; + for (int k = length(); k > 0; k--) { + total += getInt(); + } + resetForSecondPass(); + return total; + } + /** Return the occurrence count of a specific value in this band. */ + public int getIntCount(int value) { + assert(phase() == DISBURSE_PHASE); + // assert that this is the whole pass; no other reads allowed + assert(valuesRemainingForDebug() == length()); + int total = 0; + for (int k = length(); k > 0; k--) { + if (getInt() == value) { + total += 1; + } + } + resetForSecondPass(); + return total; + } + } + + static int getIntTotal(int[] values) { + int total = 0; + for (int i = 0; i < values.length; i++) { + total += values[i]; + } + return total; + } + + class CPRefBand extends ValueBand { + ConstantPool.Index index; + boolean nullOK; + + public CPRefBand(String name, Coding regularCoding, byte cpTag, boolean nullOK) { + super(name, regularCoding); + this.nullOK = nullOK; + if (cpTag != Constants.CONSTANT_None) + setBandIndex(this, cpTag); + } + public CPRefBand(String name, Coding regularCoding, byte cpTag) { + this(name, regularCoding, cpTag, false); + } + public CPRefBand(String name, Coding regularCoding, Object undef) { + this(name, regularCoding, Constants.CONSTANT_None, false); + } + + public void setIndex(ConstantPool.Index index) { + this.index = index; + } + + protected void readDataFrom(InputStream in) throws IOException { + super.readDataFrom(in); + assert(assertValidCPRefs(this)); + } + + /** Write a constant pool reference. */ + public void putRef(ConstantPool.Entry e) { + addValue(encodeRefOrNull(e, index)); + } + public void putRef(ConstantPool.Entry e, ConstantPool.Index index) { + assert(this.index == null); + addValue(encodeRefOrNull(e, index)); + } + public void putRef(ConstantPool.Entry e, byte cptag) { + putRef(e, getCPIndex(cptag)); + } + + public ConstantPool.Entry getRef() { + if (index == null) Utils.log.warning("No index for "+this); + assert(index != null); + return decodeRefOrNull(getValue(), index); + } + public ConstantPool.Entry getRef(ConstantPool.Index index) { + assert(this.index == null); + return decodeRefOrNull(getValue(), index); + } + public ConstantPool.Entry getRef(byte cptag) { + return getRef(getCPIndex(cptag)); + } + + private int encodeRefOrNull(ConstantPool.Entry e, ConstantPool.Index index) { + int nonNullCode; // NNC is the coding which assumes nulls are rare + if (e == null) { + nonNullCode = -1; // negative values are rare + } else { + nonNullCode = encodeRef(e, index); + } + // If nulls are expected, increment, to make -1 code turn to 0. + return (nullOK ? 1 : 0) + nonNullCode; + } + private ConstantPool.Entry decodeRefOrNull(int code, ConstantPool.Index index) { + // Inverse to encodeRefOrNull... + int nonNullCode = code - (nullOK ? 1 : 0); + if (nonNullCode == -1) { + return null; + } else { + return decodeRef(nonNullCode, index); + } + } + } + + // Bootstrap support for CPRefBands. These are needed to record + // intended CP indexes, before the CP has been created. + private final List allKQBands = new ArrayList<>(); + private List needPredefIndex = new ArrayList<>(); + + + int encodeRef(ConstantPool.Entry e, ConstantPool.Index ix) { + if (ix == null) + throw new RuntimeException("null index for " + e.stringValue()); + int coding = ix.indexOf(e); + if (verbose > 2) + Utils.log.fine("putRef "+coding+" => "+e); + return coding; + } + + ConstantPool.Entry decodeRef(int n, ConstantPool.Index ix) { + if (n < 0 || n >= ix.size()) + Utils.log.warning("decoding bad ref "+n+" in "+ix); + ConstantPool.Entry e = ix.getEntry(n); + if (verbose > 2) + Utils.log.fine("getRef "+n+" => "+e); + return e; + } + + private CodingChooser codingChooser; + protected CodingChooser getCodingChooser() { + if (codingChooser == null) { + codingChooser = new CodingChooser(effort, basicCodings); + if (codingChooser.stress != null + && this instanceof PackageWriter) { + // Twist the random state based on my first file. + // This sends each segment off in a different direction. + List classes = ((PackageWriter)this).pkg.classes; + if (!classes.isEmpty()) { + Package.Class cls = classes.get(0); + codingChooser.addStressSeed(cls.getName().hashCode()); + } + } + } + return codingChooser; + } + + public CodingMethod chooseCoding(int[] values, int start, int end, + Coding regular, String bandName, + int[] sizes) { + assert(optVaryCodings); + if (effort <= MIN_EFFORT) { + return regular; + } + CodingChooser cc = getCodingChooser(); + if (verbose > 1 || cc.verbose > 1) { + Utils.log.fine("--- chooseCoding "+bandName); + } + return cc.choose(values, start, end, regular, sizes); + } + + static final byte[] defaultMetaCoding = { Constants._meta_default }; + static final byte[] noMetaCoding = {}; + + // The first value in a band is always coded with the default coding D. + // If this first value X is an escape value, it actually represents the + // first (and perhaps only) byte of a meta-coding. + // + // If D.S != 0 and D includes the range [-256..-1], + // the escape values are in that range, + // and the first byte XB is -1-X. + // + // If D.S == 0 and D includes the range [(D.L)..(D.L)+255], + // the escape values are in that range, + // and XB is X-(D.L). + // + // This representation is designed so that a band header is unlikely + // to be confused with the initial value of a headerless band, + // and yet so that a band header is likely to occupy only a byte or two. + // + // Result is in [0..255] if XB was successfully extracted, else -1. + // See section "Coding Specifier Meta-Encoding" in the JSR 200 spec. + protected static int decodeEscapeValue(int X, Coding regularCoding) { + // The first value in a band is always coded with the default coding D. + // If this first value X is an escape value, it actually represents the + // first (and perhaps only) byte of a meta-coding. + // Result is in [0..255] if XB was successfully extracted, else -1. + if (regularCoding.B() == 1 || regularCoding.L() == 0) + return -1; // degenerate regular coding (BYTE1) + if (regularCoding.S() != 0) { + if (-256 <= X && X <= -1 && regularCoding.min() <= -256) { + int XB = -1-X; + assert(XB >= 0 && XB < 256); + return XB; + } + } else { + int L = regularCoding.L(); + if (L <= X && X <= L+255 && regularCoding.max() >= L+255) { + int XB = X-L; + assert(XB >= 0 && XB < 256); + return XB; + } + } + return -1; // negative value for failure + } + // Inverse to decodeEscapeValue(). + protected static int encodeEscapeValue(int XB, Coding regularCoding) { + assert(XB >= 0 && XB < 256); + assert(regularCoding.B() > 1 && regularCoding.L() > 0); + int X; + if (regularCoding.S() != 0) { + assert(regularCoding.min() <= -256); + X = -1-XB; + } else { + int L = regularCoding.L(); + assert(regularCoding.max() >= L+255); + X = XB+L; + } + assert(decodeEscapeValue(X, regularCoding) == XB) + : (regularCoding+" XB="+XB+" X="+X); + return X; + } + + static { + boolean checkXB = false; + assert(checkXB = true); + if (checkXB) { + for (int i = 0; i < basicCodings.length; i++) { + Coding D = basicCodings[i]; + if (D == null) continue; + if (D.B() == 1) continue; + if (D.L() == 0) continue; + for (int XB = 0; XB <= 255; XB++) { + // The following exercises decodeEscapeValue also: + encodeEscapeValue(XB, D); + } + } + } + } + + class MultiBand extends Band { + MultiBand(String name, Coding regularCoding) { + super(name, regularCoding); + } + + @Override + public Band init() { + super.init(); + // This is all just to keep the asserts happy: + setCapacity(0); + if (phase() == EXPECT_PHASE) { + // Fast forward: + setPhase(READ_PHASE); + setPhase(DISBURSE_PHASE); + } + return this; + } + + Band[] bands = new Band[10]; + int bandCount = 0; + + int size() { + return bandCount; + } + Band get(int i) { + assert(i < bandCount); + return bands[i]; + } + Band[] toArray() { + return (Band[]) realloc(bands, bandCount); + } + + void add(Band b) { + assert(bandCount == 0 || notePrevForAssert(b, bands[bandCount-1])); + if (bandCount == bands.length) { + bands = (Band[]) realloc(bands); + } + bands[bandCount++] = b; + } + + ByteBand newByteBand(String name) { + ByteBand b = new ByteBand(name); + b.init(); add(b); + return b; + } + IntBand newIntBand(String name) { + IntBand b = new IntBand(name, regularCoding); + b.init(); add(b); + return b; + } + IntBand newIntBand(String name, Coding regularCoding) { + IntBand b = new IntBand(name, regularCoding); + b.init(); add(b); + return b; + } + MultiBand newMultiBand(String name, Coding regularCoding) { + MultiBand b = new MultiBand(name, regularCoding); + b.init(); add(b); + return b; + } + CPRefBand newCPRefBand(String name, byte cpTag) { + CPRefBand b = new CPRefBand(name, regularCoding, cpTag); + b.init(); add(b); + return b; + } + CPRefBand newCPRefBand(String name, Coding regularCoding, + byte cpTag) { + CPRefBand b = new CPRefBand(name, regularCoding, cpTag); + b.init(); add(b); + return b; + } + CPRefBand newCPRefBand(String name, Coding regularCoding, + byte cpTag, boolean nullOK) { + CPRefBand b = new CPRefBand(name, regularCoding, cpTag, nullOK); + b.init(); add(b); + return b; + } + + int bandCount() { return bandCount; } + + private int cap = -1; + @Override + public int capacity() { return cap; } + @Override + public void setCapacity(int cap) { this.cap = cap; } + + @Override + public int length() { return 0; } + @Override + public int valuesRemainingForDebug() { return 0; } + + @Override + protected void chooseBandCodings() throws IOException { + // coding decision pass + for (int i = 0; i < bandCount; i++) { + Band b = bands[i]; + b.chooseBandCodings(); + } + } + + @Override + protected long computeOutputSize() { + // coding decision pass + long sum = 0; + for (int i = 0; i < bandCount; i++) { + Band b = bands[i]; + long bsize = b.outputSize(); + assert(bsize >= 0) : b; + sum += bsize; + } + // do not cache + return sum; + } + + @Override + protected void writeDataTo(OutputStream out) throws IOException { + long preCount = 0; + if (outputCounter != null) preCount = outputCounter.getCount(); + for (int i = 0; i < bandCount; i++) { + Band b = bands[i]; + b.writeTo(out); + if (outputCounter != null) { + long postCount = outputCounter.getCount(); + long len = postCount - preCount; + preCount = postCount; + if ((verbose > 0 && len > 0) || verbose > 1) { + Utils.log.info(" ...wrote "+len+" bytes from "+b); + } + } + } + } + + @Override + protected void readDataFrom(InputStream in) throws IOException { + assert(false); // not called? + for (int i = 0; i < bandCount; i++) { + Band b = bands[i]; + b.readFrom(in); + if ((verbose > 0 && b.length() > 0) || verbose > 1) { + Utils.log.info(" ...read "+b); + } + } + } + + @Override + public String toString() { + return "{"+bandCount()+" bands: "+super.toString()+"}"; + } + } + + /** + * An output stream which counts the number of bytes written. + */ + private static + class ByteCounter extends FilterOutputStream { + // (should go public under the name CountingOutputStream?) + + private long count; + + public ByteCounter(OutputStream out) { + super(out); + } + + public long getCount() { return count; } + public void setCount(long c) { count = c; } + + @Override + public void write(int b) throws IOException { + count++; + if (out != null) out.write(b); + } + @Override + public void write(byte b[], int off, int len) throws IOException { + count += len; + if (out != null) out.write(b, off, len); + } + @Override + public String toString() { + return String.valueOf(getCount()); + } + } + ByteCounter outputCounter; + + void writeAllBandsTo(OutputStream out) throws IOException { + // Wrap a byte-counter around the output stream. + outputCounter = new ByteCounter(out); + out = outputCounter; + all_bands.writeTo(out); + if (verbose > 0) { + long nbytes = outputCounter.getCount(); + Utils.log.info("Wrote total of "+nbytes+" bytes."); + assert(nbytes == archiveSize0+archiveSize1); + } + outputCounter = null; + } + + // random AO_XXX bits, decoded from the archive header + protected int archiveOptions; + + // archiveSize1 sizes most of the archive [archive_options..file_bits). + protected long archiveSize0; // size through archive_size_lo + protected long archiveSize1; // size reported in archive_header + protected int archiveNextCount; // reported in archive_header + + static final int AH_LENGTH_0 = 3; // archive_header_0 = {minver, majver, options} + static final int AH_LENGTH_MIN = 15; // observed in spec {header_0[3], cp_counts[8], class_counts[4]} + // Length contributions from optional archive size fields: + static final int AH_LENGTH_S = 2; // archive_header_S = optional {size_hi, size_lo} + static final int AH_ARCHIVE_SIZE_HI = 0; // offset in archive_header_S + static final int AH_ARCHIVE_SIZE_LO = 1; // offset in archive_header_S + // Length contributions from optional header fields: + static final int AH_FILE_HEADER_LEN = 5; // file_counts = {{size_hi, size_lo}, next, modtime, files} + static final int AH_SPECIAL_FORMAT_LEN = 2; // special_counts = {layouts, band_headers} + static final int AH_CP_NUMBER_LEN = 4; // cp_number_counts = {int, float, long, double} + static final int AH_CP_EXTRA_LEN = 4; // cp_attr_counts = {MH, MT, InDy, BSM} + + // Common structure of attribute band groups: + static final int AB_FLAGS_HI = 0; + static final int AB_FLAGS_LO = 1; + static final int AB_ATTR_COUNT = 2; + static final int AB_ATTR_INDEXES = 3; + static final int AB_ATTR_CALLS = 4; + + static IntBand getAttrBand(MultiBand xxx_attr_bands, int which) { + IntBand b = (IntBand) xxx_attr_bands.get(which); + switch (which) { + case AB_FLAGS_HI: + assert(b.name().endsWith("_flags_hi")); break; + case AB_FLAGS_LO: + assert(b.name().endsWith("_flags_lo")); break; + case AB_ATTR_COUNT: + assert(b.name().endsWith("_attr_count")); break; + case AB_ATTR_INDEXES: + assert(b.name().endsWith("_attr_indexes")); break; + case AB_ATTR_CALLS: + assert(b.name().endsWith("_attr_calls")); break; + default: + assert(false); break; + } + return b; + } + + private static final boolean NULL_IS_OK = true; + + MultiBand all_bands = (MultiBand) new MultiBand("(package)", UNSIGNED5).init(); + + // file header (various random bytes) + ByteBand archive_magic = all_bands.newByteBand("archive_magic"); + IntBand archive_header_0 = all_bands.newIntBand("archive_header_0", UNSIGNED5); + IntBand archive_header_S = all_bands.newIntBand("archive_header_S", UNSIGNED5); + IntBand archive_header_1 = all_bands.newIntBand("archive_header_1", UNSIGNED5); + ByteBand band_headers = all_bands.newByteBand("band_headers"); + + // constant pool contents + MultiBand cp_bands = all_bands.newMultiBand("(constant_pool)", DELTA5); + IntBand cp_Utf8_prefix = cp_bands.newIntBand("cp_Utf8_prefix"); + IntBand cp_Utf8_suffix = cp_bands.newIntBand("cp_Utf8_suffix", UNSIGNED5); + IntBand cp_Utf8_chars = cp_bands.newIntBand("cp_Utf8_chars", CHAR3); + IntBand cp_Utf8_big_suffix = cp_bands.newIntBand("cp_Utf8_big_suffix"); + MultiBand cp_Utf8_big_chars = cp_bands.newMultiBand("(cp_Utf8_big_chars)", DELTA5); + IntBand cp_Int = cp_bands.newIntBand("cp_Int", UDELTA5); + IntBand cp_Float = cp_bands.newIntBand("cp_Float", UDELTA5); + IntBand cp_Long_hi = cp_bands.newIntBand("cp_Long_hi", UDELTA5); + IntBand cp_Long_lo = cp_bands.newIntBand("cp_Long_lo"); + IntBand cp_Double_hi = cp_bands.newIntBand("cp_Double_hi", UDELTA5); + IntBand cp_Double_lo = cp_bands.newIntBand("cp_Double_lo"); + CPRefBand cp_String = cp_bands.newCPRefBand("cp_String", UDELTA5, Constants.CONSTANT_Utf8); + CPRefBand cp_Class = cp_bands.newCPRefBand("cp_Class", UDELTA5, Constants.CONSTANT_Utf8); + CPRefBand cp_Signature_form = cp_bands.newCPRefBand("cp_Signature_form", Constants.CONSTANT_Utf8); + CPRefBand cp_Signature_classes = cp_bands.newCPRefBand("cp_Signature_classes", UDELTA5, Constants.CONSTANT_Class); + CPRefBand cp_Descr_name = cp_bands.newCPRefBand("cp_Descr_name", Constants.CONSTANT_Utf8); + CPRefBand cp_Descr_type = cp_bands.newCPRefBand("cp_Descr_type", UDELTA5, Constants.CONSTANT_Signature); + CPRefBand cp_Field_class = cp_bands.newCPRefBand("cp_Field_class", Constants.CONSTANT_Class); + CPRefBand cp_Field_desc = cp_bands.newCPRefBand("cp_Field_desc", UDELTA5, Constants.CONSTANT_NameandType); + CPRefBand cp_Method_class = cp_bands.newCPRefBand("cp_Method_class", Constants.CONSTANT_Class); + CPRefBand cp_Method_desc = cp_bands.newCPRefBand("cp_Method_desc", UDELTA5, Constants.CONSTANT_NameandType); + CPRefBand cp_Imethod_class = cp_bands.newCPRefBand("cp_Imethod_class", Constants.CONSTANT_Class); + CPRefBand cp_Imethod_desc = cp_bands.newCPRefBand("cp_Imethod_desc", UDELTA5, Constants.CONSTANT_NameandType); + IntBand cp_MethodHandle_refkind = cp_bands.newIntBand("cp_MethodHandle_refkind", DELTA5); + CPRefBand cp_MethodHandle_member = cp_bands.newCPRefBand("cp_MethodHandle_member", UDELTA5, Constants.CONSTANT_AnyMember); + CPRefBand cp_MethodType = cp_bands.newCPRefBand("cp_MethodType", UDELTA5, Constants.CONSTANT_Signature); + CPRefBand cp_BootstrapMethod_ref = cp_bands.newCPRefBand("cp_BootstrapMethod_ref", DELTA5, Constants.CONSTANT_MethodHandle); + IntBand cp_BootstrapMethod_arg_count = cp_bands.newIntBand("cp_BootstrapMethod_arg_count", UDELTA5); + CPRefBand cp_BootstrapMethod_arg = cp_bands.newCPRefBand("cp_BootstrapMethod_arg", DELTA5, Constants.CONSTANT_LoadableValue); + CPRefBand cp_InvokeDynamic_spec = cp_bands.newCPRefBand("cp_InvokeDynamic_spec", DELTA5, Constants.CONSTANT_BootstrapMethod); + CPRefBand cp_InvokeDynamic_desc = cp_bands.newCPRefBand("cp_InvokeDynamic_desc", UDELTA5, Constants.CONSTANT_NameandType); + + // bands for carrying attribute definitions: + MultiBand attr_definition_bands = all_bands.newMultiBand("(attr_definition_bands)", UNSIGNED5); + ByteBand attr_definition_headers = attr_definition_bands.newByteBand("attr_definition_headers"); + CPRefBand attr_definition_name = attr_definition_bands.newCPRefBand("attr_definition_name", Constants.CONSTANT_Utf8); + CPRefBand attr_definition_layout = attr_definition_bands.newCPRefBand("attr_definition_layout", Constants.CONSTANT_Utf8); + + // bands for hardwired InnerClasses attribute (shared across the package) + MultiBand ic_bands = all_bands.newMultiBand("(ic_bands)", DELTA5); + CPRefBand ic_this_class = ic_bands.newCPRefBand("ic_this_class", UDELTA5, Constants.CONSTANT_Class); + IntBand ic_flags = ic_bands.newIntBand("ic_flags", UNSIGNED5); + // These bands contain data only where flags sets ACC_IC_LONG_FORM: + CPRefBand ic_outer_class = ic_bands.newCPRefBand("ic_outer_class", DELTA5, Constants.CONSTANT_Class, NULL_IS_OK); + CPRefBand ic_name = ic_bands.newCPRefBand("ic_name", DELTA5, Constants.CONSTANT_Utf8, NULL_IS_OK); + + // bands for carrying class schema information: + MultiBand class_bands = all_bands.newMultiBand("(class_bands)", DELTA5); + CPRefBand class_this = class_bands.newCPRefBand("class_this", Constants.CONSTANT_Class); + CPRefBand class_super = class_bands.newCPRefBand("class_super", Constants.CONSTANT_Class); + IntBand class_interface_count = class_bands.newIntBand("class_interface_count"); + CPRefBand class_interface = class_bands.newCPRefBand("class_interface", Constants.CONSTANT_Class); + + // bands for class members + IntBand class_field_count = class_bands.newIntBand("class_field_count"); + IntBand class_method_count = class_bands.newIntBand("class_method_count"); + + CPRefBand field_descr = class_bands.newCPRefBand("field_descr", Constants.CONSTANT_NameandType); + MultiBand field_attr_bands = class_bands.newMultiBand("(field_attr_bands)", UNSIGNED5); + IntBand field_flags_hi = field_attr_bands.newIntBand("field_flags_hi"); + IntBand field_flags_lo = field_attr_bands.newIntBand("field_flags_lo"); + IntBand field_attr_count = field_attr_bands.newIntBand("field_attr_count"); + IntBand field_attr_indexes = field_attr_bands.newIntBand("field_attr_indexes"); + IntBand field_attr_calls = field_attr_bands.newIntBand("field_attr_calls"); + + // bands for predefined field attributes + CPRefBand field_ConstantValue_KQ = field_attr_bands.newCPRefBand("field_ConstantValue_KQ", Constants.CONSTANT_FieldSpecific); + CPRefBand field_Signature_RS = field_attr_bands.newCPRefBand("field_Signature_RS", Constants.CONSTANT_Signature); + MultiBand field_metadata_bands = field_attr_bands.newMultiBand("(field_metadata_bands)", UNSIGNED5); + MultiBand field_type_metadata_bands = field_attr_bands.newMultiBand("(field_type_metadata_bands)", UNSIGNED5); + + CPRefBand method_descr = class_bands.newCPRefBand("method_descr", MDELTA5, Constants.CONSTANT_NameandType); + MultiBand method_attr_bands = class_bands.newMultiBand("(method_attr_bands)", UNSIGNED5); + IntBand method_flags_hi = method_attr_bands.newIntBand("method_flags_hi"); + IntBand method_flags_lo = method_attr_bands.newIntBand("method_flags_lo"); + IntBand method_attr_count = method_attr_bands.newIntBand("method_attr_count"); + IntBand method_attr_indexes = method_attr_bands.newIntBand("method_attr_indexes"); + IntBand method_attr_calls = method_attr_bands.newIntBand("method_attr_calls"); + // band for predefined method attributes + IntBand method_Exceptions_N = method_attr_bands.newIntBand("method_Exceptions_N"); + CPRefBand method_Exceptions_RC = method_attr_bands.newCPRefBand("method_Exceptions_RC", Constants.CONSTANT_Class); + CPRefBand method_Signature_RS = method_attr_bands.newCPRefBand("method_Signature_RS", Constants.CONSTANT_Signature); + MultiBand method_metadata_bands = method_attr_bands.newMultiBand("(method_metadata_bands)", UNSIGNED5); + // band for predefine method parameters + IntBand method_MethodParameters_NB = method_attr_bands.newIntBand("method_MethodParameters_NB", BYTE1); + CPRefBand method_MethodParameters_name_RUN = method_attr_bands.newCPRefBand("method_MethodParameters_name_RUN", UNSIGNED5, Constants.CONSTANT_Utf8, NULL_IS_OK); + IntBand method_MethodParameters_flag_FH = method_attr_bands.newIntBand("method_MethodParameters_flag_FH"); + MultiBand method_type_metadata_bands = method_attr_bands.newMultiBand("(method_type_metadata_bands)", UNSIGNED5); + + MultiBand class_attr_bands = class_bands.newMultiBand("(class_attr_bands)", UNSIGNED5); + IntBand class_flags_hi = class_attr_bands.newIntBand("class_flags_hi"); + IntBand class_flags_lo = class_attr_bands.newIntBand("class_flags_lo"); + IntBand class_attr_count = class_attr_bands.newIntBand("class_attr_count"); + IntBand class_attr_indexes = class_attr_bands.newIntBand("class_attr_indexes"); + IntBand class_attr_calls = class_attr_bands.newIntBand("class_attr_calls"); + // band for predefined SourceFile and other class attributes + CPRefBand class_SourceFile_RUN = class_attr_bands.newCPRefBand("class_SourceFile_RUN", UNSIGNED5, Constants.CONSTANT_Utf8, NULL_IS_OK); + CPRefBand class_EnclosingMethod_RC = class_attr_bands.newCPRefBand("class_EnclosingMethod_RC", Constants.CONSTANT_Class); + CPRefBand class_EnclosingMethod_RDN = class_attr_bands.newCPRefBand("class_EnclosingMethod_RDN", UNSIGNED5, Constants.CONSTANT_NameandType, NULL_IS_OK); + CPRefBand class_Signature_RS = class_attr_bands.newCPRefBand("class_Signature_RS", Constants.CONSTANT_Signature); + MultiBand class_metadata_bands = class_attr_bands.newMultiBand("(class_metadata_bands)", UNSIGNED5); + IntBand class_InnerClasses_N = class_attr_bands.newIntBand("class_InnerClasses_N"); + CPRefBand class_InnerClasses_RC = class_attr_bands.newCPRefBand("class_InnerClasses_RC", Constants.CONSTANT_Class); + IntBand class_InnerClasses_F = class_attr_bands.newIntBand("class_InnerClasses_F"); + CPRefBand class_InnerClasses_outer_RCN = class_attr_bands.newCPRefBand("class_InnerClasses_outer_RCN", UNSIGNED5, Constants.CONSTANT_Class, NULL_IS_OK); + CPRefBand class_InnerClasses_name_RUN = class_attr_bands.newCPRefBand("class_InnerClasses_name_RUN", UNSIGNED5, Constants.CONSTANT_Utf8, NULL_IS_OK); + IntBand class_ClassFile_version_minor_H = class_attr_bands.newIntBand("class_ClassFile_version_minor_H"); + IntBand class_ClassFile_version_major_H = class_attr_bands.newIntBand("class_ClassFile_version_major_H"); + MultiBand class_type_metadata_bands = class_attr_bands.newMultiBand("(class_type_metadata_bands)", UNSIGNED5); + + MultiBand code_bands = class_bands.newMultiBand("(code_bands)", UNSIGNED5); + ByteBand code_headers = code_bands.newByteBand("code_headers"); //BYTE1 + IntBand code_max_stack = code_bands.newIntBand("code_max_stack", UNSIGNED5); + IntBand code_max_na_locals = code_bands.newIntBand("code_max_na_locals", UNSIGNED5); + IntBand code_handler_count = code_bands.newIntBand("code_handler_count", UNSIGNED5); + IntBand code_handler_start_P = code_bands.newIntBand("code_handler_start_P", BCI5); + IntBand code_handler_end_PO = code_bands.newIntBand("code_handler_end_PO", BRANCH5); + IntBand code_handler_catch_PO = code_bands.newIntBand("code_handler_catch_PO", BRANCH5); + CPRefBand code_handler_class_RCN = code_bands.newCPRefBand("code_handler_class_RCN", UNSIGNED5, Constants.CONSTANT_Class, NULL_IS_OK); + + MultiBand code_attr_bands = class_bands.newMultiBand("(code_attr_bands)", UNSIGNED5); + IntBand code_flags_hi = code_attr_bands.newIntBand("code_flags_hi"); + IntBand code_flags_lo = code_attr_bands.newIntBand("code_flags_lo"); + IntBand code_attr_count = code_attr_bands.newIntBand("code_attr_count"); + IntBand code_attr_indexes = code_attr_bands.newIntBand("code_attr_indexes"); + IntBand code_attr_calls = code_attr_bands.newIntBand("code_attr_calls"); + + MultiBand stackmap_bands = code_attr_bands.newMultiBand("(StackMapTable_bands)", UNSIGNED5); + IntBand code_StackMapTable_N = stackmap_bands.newIntBand("code_StackMapTable_N"); + IntBand code_StackMapTable_frame_T = stackmap_bands.newIntBand("code_StackMapTable_frame_T",BYTE1); + IntBand code_StackMapTable_local_N = stackmap_bands.newIntBand("code_StackMapTable_local_N"); + IntBand code_StackMapTable_stack_N = stackmap_bands.newIntBand("code_StackMapTable_stack_N"); + IntBand code_StackMapTable_offset = stackmap_bands.newIntBand("code_StackMapTable_offset", UNSIGNED5); + IntBand code_StackMapTable_T = stackmap_bands.newIntBand("code_StackMapTable_T", BYTE1); + CPRefBand code_StackMapTable_RC = stackmap_bands.newCPRefBand("code_StackMapTable_RC", Constants.CONSTANT_Class); + IntBand code_StackMapTable_P = stackmap_bands.newIntBand("code_StackMapTable_P", BCI5); + + // bands for predefined LineNumberTable attribute + IntBand code_LineNumberTable_N = code_attr_bands.newIntBand("code_LineNumberTable_N"); + IntBand code_LineNumberTable_bci_P = code_attr_bands.newIntBand("code_LineNumberTable_bci_P", BCI5); + IntBand code_LineNumberTable_line = code_attr_bands.newIntBand("code_LineNumberTable_line"); + + // bands for predefined LocalVariable{Type}Table attributes + IntBand code_LocalVariableTable_N = code_attr_bands.newIntBand("code_LocalVariableTable_N"); + IntBand code_LocalVariableTable_bci_P = code_attr_bands.newIntBand("code_LocalVariableTable_bci_P", BCI5); + IntBand code_LocalVariableTable_span_O = code_attr_bands.newIntBand("code_LocalVariableTable_span_O", BRANCH5); + CPRefBand code_LocalVariableTable_name_RU = code_attr_bands.newCPRefBand("code_LocalVariableTable_name_RU", Constants.CONSTANT_Utf8); + CPRefBand code_LocalVariableTable_type_RS = code_attr_bands.newCPRefBand("code_LocalVariableTable_type_RS", Constants.CONSTANT_Signature); + IntBand code_LocalVariableTable_slot = code_attr_bands.newIntBand("code_LocalVariableTable_slot"); + IntBand code_LocalVariableTypeTable_N = code_attr_bands.newIntBand("code_LocalVariableTypeTable_N"); + IntBand code_LocalVariableTypeTable_bci_P = code_attr_bands.newIntBand("code_LocalVariableTypeTable_bci_P", BCI5); + IntBand code_LocalVariableTypeTable_span_O = code_attr_bands.newIntBand("code_LocalVariableTypeTable_span_O", BRANCH5); + CPRefBand code_LocalVariableTypeTable_name_RU = code_attr_bands.newCPRefBand("code_LocalVariableTypeTable_name_RU", Constants.CONSTANT_Utf8); + CPRefBand code_LocalVariableTypeTable_type_RS = code_attr_bands.newCPRefBand("code_LocalVariableTypeTable_type_RS", Constants.CONSTANT_Signature); + IntBand code_LocalVariableTypeTable_slot = code_attr_bands.newIntBand("code_LocalVariableTypeTable_slot"); + MultiBand code_type_metadata_bands = code_attr_bands.newMultiBand("(code_type_metadata_bands)", UNSIGNED5); + + // bands for bytecodes + MultiBand bc_bands = all_bands.newMultiBand("(byte_codes)", UNSIGNED5); + ByteBand bc_codes = bc_bands.newByteBand("bc_codes"); //BYTE1 + // remaining bands provide typed opcode fields required by the bc_codes + + IntBand bc_case_count = bc_bands.newIntBand("bc_case_count"); // *switch + IntBand bc_case_value = bc_bands.newIntBand("bc_case_value", DELTA5); // *switch + ByteBand bc_byte = bc_bands.newByteBand("bc_byte"); //BYTE1 // bipush, iinc, *newarray + IntBand bc_short = bc_bands.newIntBand("bc_short", DELTA5); // sipush, wide iinc + IntBand bc_local = bc_bands.newIntBand("bc_local"); // *load, *store, iinc, ret + IntBand bc_label = bc_bands.newIntBand("bc_label", BRANCH5); // if*, goto*, jsr*, *switch + + // Most CP refs exhibit some correlation, and benefit from delta coding. + // The notable exceptions are class and method references. + + // ldc* operands: + CPRefBand bc_intref = bc_bands.newCPRefBand("bc_intref", DELTA5, Constants.CONSTANT_Integer); + CPRefBand bc_floatref = bc_bands.newCPRefBand("bc_floatref", DELTA5, Constants.CONSTANT_Float); + CPRefBand bc_longref = bc_bands.newCPRefBand("bc_longref", DELTA5, Constants.CONSTANT_Long); + CPRefBand bc_doubleref = bc_bands.newCPRefBand("bc_doubleref", DELTA5, Constants.CONSTANT_Double); + CPRefBand bc_stringref = bc_bands.newCPRefBand("bc_stringref", DELTA5, Constants.CONSTANT_String); + CPRefBand bc_loadablevalueref = bc_bands.newCPRefBand("bc_loadablevalueref", DELTA5, Constants.CONSTANT_LoadableValue); + + // nulls produced by bc_classref are taken to mean the current class + CPRefBand bc_classref = bc_bands.newCPRefBand("bc_classref", UNSIGNED5, Constants.CONSTANT_Class, NULL_IS_OK); // new, *anew*, c*cast, i*of, ldc + CPRefBand bc_fieldref = bc_bands.newCPRefBand("bc_fieldref", DELTA5, Constants.CONSTANT_Fieldref); // get*, put* + CPRefBand bc_methodref = bc_bands.newCPRefBand("bc_methodref", Constants.CONSTANT_Methodref); // invoke[vs]* + CPRefBand bc_imethodref = bc_bands.newCPRefBand("bc_imethodref", DELTA5, Constants.CONSTANT_InterfaceMethodref); // invokeinterface + CPRefBand bc_indyref = bc_bands.newCPRefBand("bc_indyref", DELTA5, Constants.CONSTANT_InvokeDynamic); // invokedynamic + + // _self_linker_op family + CPRefBand bc_thisfield = bc_bands.newCPRefBand("bc_thisfield", Constants.CONSTANT_None); // any field within cur. class + CPRefBand bc_superfield = bc_bands.newCPRefBand("bc_superfield", Constants.CONSTANT_None); // any field within superclass + CPRefBand bc_thismethod = bc_bands.newCPRefBand("bc_thismethod", Constants.CONSTANT_None); // any method within cur. class + CPRefBand bc_supermethod = bc_bands.newCPRefBand("bc_supermethod", Constants.CONSTANT_None); // any method within superclass + // bc_invokeinit family: + IntBand bc_initref = bc_bands.newIntBand("bc_initref"); + // escapes + CPRefBand bc_escref = bc_bands.newCPRefBand("bc_escref", Constants.CONSTANT_All); + IntBand bc_escrefsize = bc_bands.newIntBand("bc_escrefsize"); + IntBand bc_escsize = bc_bands.newIntBand("bc_escsize"); + ByteBand bc_escbyte = bc_bands.newByteBand("bc_escbyte"); + + // bands for carrying resource files and file attributes: + MultiBand file_bands = all_bands.newMultiBand("(file_bands)", UNSIGNED5); + CPRefBand file_name = file_bands.newCPRefBand("file_name", Constants.CONSTANT_Utf8); + IntBand file_size_hi = file_bands.newIntBand("file_size_hi"); + IntBand file_size_lo = file_bands.newIntBand("file_size_lo"); + IntBand file_modtime = file_bands.newIntBand("file_modtime", DELTA5); + IntBand file_options = file_bands.newIntBand("file_options"); + ByteBand file_bits = file_bands.newByteBand("file_bits"); + + // End of band definitions! + + /** Given CP indexes, distribute tag-specific indexes to bands. */ + protected void setBandIndexes() { + // Handle prior calls to setBandIndex: + for (Object[] need : needPredefIndex) { + CPRefBand b = (CPRefBand) need[0]; + Byte which = (Byte) need[1]; + b.setIndex(getCPIndex(which.byteValue())); + } + needPredefIndex = null; // no more predefs + + if (verbose > 3) { + printCDecl(all_bands); + } + } + + protected void setBandIndex(CPRefBand b, byte which) { + Object[] need = { b, Byte.valueOf(which) }; + if (which == Constants.CONSTANT_FieldSpecific) { + // I.e., attribute layouts KQ (no null) or KQN (null ok). + allKQBands.add(b); + } else if (needPredefIndex != null) { + needPredefIndex.add(need); + } else { + // Not in predefinition mode; getCPIndex now works. + b.setIndex(getCPIndex(which)); + } + } + + protected void setConstantValueIndex(Package.Class.Field f) { + ConstantPool.Index ix = null; + if (f != null) { + byte tag = f.getLiteralTag(); + ix = getCPIndex(tag); + if (verbose > 2) + Utils.log.fine("setConstantValueIndex "+f+" "+ConstantPool.tagName(tag)+" => "+ix); + assert(ix != null); + } + // Typically, allKQBands is the singleton of field_ConstantValue_KQ. + for (CPRefBand xxx_KQ : allKQBands) { + xxx_KQ.setIndex(ix); + } + } + + // Table of bands which contain metadata. + protected MultiBand[] metadataBands = new MultiBand[Constants.ATTR_CONTEXT_LIMIT]; + { + metadataBands[Constants.ATTR_CONTEXT_CLASS] = class_metadata_bands; + metadataBands[Constants.ATTR_CONTEXT_FIELD] = field_metadata_bands; + metadataBands[Constants.ATTR_CONTEXT_METHOD] = method_metadata_bands; + } + // Table of bands which contains type_metadata (TypeAnnotations) + protected MultiBand[] typeMetadataBands = new MultiBand[Constants.ATTR_CONTEXT_LIMIT]; + { + typeMetadataBands[Constants.ATTR_CONTEXT_CLASS] = class_type_metadata_bands; + typeMetadataBands[Constants.ATTR_CONTEXT_FIELD] = field_type_metadata_bands; + typeMetadataBands[Constants.ATTR_CONTEXT_METHOD] = method_type_metadata_bands; + typeMetadataBands[Constants.ATTR_CONTEXT_CODE] = code_type_metadata_bands; + } + + // Attribute layouts. + public static final int ADH_CONTEXT_MASK = 0x3; // (ad_hdr & ADH_CONTEXT_MASK) + public static final int ADH_BIT_SHIFT = 0x2; // (ad_hdr >> ADH_BIT_SHIFT) + public static final int ADH_BIT_IS_LSB = 1; + public static final int ATTR_INDEX_OVERFLOW = -1; + + public int[] attrIndexLimit = new int[Constants.ATTR_CONTEXT_LIMIT]; + // Each index limit is either 32 or 63, depending on AO_HAVE_XXX_FLAGS_HI. + + // Which flag bits are taken over by attributes? + protected long[] attrFlagMask = new long[Constants.ATTR_CONTEXT_LIMIT]; + // Which flag bits have been taken over explicitly? + protected long[] attrDefSeen = new long[Constants.ATTR_CONTEXT_LIMIT]; + + // What pseudo-attribute bits are there to watch for? + protected int[] attrOverflowMask = new int[Constants.ATTR_CONTEXT_LIMIT]; + protected int attrClassFileVersionMask; + + // Mapping from Attribute.Layout to Band[] (layout element bands). + protected Map attrBandTable = new HashMap<>(); + + // Well-known attributes: + protected final Attribute.Layout attrCodeEmpty; + protected final Attribute.Layout attrInnerClassesEmpty; + protected final Attribute.Layout attrClassFileVersion; + protected final Attribute.Layout attrConstantValue; + + // Mapping from Attribute.Layout to Integer (inverse of attrDefs) + Map attrIndexTable = new HashMap<>(); + + // Mapping from attribute index (<32 are flag bits) to attributes. + protected List> attrDefs = + new FixedList<>(Constants.ATTR_CONTEXT_LIMIT); + { + for (int i = 0; i < Constants.ATTR_CONTEXT_LIMIT; i++) { + assert(attrIndexLimit[i] == 0); + attrIndexLimit[i] = 32; // just for the sake of predefs. + attrDefs.set(i, new ArrayList<>(Collections.nCopies( + attrIndexLimit[i], (Attribute.Layout)null))); + + } + + // Add predefined attribute definitions: + attrInnerClassesEmpty = + predefineAttribute(Constants.CLASS_ATTR_InnerClasses, Constants.ATTR_CONTEXT_CLASS, null, + "InnerClasses", ""); + assert(attrInnerClassesEmpty == Package.attrInnerClassesEmpty); + predefineAttribute(Constants.CLASS_ATTR_SourceFile, Constants.ATTR_CONTEXT_CLASS, + new Band[] { class_SourceFile_RUN }, + "SourceFile", "RUNH"); + predefineAttribute(Constants.CLASS_ATTR_EnclosingMethod, Constants.ATTR_CONTEXT_CLASS, + new Band[] { + class_EnclosingMethod_RC, + class_EnclosingMethod_RDN + }, + "EnclosingMethod", "RCHRDNH"); + attrClassFileVersion = + predefineAttribute(Constants.CLASS_ATTR_ClassFile_version, Constants.ATTR_CONTEXT_CLASS, + new Band[] { + class_ClassFile_version_minor_H, + class_ClassFile_version_major_H + }, + ".ClassFile.version", "HH"); + predefineAttribute(Constants.X_ATTR_Signature, Constants.ATTR_CONTEXT_CLASS, + new Band[] { class_Signature_RS }, + "Signature", "RSH"); + predefineAttribute(Constants.X_ATTR_Deprecated, Constants.ATTR_CONTEXT_CLASS, null, + "Deprecated", ""); + //predefineAttribute(X_ATTR_Synthetic, ATTR_CONTEXT_CLASS, null, + // "Synthetic", ""); + predefineAttribute(Constants.X_ATTR_OVERFLOW, Constants.ATTR_CONTEXT_CLASS, null, + ".Overflow", ""); + attrConstantValue = + predefineAttribute(Constants.FIELD_ATTR_ConstantValue, Constants.ATTR_CONTEXT_FIELD, + new Band[] { field_ConstantValue_KQ }, + "ConstantValue", "KQH"); + predefineAttribute(Constants.X_ATTR_Signature, Constants.ATTR_CONTEXT_FIELD, + new Band[] { field_Signature_RS }, + "Signature", "RSH"); + predefineAttribute(Constants.X_ATTR_Deprecated, Constants.ATTR_CONTEXT_FIELD, null, + "Deprecated", ""); + //predefineAttribute(X_ATTR_Synthetic, ATTR_CONTEXT_FIELD, null, + // "Synthetic", ""); + predefineAttribute(Constants.X_ATTR_OVERFLOW, Constants.ATTR_CONTEXT_FIELD, null, + ".Overflow", ""); + attrCodeEmpty = + predefineAttribute(Constants.METHOD_ATTR_Code, Constants.ATTR_CONTEXT_METHOD, null, + "Code", ""); + predefineAttribute(Constants.METHOD_ATTR_Exceptions, Constants.ATTR_CONTEXT_METHOD, + new Band[] { + method_Exceptions_N, + method_Exceptions_RC + }, + "Exceptions", "NH[RCH]"); + predefineAttribute(Constants.METHOD_ATTR_MethodParameters, Constants.ATTR_CONTEXT_METHOD, + new Band[]{ + method_MethodParameters_NB, + method_MethodParameters_name_RUN, + method_MethodParameters_flag_FH + }, + "MethodParameters", "NB[RUNHFH]"); + assert(attrCodeEmpty == Package.attrCodeEmpty); + predefineAttribute(Constants.X_ATTR_Signature, Constants.ATTR_CONTEXT_METHOD, + new Band[] { method_Signature_RS }, + "Signature", "RSH"); + predefineAttribute(Constants.X_ATTR_Deprecated, Constants.ATTR_CONTEXT_METHOD, null, + "Deprecated", ""); + //predefineAttribute(X_ATTR_Synthetic, ATTR_CONTEXT_METHOD, null, + // "Synthetic", ""); + predefineAttribute(Constants.X_ATTR_OVERFLOW, Constants.ATTR_CONTEXT_METHOD, null, + ".Overflow", ""); + + for (int ctype = 0; ctype < Constants.ATTR_CONTEXT_LIMIT; ctype++) { + MultiBand xxx_metadata_bands = metadataBands[ctype]; + if (ctype != Constants.ATTR_CONTEXT_CODE) { + // These arguments cause the bands to be built + // automatically for this complicated layout: + predefineAttribute(Constants.X_ATTR_RuntimeVisibleAnnotations, + Constants.ATTR_CONTEXT_NAME[ctype]+"_RVA_", + xxx_metadata_bands, + Attribute.lookup(null, ctype, + "RuntimeVisibleAnnotations")); + predefineAttribute(Constants.X_ATTR_RuntimeInvisibleAnnotations, + Constants.ATTR_CONTEXT_NAME[ctype]+"_RIA_", + xxx_metadata_bands, + Attribute.lookup(null, ctype, + "RuntimeInvisibleAnnotations")); + + if (ctype == Constants.ATTR_CONTEXT_METHOD) { + predefineAttribute(Constants.METHOD_ATTR_RuntimeVisibleParameterAnnotations, + "method_RVPA_", xxx_metadata_bands, + Attribute.lookup(null, ctype, + "RuntimeVisibleParameterAnnotations")); + predefineAttribute(Constants.METHOD_ATTR_RuntimeInvisibleParameterAnnotations, + "method_RIPA_", xxx_metadata_bands, + Attribute.lookup(null, ctype, + "RuntimeInvisibleParameterAnnotations")); + predefineAttribute(Constants.METHOD_ATTR_AnnotationDefault, + "method_AD_", xxx_metadata_bands, + Attribute.lookup(null, ctype, + "AnnotationDefault")); + } + } + // All contexts have these + MultiBand xxx_type_metadata_bands = typeMetadataBands[ctype]; + predefineAttribute(Constants.X_ATTR_RuntimeVisibleTypeAnnotations, + Constants.ATTR_CONTEXT_NAME[ctype] + "_RVTA_", + xxx_type_metadata_bands, + Attribute.lookup(null, ctype, + "RuntimeVisibleTypeAnnotations")); + predefineAttribute(Constants.X_ATTR_RuntimeInvisibleTypeAnnotations, + Constants.ATTR_CONTEXT_NAME[ctype] + "_RITA_", + xxx_type_metadata_bands, + Attribute.lookup(null, ctype, + "RuntimeInvisibleTypeAnnotations")); + } + + + Attribute.Layout stackMapDef = Attribute.lookup(null, Constants.ATTR_CONTEXT_CODE, "StackMapTable").layout(); + predefineAttribute(Constants.CODE_ATTR_StackMapTable, Constants.ATTR_CONTEXT_CODE, + stackmap_bands.toArray(), + stackMapDef.name(), stackMapDef.layout()); + + predefineAttribute(Constants.CODE_ATTR_LineNumberTable, Constants.ATTR_CONTEXT_CODE, + new Band[] { + code_LineNumberTable_N, + code_LineNumberTable_bci_P, + code_LineNumberTable_line + }, + "LineNumberTable", "NH[PHH]"); + predefineAttribute(Constants.CODE_ATTR_LocalVariableTable, Constants.ATTR_CONTEXT_CODE, + new Band[] { + code_LocalVariableTable_N, + code_LocalVariableTable_bci_P, + code_LocalVariableTable_span_O, + code_LocalVariableTable_name_RU, + code_LocalVariableTable_type_RS, + code_LocalVariableTable_slot + }, + "LocalVariableTable", "NH[PHOHRUHRSHH]"); + predefineAttribute(Constants.CODE_ATTR_LocalVariableTypeTable, Constants.ATTR_CONTEXT_CODE, + new Band[] { + code_LocalVariableTypeTable_N, + code_LocalVariableTypeTable_bci_P, + code_LocalVariableTypeTable_span_O, + code_LocalVariableTypeTable_name_RU, + code_LocalVariableTypeTable_type_RS, + code_LocalVariableTypeTable_slot + }, + "LocalVariableTypeTable", "NH[PHOHRUHRSHH]"); + predefineAttribute(Constants.X_ATTR_OVERFLOW, Constants.ATTR_CONTEXT_CODE, null, + ".Overflow", ""); + + // Clear the record of having seen these definitions, + // so they may be redefined without error. + for (int i = 0; i < Constants.ATTR_CONTEXT_LIMIT; i++) { + attrDefSeen[i] = 0; + } + + // Set up the special masks: + for (int i = 0; i < Constants.ATTR_CONTEXT_LIMIT; i++) { + attrOverflowMask[i] = (1< 0) Utils.log.fine("Legacy package version"); + // Revoke definition of pre-1.6 attribute type. + undefineAttribute(Constants.CODE_ATTR_StackMapTable, Constants.ATTR_CONTEXT_CODE); + } + } + + protected void initAttrIndexLimit() { + for (int i = 0; i < Constants.ATTR_CONTEXT_LIMIT; i++) { + assert(attrIndexLimit[i] == 0); // decide on it now! + attrIndexLimit[i] = (haveFlagsHi(i)? 63: 32); + List defList = attrDefs.get(i); + assert(defList.size() == 32); // all predef indexes are <32 + int addMore = attrIndexLimit[i] - defList.size(); + defList.addAll(Collections.nCopies(addMore, (Attribute.Layout) null)); + } + } + + protected boolean haveFlagsHi(int ctype) { + int mask = 1<<(Constants.LG_AO_HAVE_XXX_FLAGS_HI+ctype); + switch (ctype) { + case Constants.ATTR_CONTEXT_CLASS: + assert(mask == Constants.AO_HAVE_CLASS_FLAGS_HI); break; + case Constants.ATTR_CONTEXT_FIELD: + assert(mask == Constants.AO_HAVE_FIELD_FLAGS_HI); break; + case Constants.ATTR_CONTEXT_METHOD: + assert(mask == Constants.AO_HAVE_METHOD_FLAGS_HI); break; + case Constants.ATTR_CONTEXT_CODE: + assert(mask == Constants.AO_HAVE_CODE_FLAGS_HI); break; + default: + assert(false); + } + return testBit(archiveOptions, mask); + } + + protected List getPredefinedAttrs(int ctype) { + assert(attrIndexLimit[ctype] != 0); + List res = new ArrayList<>(attrIndexLimit[ctype]); + // Remove nulls and non-predefs. + for (int ai = 0; ai < attrIndexLimit[ctype]; ai++) { + if (testBit(attrDefSeen[ctype], 1L<= attrIndexLimit[ctype]) return false; + // If the bit is set, it was explicitly def'd. + if (testBit(attrDefSeen[ctype], 1L<= 0) { + setAttributeLayoutIndex(def, index); + } + if (ab == null) { + ab = new Band[0]; + } + assert(attrBandTable.get(def) == null); // no redef + attrBandTable.put(def, ab); + assert(def.bandCount == ab.length) + : (def+" // "+Arrays.asList(ab)); + // Let's make sure the band types match: + assert(assertBandOKForElems(ab, def.elems)); + return def; + } + + // This version takes bandPrefix/addHere instead of prebuilt Band[] ab. + private + Attribute.Layout predefineAttribute(int index, + String bandPrefix, MultiBand addHere, + Attribute attr) { + //Attribute.Layout def = Attribute.find(ctype, name, layout).layout(); + Attribute.Layout def = attr.layout(); + int ctype = def.ctype(); + return predefineAttribute(index, ctype, + makeNewAttributeBands(bandPrefix, def, addHere), + def.name(), def.layout()); + } + + private + void undefineAttribute(int index, int ctype) { + if (verbose > 1) { + System.out.println("Removing predefined "+Constants.ATTR_CONTEXT_NAME[ctype]+ + " attribute on bit "+index); + } + List defList = attrDefs.get(ctype); + Attribute.Layout def = defList.get(index); + assert(def != null); + defList.set(index, null); + attrIndexTable.put(def, null); + // Clear the def bit. (For predefs, it's already clear.) + assert(index < 64); + attrDefSeen[ctype] &= ~(1L< 1) + Utils.log.fine("Making new bands for "+def); + Band[] newAB = makeNewAttributeBands(pfx, def, + xxx_attr_bands); + assert(newAB.length == def.bandCount); + Band[] prevAB = attrBandTable.put(def, newAB); + if (prevAB != null) { + // We won't be using these predefined bands. + for (int j = 0; j < prevAB.length; j++) { + prevAB[j].doneWithUnusedBand(); + } + } + } + } + //System.out.println(prevForAssertMap); + } + private + Band[] makeNewAttributeBands(String pfx, Attribute.Layout def, + MultiBand addHere) { + int base = addHere.size(); + makeNewAttributeBands(pfx, def.elems, addHere); + int nb = addHere.size() - base; + Band[] newAB = new Band[nb]; + for (int i = 0; i < nb; i++) { + newAB[i] = addHere.get(base+i); + } + return newAB; + } + // Recursive helper, operates on a "body" or other sequence of elems: + private + void makeNewAttributeBands(String pfx, Attribute.Layout.Element[] elems, + MultiBand ab) { + for (int i = 0; i < elems.length; i++) { + Attribute.Layout.Element e = elems[i]; + String name = pfx+ab.size()+"_"+e.layout; + { + int tem; + if ((tem = name.indexOf('[')) > 0) + name = name.substring(0, tem); + if ((tem = name.indexOf('(')) > 0) + name = name.substring(0, tem); + if (name.endsWith("H")) + name = name.substring(0, name.length()-1); + } + Band nb; + switch (e.kind) { + case Attribute.EK_INT: + nb = newElemBand(e, name, ab); + break; + case Attribute.EK_BCI: + if (!e.flagTest(Attribute.EF_DELTA)) { + // PH: transmit R(bci), store bci + nb = ab.newIntBand(name, BCI5); + } else { + // POH: transmit D(R(bci)), store bci + nb = ab.newIntBand(name, BRANCH5); + } + // Note: No case for BYTE1 here. + break; + case Attribute.EK_BCO: + // OH: transmit D(R(bci)), store D(bci) + nb = ab.newIntBand(name, BRANCH5); + // Note: No case for BYTE1 here. + break; + case Attribute.EK_FLAG: + assert(!e.flagTest(Attribute.EF_SIGN)); + nb = newElemBand(e, name, ab); + break; + case Attribute.EK_REPL: + assert(!e.flagTest(Attribute.EF_SIGN)); + nb = newElemBand(e, name, ab); + makeNewAttributeBands(pfx, e.body, ab); + break; + case Attribute.EK_UN: + nb = newElemBand(e, name, ab); + makeNewAttributeBands(pfx, e.body, ab); + break; + case Attribute.EK_CASE: + if (!e.flagTest(Attribute.EF_BACK)) { + // If it's not a duplicate body, make the bands. + makeNewAttributeBands(pfx, e.body, ab); + } + continue; // no new band to make + case Attribute.EK_REF: + byte refKind = e.refKind; + boolean nullOK = e.flagTest(Attribute.EF_NULL); + nb = ab.newCPRefBand(name, UNSIGNED5, refKind, nullOK); + // Note: No case for BYTE1 here. + break; + case Attribute.EK_CALL: + continue; // no new band to make + case Attribute.EK_CBLE: + makeNewAttributeBands(pfx, e.body, ab); + continue; // no new band to make + default: assert(false); continue; + } + if (verbose > 1) { + Utils.log.fine("New attribute band "+nb); + } + } + } + private + Band newElemBand(Attribute.Layout.Element e, String name, MultiBand ab) { + if (e.flagTest(Attribute.EF_SIGN)) { + return ab.newIntBand(name, SIGNED5); + } else if (e.len == 1) { + return ab.newIntBand(name, BYTE1); // Not ByteBand, please. + } else { + return ab.newIntBand(name, UNSIGNED5); + } + } + + protected int setAttributeLayoutIndex(Attribute.Layout def, int index) { + int ctype = def.ctype; + assert(ATTR_INDEX_OVERFLOW <= index && index < attrIndexLimit[ctype]); + List defList = attrDefs.get(ctype); + if (index == ATTR_INDEX_OVERFLOW) { + // Overflow attribute. + index = defList.size(); + defList.add(def); + if (verbose > 0) + Utils.log.info("Adding new attribute at "+def +": "+index); + attrIndexTable.put(def, index); + return index; + } + + // Detect redefinitions: + if (testBit(attrDefSeen[ctype], 1L< (attrClassFileVersionMask == 0? 2:0)) + Utils.log.fine("Fixing new attribute at "+index + +": "+def + +(defList.get(index) == null? "": + "; replacing "+defList.get(index))); + attrFlagMask[ctype] |= (1L<= shortCodeLimits.length) return LONG_CODE_HEADER; + int siglen = code.getMethod().getArgumentSize(); + assert(l0 >= siglen); // enough locals for signature! + if (l0 < siglen) return LONG_CODE_HEADER; + int l1 = l0 - siglen; // do not count locals required by the signature + int lims = shortCodeLimits[h][0]; + int liml = shortCodeLimits[h][1]; + if (s >= lims || l1 >= liml) return LONG_CODE_HEADER; + int sc = shortCodeHeader_h_base(h); + sc += s + lims*l1; + if (sc > 255) return LONG_CODE_HEADER; + assert(shortCodeHeader_max_stack(sc) == s); + assert(shortCodeHeader_max_na_locals(sc) == l1); + assert(shortCodeHeader_handler_count(sc) == h); + return sc; + } + + static final int LONG_CODE_HEADER = 0; + static int shortCodeHeader_handler_count(int sc) { + assert(sc > 0 && sc <= 255); + for (int h = 0; ; h++) { + if (sc < shortCodeHeader_h_base(h+1)) + return h; + } + } + static int shortCodeHeader_max_stack(int sc) { + int h = shortCodeHeader_handler_count(sc); + int lims = shortCodeLimits[h][0]; + return (sc - shortCodeHeader_h_base(h)) % lims; + } + static int shortCodeHeader_max_na_locals(int sc) { + int h = shortCodeHeader_handler_count(sc); + int lims = shortCodeLimits[h][0]; + return (sc - shortCodeHeader_h_base(h)) / lims; + } + + private static int shortCodeHeader_h_base(int h) { + assert(h <= shortCodeLimits.length); + int sc = 1; + for (int h0 = 0; h0 < h; h0++) { + int lims = shortCodeLimits[h0][0]; + int liml = shortCodeLimits[h0][1]; + sc += lims * liml; + } + return sc; + } + + // utilities for accessing the bc_label band: + protected void putLabel(IntBand bc_label, Code c, int pc, int targetPC) { + bc_label.putInt(c.encodeBCI(targetPC) - c.encodeBCI(pc)); + } + protected int getLabel(IntBand bc_label, Code c, int pc) { + return c.decodeBCI(bc_label.getInt() + c.encodeBCI(pc)); + } + + protected CPRefBand getCPRefOpBand(int bc) { + switch (Instruction.getCPRefOpTag(bc)) { + case Constants.CONSTANT_Class: + return bc_classref; + case Constants.CONSTANT_Fieldref: + return bc_fieldref; + case Constants.CONSTANT_Methodref: + return bc_methodref; + case Constants.CONSTANT_InterfaceMethodref: + return bc_imethodref; + case Constants.CONSTANT_InvokeDynamic: + return bc_indyref; + case Constants.CONSTANT_LoadableValue: + switch (bc) { + case Constants._ildc: case Constants._ildc_w: + return bc_intref; + case Constants._fldc: case Constants._fldc_w: + return bc_floatref; + case Constants._lldc2_w: + return bc_longref; + case Constants._dldc2_w: + return bc_doubleref; + case Constants._sldc: case Constants._sldc_w: + return bc_stringref; + case Constants._cldc: case Constants._cldc_w: + return bc_classref; + case Constants._qldc: case Constants._qldc_w: + return bc_loadablevalueref; + } + break; + } + assert(false); + return null; + } + + protected CPRefBand selfOpRefBand(int self_bc) { + assert(Instruction.isSelfLinkerOp(self_bc)); + int idx = (self_bc - Constants._self_linker_op); + boolean isSuper = (idx >= Constants._self_linker_super_flag); + if (isSuper) idx -= Constants._self_linker_super_flag; + boolean isAload = (idx >= Constants._self_linker_aload_flag); + if (isAload) idx -= Constants._self_linker_aload_flag; + int origBC = Constants._first_linker_op + idx; + boolean isField = Instruction.isFieldOp(origBC); + if (!isSuper) + return isField? bc_thisfield: bc_thismethod; + else + return isField? bc_superfield: bc_supermethod; + } + + //////////////////////////////////////////////////////////////////// + + static int nextSeqForDebug; + static File dumpDir = null; + static OutputStream getDumpStream(Band b, String ext) throws IOException { + return getDumpStream(b.name, b.seqForDebug, ext, b); + } + static OutputStream getDumpStream(ConstantPool.Index ix, String ext) throws IOException { + if (ix.size() == 0) return new ByteArrayOutputStream(); + int seq = ConstantPool.TAG_ORDER[ix.cpMap[0].tag]; + return getDumpStream(ix.debugName, seq, ext, ix); + } + static OutputStream getDumpStream(String name, int seq, String ext, Object b) throws IOException { + if (dumpDir == null) { + dumpDir = File.createTempFile("BD_", "", new File(".")); + dumpDir.delete(); + if (dumpDir.mkdir()) + Utils.log.info("Dumping bands to "+dumpDir); + } + name = name.replace('(', ' ').replace(')', ' '); + name = name.replace('/', ' '); + name = name.replace('*', ' '); + name = name.trim().replace(' ','_'); + name = ((10000+seq) + "_" + name).substring(1); + File dumpFile = new File(dumpDir, name+ext); + Utils.log.info("Dumping "+b+" to "+dumpFile); + return new BufferedOutputStream(new FileOutputStream(dumpFile)); + } + + // DEBUG ONLY: Validate me at each length change. + static boolean assertCanChangeLength(Band b) { + switch (b.phase) { + case COLLECT_PHASE: + case READ_PHASE: + return true; + } + return false; + } + + // DEBUG ONLY: Validate a phase. + static boolean assertPhase(Band b, int phaseExpected) { + if (b.phase() != phaseExpected) { + Utils.log.warning("phase expected "+phaseExpected+" was "+b.phase()+" in "+b); + return false; + } + return true; + } + + + // DEBUG ONLY: Tells whether verbosity is turned on. + static int verbose() { + return Utils.currentPropMap().getInteger(Utils.DEBUG_VERBOSE); + } + + + // DEBUG ONLY: Validate me at each phase change. + static boolean assertPhaseChangeOK(Band b, int p0, int p1) { + switch (p0*10+p1) { + /// Writing phases: + case NO_PHASE*10+COLLECT_PHASE: + // Ready to collect data from the input classes. + assert(!b.isReader()); + assert(b.capacity() >= 0); + assert(b.length() == 0); + return true; + case COLLECT_PHASE*10+FROZEN_PHASE: + case FROZEN_PHASE*10+FROZEN_PHASE: + assert(b.length() == 0); + return true; + case COLLECT_PHASE*10+WRITE_PHASE: + case FROZEN_PHASE*10+WRITE_PHASE: + // Data is all collected. Ready to write bytes to disk. + return true; + case WRITE_PHASE*10+DONE_PHASE: + // Done writing to disk. Ready to reset, in principle. + return true; + + /// Reading phases: + case NO_PHASE*10+EXPECT_PHASE: + assert(b.isReader()); + assert(b.capacity() < 0); + return true; + case EXPECT_PHASE*10+READ_PHASE: + // Ready to read values from disk. + assert(Math.max(0,b.capacity()) >= b.valuesExpected()); + assert(b.length() <= 0); + return true; + case READ_PHASE*10+DISBURSE_PHASE: + // Ready to disburse values. + assert(b.valuesRemainingForDebug() == b.length()); + return true; + case DISBURSE_PHASE*10+DONE_PHASE: + // Done disbursing values. Ready to reset, in principle. + assert(assertDoneDisbursing(b)); + return true; + } + if (p0 == p1) + Utils.log.warning("Already in phase "+p0); + else + Utils.log.warning("Unexpected phase "+p0+" -> "+p1); + return false; + } + + private static boolean assertDoneDisbursing(Band b) { + if (b.phase != DISBURSE_PHASE) { + Utils.log.warning("assertDoneDisbursing: still in phase "+b.phase+": "+b); + if (verbose() <= 1) return false; // fail now + } + int left = b.valuesRemainingForDebug(); + if (left > 0) { + Utils.log.warning("assertDoneDisbursing: "+left+" values left in "+b); + if (verbose() <= 1) return false; // fail now + } + if (b instanceof MultiBand) { + MultiBand mb = (MultiBand) b; + for (int i = 0; i < mb.bandCount; i++) { + Band sub = mb.bands[i]; + if (sub.phase != DONE_PHASE) { + Utils.log.warning("assertDoneDisbursing: sub-band still in phase "+sub.phase+": "+sub); + if (verbose() <= 1) return false; // fail now + } + } + } + return true; + } + + private static void printCDecl(Band b) { + if (b instanceof MultiBand) { + MultiBand mb = (MultiBand) b; + for (int i = 0; i < mb.bandCount; i++) { + printCDecl(mb.bands[i]); + } + return; + } + String ixS = "NULL"; + if (b instanceof CPRefBand) { + ConstantPool.Index ix = ((CPRefBand)b).index; + if (ix != null) ixS = "INDEX("+ix.debugName+")"; + } + Coding[] knownc = { BYTE1, CHAR3, BCI5, BRANCH5, UNSIGNED5, + UDELTA5, SIGNED5, DELTA5, MDELTA5 }; + String[] knowns = { "BYTE1", "CHAR3", "BCI5", "BRANCH5", "UNSIGNED5", + "UDELTA5", "SIGNED5", "DELTA5", "MDELTA5" }; + Coding rc = b.regularCoding; + int rci = Arrays.asList(knownc).indexOf(rc); + String cstr; + if (rci >= 0) + cstr = knowns[rci]; + else + cstr = "CODING"+rc.keyString(); + System.out.println(" BAND_INIT(\""+b.name()+"\"" + +", "+cstr+", "+ixS+"),"); + } + + private Map prevForAssertMap; + + // DEBUG ONLY: Record something about the band order. + boolean notePrevForAssert(Band b, Band p) { + if (prevForAssertMap == null) + prevForAssertMap = new HashMap<>(); + prevForAssertMap.put(b, p); + return true; + } + + // DEBUG ONLY: Validate next input band, ensure bands are read in sequence + private boolean assertReadyToReadFrom(Band b, InputStream in) throws IOException { + Band p = prevForAssertMap.get(b); + // Any previous band must be done reading before this one starts. + if (p != null && phaseCmp(p.phase(), DISBURSE_PHASE) < 0) { + Utils.log.warning("Previous band not done reading."); + Utils.log.info(" Previous band: "+p); + Utils.log.info(" Next band: "+b); + assert(verbose > 0); // die unless verbose is true + } + String name = b.name; + if (optDebugBands && !name.startsWith("(")) { + assert(bandSequenceList != null); + // Verify synchronization between reader & writer: + String inName = bandSequenceList.removeFirst(); + // System.out.println("Reading: " + name); + if (!inName.equals(name)) { + Utils.log.warning("Expected " + name + " but read: " + inName); + return false; + } + Utils.log.info("Read band in sequence: " + name); + } + return true; + } + + // DEBUG ONLY: Make sure a bunch of cprefs are correct. + private boolean assertValidCPRefs(CPRefBand b) { + if (b.index == null) return true; + int limit = b.index.size()+1; + for (int i = 0; i < b.length(); i++) { + int v = b.valueAtForDebug(i); + if (v < 0 || v >= limit) { + Utils.log.warning("CP ref out of range "+ + "["+i+"] = "+v+" in "+b); + return false; + } + } + return true; + } + + /* + * DEBUG ONLY: write the bands to a list and read back the list in order, + * this works perfectly if we use the java packer and unpacker, typically + * this will work with --repack or if they are in the same jvm instance. + */ + static LinkedList bandSequenceList = null; + private boolean assertReadyToWriteTo(Band b, OutputStream out) throws IOException { + Band p = prevForAssertMap.get(b); + // Any previous band must be done writing before this one starts. + if (p != null && phaseCmp(p.phase(), DONE_PHASE) < 0) { + Utils.log.warning("Previous band not done writing."); + Utils.log.info(" Previous band: "+p); + Utils.log.info(" Next band: "+b); + assert(verbose > 0); // die unless verbose is true + } + String name = b.name; + if (optDebugBands && !name.startsWith("(")) { + if (bandSequenceList == null) + bandSequenceList = new LinkedList<>(); + // Verify synchronization between reader & writer: + bandSequenceList.add(name); + // System.out.println("Writing: " + b); + } + return true; + } + + protected static boolean testBit(int flags, int bitMask) { + return (flags & bitMask) != 0; + } + protected static int setBit(int flags, int bitMask, boolean z) { + return z ? (flags | bitMask) : (flags &~ bitMask); + } + protected static boolean testBit(long flags, long bitMask) { + return (flags & bitMask) != 0; + } + protected static long setBit(long flags, long bitMask, boolean z) { + return z ? (flags | bitMask) : (flags &~ bitMask); + } + + + static void printArrayTo(PrintStream ps, int[] values, int start, int end) { + int len = end-start; + for (int i = 0; i < len; i++) { + if (i % 10 == 0) + ps.println(); + else + ps.print(" "); + ps.print(values[start+i]); + } + ps.println(); + } + + static void printArrayTo(PrintStream ps, ConstantPool.Entry[] cpMap, int start, int end) { + printArrayTo(ps, cpMap, start, end, false); + } + static void printArrayTo(PrintStream ps, ConstantPool.Entry[] cpMap, int start, int end, boolean showTags) { + StringBuffer buf = new StringBuffer(); + int len = end-start; + for (int i = 0; i < len; i++) { + ConstantPool.Entry e = cpMap[start+i]; + ps.print(start+i); ps.print("="); + if (showTags) { ps.print(e.tag); ps.print(":"); } + String s = e.stringValue(); + buf.setLength(0); + for (int j = 0; j < s.length(); j++) { + char ch = s.charAt(j); + if (!(ch < ' ' || ch > '~' || ch == '\\')) { + buf.append(ch); + } else if (ch == '\\') { + buf.append("\\\\"); + } else if (ch == '\n') { + buf.append("\\n"); + } else if (ch == '\t') { + buf.append("\\t"); + } else if (ch == '\r') { + buf.append("\\r"); + } else { + String str = "000"+Integer.toHexString(ch); + buf.append("\\u").append(str.substring(str.length()-4)); + } + } + ps.println(buf); + } + } + + + // Utilities for reallocating: + protected static Object[] realloc(Object[] a, int len) { + Class elt = a.getClass().getComponentType(); + Object[] na = (Object[]) java.lang.reflect.Array.newInstance(elt, len); + System.arraycopy(a, 0, na, 0, Math.min(a.length, len)); + return na; + } + protected static Object[] realloc(Object[] a) { + return realloc(a, Math.max(10, a.length*2)); + } + + protected static int[] realloc(int[] a, int len) { + if (len == 0) return Constants.noInts; + if (a == null) return new int[len]; + int[] na = new int[len]; + System.arraycopy(a, 0, na, 0, Math.min(a.length, len)); + return na; + } + protected static int[] realloc(int[] a) { + return realloc(a, Math.max(10, a.length*2)); + } + + protected static byte[] realloc(byte[] a, int len) { + if (len == 0) return Constants.noBytes; + if (a == null) return new byte[len]; + byte[] na = new byte[len]; + System.arraycopy(a, 0, na, 0, Math.min(a.length, len)); + return na; + } + protected static byte[] realloc(byte[] a) { + return realloc(a, Math.max(10, a.length*2)); + } +} diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/ClassReader.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/ClassReader.java new file mode 100644 index 000000000..f55aba900 --- /dev/null +++ b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/ClassReader.java @@ -0,0 +1,636 @@ +/* + * Copyright (c) 2001, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package net.fabricmc.shade.com.sun.java.util.jar.pack; + +import java.io.DataInputStream; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Map; + +/** + * Reader for a class file that is being incorporated into a package. + * @author John Rose + */ +class ClassReader { + int verbose; + + Package pkg; + Package.Class cls; + long inPos; + long constantPoolLimit = -1; + DataInputStream in; + Map attrDefs; + Map attrCommands; + String unknownAttrCommand = "error";; + + ClassReader(Package.Class cls, InputStream in) throws IOException { + this.pkg = cls.getPackage(); + this.cls = cls; + this.verbose = pkg.verbose; + this.in = new DataInputStream(new FilterInputStream(in) { + public int read(byte b[], int off, int len) throws IOException { + int nr = super.read(b, off, len); + if (nr >= 0) inPos += nr; + return nr; + } + public int read() throws IOException { + int ch = super.read(); + if (ch >= 0) inPos += 1; + return ch; + } + public long skip(long n) throws IOException { + long ns = super.skip(n); + if (ns >= 0) inPos += ns; + return ns; + } + }); + } + + public void setAttrDefs(Map attrDefs) { + this.attrDefs = attrDefs; + } + + public void setAttrCommands(Map attrCommands) { + this.attrCommands = attrCommands; + } + + private void skip(int n, String what) throws IOException { + Utils.log.warning("skipping "+n+" bytes of "+what); + long skipped = 0; + while (skipped < n) { + long j = in.skip(n - skipped); + assert(j > 0); + skipped += j; + } + assert(skipped == n); + } + + private int readUnsignedShort() throws IOException { + return in.readUnsignedShort(); + } + + private int readInt() throws IOException { + return in.readInt(); + } + + /** Read a 2-byte int, and return the global CP entry for it. */ + private ConstantPool.Entry readRef() throws IOException { + int i = in.readUnsignedShort(); + return i == 0 ? null : cls.cpMap[i]; + } + + private ConstantPool.Entry readRef(byte tag) throws IOException { + ConstantPool.Entry e = readRef(); + assert(!(e instanceof UnresolvedEntry)); + checkTag(e, tag); + return e; + } + + private ConstantPool.Entry checkValid(ConstantPool.Entry e) { + if (e == INVALID_ENTRY) { + throw new IllegalStateException("Invalid constant pool reference"); + } + return e; + } + + /** Throw a ClassFormatException if the entry does not match the expected tag type. */ + private ConstantPool.Entry checkTag(ConstantPool.Entry e, byte tag) throws ClassFormatException { + if (e == null || !e.tagMatches(tag)) { + String where = (inPos == constantPoolLimit + ? " in constant pool" + : " at pos: " + inPos); + String got = (e == null + ? "null CP index" + : "type=" + ConstantPool.tagName(e.tag)); + throw new ClassFormatException("Bad constant, expected type=" + + ConstantPool.tagName(tag) + + " got "+ got + ", in File: " + cls.file.nameString + where); + } + return e; + } + private ConstantPool.Entry checkTag(ConstantPool.Entry e, byte tag, boolean nullOK) throws ClassFormatException { + return nullOK && e == null ? null : checkTag(e, tag); + } + + private ConstantPool.Entry readRefOrNull(byte tag) throws IOException { + ConstantPool.Entry e = readRef(); + checkTag(e, tag, true); + return e; + } + + private ConstantPool.Utf8Entry readUtf8Ref() throws IOException { + return (ConstantPool.Utf8Entry) readRef(Constants.CONSTANT_Utf8); + } + + private ConstantPool.ClassEntry readClassRef() throws IOException { + return (ConstantPool.ClassEntry) readRef(Constants.CONSTANT_Class); + } + + private ConstantPool.ClassEntry readClassRefOrNull() throws IOException { + return (ConstantPool.ClassEntry) readRefOrNull(Constants.CONSTANT_Class); + } + + private ConstantPool.SignatureEntry readSignatureRef() throws IOException { + // The class file stores a Utf8, but we want a Signature. + ConstantPool.Entry e = readRef(Constants.CONSTANT_Signature); + return (e != null && e.getTag() == Constants.CONSTANT_Utf8) + ? ConstantPool.getSignatureEntry(e.stringValue()) + : (ConstantPool.SignatureEntry) e; + } + + void read() throws IOException { + boolean ok = false; + try { + readMagicNumbers(); + readConstantPool(); + readHeader(); + readMembers(false); // fields + readMembers(true); // methods + readAttributes(Constants.ATTR_CONTEXT_CLASS, cls); + fixUnresolvedEntries(); + cls.finishReading(); + assert(0 >= in.read(new byte[1])); + ok = true; + } finally { + if (!ok) { + if (verbose > 0) Utils.log.warning("Erroneous data at input offset "+inPos+" of "+cls.file); + } + } + } + + void readMagicNumbers() throws IOException { + cls.magic = in.readInt(); + if (cls.magic != Constants.JAVA_MAGIC) + throw new Attribute.FormatException + ("Bad magic number in class file " + +Integer.toHexString(cls.magic), + Constants.ATTR_CONTEXT_CLASS, "magic-number", "pass"); + int minver = (short) readUnsignedShort(); + int majver = (short) readUnsignedShort(); + cls.version = Package.Version.of(majver, minver); + + //System.out.println("ClassFile.version="+cls.majver+"."+cls.minver); + String bad = checkVersion(cls.version); + if (bad != null) { + throw new Attribute.FormatException + ("classfile version too "+bad+": " + +cls.version+" in "+cls.file, + Constants.ATTR_CONTEXT_CLASS, "version", "pass"); + } + } + + private String checkVersion(Package.Version ver) { + int majver = ver.major; + int minver = ver.minor; + if (majver < pkg.minClassVersion.major || + (majver == pkg.minClassVersion.major && + minver < pkg.minClassVersion.minor)) { + return "small"; + } + if (majver > pkg.maxClassVersion.major || + (majver == pkg.maxClassVersion.major && + minver > pkg.maxClassVersion.minor)) { + return "large"; + } + return null; // OK + } + + // use this identity for invalid references + private static final ConstantPool.Entry INVALID_ENTRY = new ConstantPool.Entry((byte) -1) { + @Override + public boolean equals(Object o) { + throw new IllegalStateException("Should not call this"); + } + + @Override + protected int computeValueHash() { + throw new IllegalStateException("Should not call this"); + } + + @Override + public int compareTo(Object o) { + throw new IllegalStateException("Should not call this"); + } + + @Override + public String stringValue() { + throw new IllegalStateException("Should not call this"); + } + }; + + void readConstantPool() throws IOException { + int length = in.readUnsignedShort(); + //System.err.println("reading CP, length="+length); + + int[] fixups = new int[length*4]; + int fptr = 0; + + ConstantPool.Entry[] cpMap = new ConstantPool.Entry[length]; + cpMap[0] = INVALID_ENTRY; + for (int i = 1; i < length; i++) { + //System.err.println("reading CP elt, i="+i); + int tag = in.readByte(); + switch (tag) { + case Constants.CONSTANT_Utf8: + cpMap[i] = ConstantPool.getUtf8Entry(in.readUTF()); + break; + case Constants.CONSTANT_Integer: + { + cpMap[i] = ConstantPool.getLiteralEntry(in.readInt()); + } + break; + case Constants.CONSTANT_Float: + { + cpMap[i] = ConstantPool.getLiteralEntry(in.readFloat()); + } + break; + case Constants.CONSTANT_Long: + { + cpMap[i] = ConstantPool.getLiteralEntry(in.readLong()); + cpMap[++i] = INVALID_ENTRY; + } + break; + case Constants.CONSTANT_Double: + { + cpMap[i] = ConstantPool.getLiteralEntry(in.readDouble()); + cpMap[++i] = INVALID_ENTRY; + } + break; + + // just read the refs; do not attempt to resolve while reading + case Constants.CONSTANT_Class: + case Constants.CONSTANT_String: + case Constants.CONSTANT_MethodType: + fixups[fptr++] = i; + fixups[fptr++] = tag; + fixups[fptr++] = in.readUnsignedShort(); + fixups[fptr++] = -1; // empty ref2 + break; + case Constants.CONSTANT_Fieldref: + case Constants.CONSTANT_Methodref: + case Constants.CONSTANT_InterfaceMethodref: + case Constants.CONSTANT_NameandType: + fixups[fptr++] = i; + fixups[fptr++] = tag; + fixups[fptr++] = in.readUnsignedShort(); + fixups[fptr++] = in.readUnsignedShort(); + break; + case Constants.CONSTANT_InvokeDynamic: + fixups[fptr++] = i; + fixups[fptr++] = tag; + fixups[fptr++] = -1 ^ in.readUnsignedShort(); // not a ref + fixups[fptr++] = in.readUnsignedShort(); + break; + case Constants.CONSTANT_MethodHandle: + fixups[fptr++] = i; + fixups[fptr++] = tag; + fixups[fptr++] = -1 ^ in.readUnsignedByte(); + fixups[fptr++] = in.readUnsignedShort(); + break; + default: + throw new ClassFormatException("Bad constant pool tag " + + tag + " in File: " + cls.file.nameString + + " at pos: " + inPos); + } + } + constantPoolLimit = inPos; + + // Fix up refs, which might be out of order. + while (fptr > 0) { + if (verbose > 3) + Utils.log.fine("CP fixups ["+fptr/4+"]"); + int flimit = fptr; + fptr = 0; + for (int fi = 0; fi < flimit; ) { + int cpi = fixups[fi++]; + int tag = fixups[fi++]; + int ref = fixups[fi++]; + int ref2 = fixups[fi++]; + if (verbose > 3) + Utils.log.fine(" cp["+cpi+"] = "+ConstantPool.tagName(tag)+"{"+ref+","+ref2+"}"); + if (ref >= 0 && checkValid(cpMap[ref]) == null || ref2 >= 0 && checkValid(cpMap[ref2]) == null) { + // Defer. + fixups[fptr++] = cpi; + fixups[fptr++] = tag; + fixups[fptr++] = ref; + fixups[fptr++] = ref2; + continue; + } + switch (tag) { + case Constants.CONSTANT_Class: + cpMap[cpi] = ConstantPool.getClassEntry(cpMap[ref].stringValue()); + break; + case Constants.CONSTANT_String: + cpMap[cpi] = ConstantPool.getStringEntry(cpMap[ref].stringValue()); + break; + case Constants.CONSTANT_Fieldref: + case Constants.CONSTANT_Methodref: + case Constants.CONSTANT_InterfaceMethodref: + ConstantPool.ClassEntry mclass = (ConstantPool.ClassEntry) checkTag(cpMap[ref], Constants.CONSTANT_Class); + ConstantPool.DescriptorEntry mdescr = (ConstantPool.DescriptorEntry) checkTag(cpMap[ref2], Constants.CONSTANT_NameandType); + cpMap[cpi] = ConstantPool.getMemberEntry((byte)tag, mclass, mdescr); + break; + case Constants.CONSTANT_NameandType: + ConstantPool.Utf8Entry mname = (ConstantPool.Utf8Entry) checkTag(cpMap[ref], Constants.CONSTANT_Utf8); + ConstantPool.Utf8Entry mtype = (ConstantPool.Utf8Entry) checkTag(cpMap[ref2], Constants.CONSTANT_Signature); + cpMap[cpi] = ConstantPool.getDescriptorEntry(mname, mtype); + break; + case Constants.CONSTANT_MethodType: + cpMap[cpi] = ConstantPool.getMethodTypeEntry((ConstantPool.Utf8Entry) checkTag(cpMap[ref], Constants.CONSTANT_Signature)); + break; + case Constants.CONSTANT_MethodHandle: + byte refKind = (byte)(-1 ^ ref); + ConstantPool.MemberEntry memRef = (ConstantPool.MemberEntry) checkTag(cpMap[ref2], Constants.CONSTANT_AnyMember); + cpMap[cpi] = ConstantPool.getMethodHandleEntry(refKind, memRef); + break; + case Constants.CONSTANT_InvokeDynamic: + ConstantPool.DescriptorEntry idescr = (ConstantPool.DescriptorEntry) checkTag(cpMap[ref2], Constants.CONSTANT_NameandType); + cpMap[cpi] = new UnresolvedEntry((byte)tag, (-1 ^ ref), idescr); + // Note that ref must be resolved later, using the BootstrapMethods attribute. + break; + default: + assert(false); + } + } + assert(fptr < flimit); // Must make progress. + } + + cls.cpMap = cpMap; + } + private /*non-static*/ + class UnresolvedEntry extends ConstantPool.Entry { + final Object[] refsOrIndexes; + UnresolvedEntry(byte tag, Object... refsOrIndexes) { + super(tag); + this.refsOrIndexes = refsOrIndexes; + ClassReader.this.haveUnresolvedEntry = true; + } + ConstantPool.Entry resolve() { + Package.Class cls = ClassReader.this.cls; + ConstantPool.Entry res; + switch (tag) { + case Constants.CONSTANT_InvokeDynamic: + ConstantPool.BootstrapMethodEntry iboots = cls.bootstrapMethods.get((Integer) refsOrIndexes[0]); + ConstantPool.DescriptorEntry idescr = (ConstantPool.DescriptorEntry) refsOrIndexes[1]; + res = ConstantPool.getInvokeDynamicEntry(iboots, idescr); + break; + default: + throw new AssertionError(); + } + return res; + } + private void unresolved() { throw new RuntimeException("unresolved entry has no string"); } + public int compareTo(Object x) { unresolved(); return 0; } + public boolean equals(Object x) { unresolved(); return false; } + protected int computeValueHash() { unresolved(); return 0; } + public String stringValue() { unresolved(); return toString(); } + public String toString() { return "(unresolved "+ConstantPool.tagName(tag)+")"; } + } + + boolean haveUnresolvedEntry; + private void fixUnresolvedEntries() { + if (!haveUnresolvedEntry) return; + ConstantPool.Entry[] cpMap = cls.getCPMap(); + for (int i = 0; i < cpMap.length; i++) { + ConstantPool.Entry e = cpMap[i]; + if (e instanceof UnresolvedEntry) { + cpMap[i] = e = ((UnresolvedEntry)e).resolve(); + assert(!(e instanceof UnresolvedEntry)); + } + } + haveUnresolvedEntry = false; + } + + void readHeader() throws IOException { + cls.flags = readUnsignedShort(); + cls.thisClass = readClassRef(); + cls.superClass = readClassRefOrNull(); + int ni = readUnsignedShort(); + cls.interfaces = new ConstantPool.ClassEntry[ni]; + for (int i = 0; i < ni; i++) { + cls.interfaces[i] = readClassRef(); + } + } + + void readMembers(boolean doMethods) throws IOException { + int nm = readUnsignedShort(); + for (int i = 0; i < nm; i++) { + readMember(doMethods); + } + } + + void readMember(boolean doMethod) throws IOException { + int mflags = readUnsignedShort(); + ConstantPool.Utf8Entry mname = readUtf8Ref(); + ConstantPool.SignatureEntry mtype = readSignatureRef(); + ConstantPool.DescriptorEntry descr = ConstantPool.getDescriptorEntry(mname, mtype); + Package.Class.Member m; + if (!doMethod) + m = cls.new Field(mflags, descr); + else + m = cls.new Method(mflags, descr); + readAttributes(!doMethod ? Constants.ATTR_CONTEXT_FIELD : Constants.ATTR_CONTEXT_METHOD, + m); + } + void readAttributes(int ctype, Attribute.Holder h) throws IOException { + int na = readUnsignedShort(); + if (na == 0) return; // nothing to do here + if (verbose > 3) + Utils.log.fine("readAttributes "+h+" ["+na+"]"); + for (int i = 0; i < na; i++) { + String name = readUtf8Ref().stringValue(); + int length = readInt(); + // See if there is a special command that applies. + if (attrCommands != null) { + Attribute.Layout lkey = Attribute.keyForLookup(ctype, name); + String cmd = attrCommands.get(lkey); + if (cmd != null) { + switch (cmd) { + case "pass": + String message1 = "passing attribute bitwise in " + h; + throw new Attribute.FormatException(message1, ctype, name, cmd); + case "error": + String message2 = "attribute not allowed in " + h; + throw new Attribute.FormatException(message2, ctype, name, cmd); + case "strip": + skip(length, name + " attribute in " + h); + continue; + } + } + } + // Find canonical instance of the requested attribute. + Attribute a = Attribute.lookup(Package.attrDefs, ctype, name); + if (verbose > 4 && a != null) + Utils.log.fine("pkg_attribute_lookup "+name+" = "+a); + if (a == null) { + a = Attribute.lookup(this.attrDefs, ctype, name); + if (verbose > 4 && a != null) + Utils.log.fine("this "+name+" = "+a); + } + if (a == null) { + a = Attribute.lookup(null, ctype, name); + if (verbose > 4 && a != null) + Utils.log.fine("null_attribute_lookup "+name+" = "+a); + } + if (a == null && length == 0) { + // Any zero-length attr is "known"... + // We can assume an empty attr. has an empty layout. + // Handles markers like Enum, Bridge, Synthetic, Deprecated. + a = Attribute.find(ctype, name, ""); + } + boolean isStackMap = (ctype == Constants.ATTR_CONTEXT_CODE + && (name.equals("StackMap") || + name.equals("StackMapX"))); + if (isStackMap) { + // Known attribute but with a corner case format, "pass" it. + Code code = (Code) h; + final int TOO_BIG = 0x10000; + if (code.max_stack >= TOO_BIG || + code.max_locals >= TOO_BIG || + code.getLength() >= TOO_BIG || + name.endsWith("X")) { + // No, we don't really know what to do with this one. + // Do not compress the rare and strange "u4" and "X" cases. + a = null; + } + } + if (a == null) { + if (isStackMap) { + // Known attribute but w/o a format; pass it. + String message = "unsupported StackMap variant in "+h; + throw new Attribute.FormatException(message, ctype, name, + "pass"); + } else if ("strip".equals(unknownAttrCommand)) { + // Skip the unknown attribute. + skip(length, "unknown "+name+" attribute in "+h); + continue; + } else { + String message = " is unknown attribute in class " + h; + throw new Attribute.FormatException(message, ctype, name, + unknownAttrCommand); + } + } + long pos0 = inPos; // in case we want to check it + if (a.layout() == Package.attrCodeEmpty) { + // These are hardwired. + Package.Class.Method m = (Package.Class.Method) h; + m.code = new Code(m); + try { + readCode(m.code); + } catch (Instruction.FormatException iie) { + String message = iie.getMessage() + " in " + h; + throw new ClassFormatException(message, iie); + } + assert(length == inPos - pos0); + // Keep empty attribute a... + } else if (a.layout() == Package.attrBootstrapMethodsEmpty) { + assert(h == cls); + readBootstrapMethods(cls); + assert(length == inPos - pos0); + // Delete the attribute; it is logically part of the constant pool. + continue; + } else if (a.layout() == Package.attrInnerClassesEmpty) { + // These are hardwired also. + assert(h == cls); + readInnerClasses(cls); + assert(length == inPos - pos0); + // Keep empty attribute a... + } else if (length > 0) { + byte[] bytes = new byte[length]; + in.readFully(bytes); + a = a.addContent(bytes); + } + if (a.size() == 0 && !a.layout().isEmpty()) { + throw new ClassFormatException(name + + ": attribute length cannot be zero, in " + h); + } + h.addAttribute(a); + if (verbose > 2) + Utils.log.fine("read "+a); + } + } + + void readCode(Code code) throws IOException { + code.max_stack = readUnsignedShort(); + code.max_locals = readUnsignedShort(); + code.bytes = new byte[readInt()]; + in.readFully(code.bytes); + ConstantPool.Entry[] cpMap = cls.getCPMap(); + Instruction.opcodeChecker(code.bytes, cpMap, this.cls.version); + int nh = readUnsignedShort(); + code.setHandlerCount(nh); + for (int i = 0; i < nh; i++) { + code.handler_start[i] = readUnsignedShort(); + code.handler_end[i] = readUnsignedShort(); + code.handler_catch[i] = readUnsignedShort(); + code.handler_class[i] = readClassRefOrNull(); + } + readAttributes(Constants.ATTR_CONTEXT_CODE, code); + } + + void readBootstrapMethods(Package.Class cls) throws IOException { + ConstantPool.BootstrapMethodEntry[] bsms = new ConstantPool.BootstrapMethodEntry[readUnsignedShort()]; + for (int i = 0; i < bsms.length; i++) { + ConstantPool.MethodHandleEntry bsmRef = (ConstantPool.MethodHandleEntry) readRef(Constants.CONSTANT_MethodHandle); + ConstantPool.Entry[] argRefs = new ConstantPool.Entry[readUnsignedShort()]; + for (int j = 0; j < argRefs.length; j++) { + argRefs[j] = readRef(Constants.CONSTANT_LoadableValue); + } + bsms[i] = ConstantPool.getBootstrapMethodEntry(bsmRef, argRefs); + } + cls.setBootstrapMethods(Arrays.asList(bsms)); + } + + void readInnerClasses(Package.Class cls) throws IOException { + int nc = readUnsignedShort(); + ArrayList ics = new ArrayList<>(nc); + for (int i = 0; i < nc; i++) { + Package.InnerClass ic = + new Package.InnerClass(readClassRef(), + readClassRefOrNull(), + (ConstantPool.Utf8Entry)readRefOrNull(Constants.CONSTANT_Utf8), + readUnsignedShort()); + ics.add(ic); + } + cls.innerClasses = ics; // set directly; do not use setInnerClasses. + // (Later, ics may be transferred to the pkg.) + } + + static class ClassFormatException extends IOException { + private static final long serialVersionUID = -3564121733989501833L; + + public ClassFormatException(String message) { + super(message); + } + + public ClassFormatException(String message, Throwable cause) { + super(message, cause); + } + } +} diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/ClassWriter.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/ClassWriter.java new file mode 100644 index 000000000..503be0c95 --- /dev/null +++ b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/ClassWriter.java @@ -0,0 +1,310 @@ +/* + * Copyright (c) 2001, 2012, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package net.fabricmc.shade.com.sun.java.util.jar.pack; + + +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.List; + +/** + * Writer for a class file that is incorporated into a package. + * @author John Rose + */ +class ClassWriter { + int verbose; + + Package pkg; + Package.Class cls; + DataOutputStream out; + ConstantPool.Index cpIndex; + ConstantPool.Index bsmIndex; + + ClassWriter(Package.Class cls, OutputStream out) throws IOException { + this.pkg = cls.getPackage(); + this.cls = cls; + this.verbose = pkg.verbose; + this.out = new DataOutputStream(new BufferedOutputStream(out)); + this.cpIndex = ConstantPool.makeIndex(cls.toString(), cls.getCPMap()); + this.cpIndex.flattenSigs = true; + if (cls.hasBootstrapMethods()) { + this.bsmIndex = ConstantPool.makeIndex(cpIndex.debugName+".BootstrapMethods", + cls.getBootstrapMethodMap()); + } + if (verbose > 1) + Utils.log.fine("local CP="+(verbose > 2 ? cpIndex.dumpString() : cpIndex.toString())); + } + + private void writeShort(int x) throws IOException { + out.writeShort(x); + } + + private void writeInt(int x) throws IOException { + out.writeInt(x); + } + + /** Write a 2-byte int representing a CP entry, using the local cpIndex. */ + private void writeRef(ConstantPool.Entry e) throws IOException { + writeRef(e, cpIndex); + } + + /** Write a 2-byte int representing a CP entry, using the given cpIndex. */ + private void writeRef(ConstantPool.Entry e, ConstantPool.Index cpIndex) throws IOException { + int i = (e == null) ? 0 : cpIndex.indexOf(e); + writeShort(i); + } + + void write() throws IOException { + boolean ok = false; + try { + if (verbose > 1) Utils.log.fine("...writing "+cls); + writeMagicNumbers(); + writeConstantPool(); + writeHeader(); + writeMembers(false); // fields + writeMembers(true); // methods + writeAttributes(Constants.ATTR_CONTEXT_CLASS, cls); + /* Closing here will cause all the underlying + streams to close, Causing the jar stream + to close prematurely, instead we just flush. + out.close(); + */ + out.flush(); + ok = true; + } finally { + if (!ok) { + Utils.log.warning("Error on output of "+cls); + } + } + } + + void writeMagicNumbers() throws IOException { + writeInt(cls.magic); + writeShort(cls.version.minor); + writeShort(cls.version.major); + } + + void writeConstantPool() throws IOException { + ConstantPool.Entry[] cpMap = cls.cpMap; + writeShort(cpMap.length); + for (int i = 0; i < cpMap.length; i++) { + ConstantPool.Entry e = cpMap[i]; + assert((e == null) == (i == 0 || cpMap[i-1] != null && cpMap[i-1].isDoubleWord())); + if (e == null) continue; + byte tag = e.getTag(); + if (verbose > 2) Utils.log.fine(" CP["+i+"] = "+e); + out.write(tag); + switch (tag) { + case Constants.CONSTANT_Signature: + throw new AssertionError("CP should have Signatures remapped to Utf8"); + case Constants.CONSTANT_Utf8: + out.writeUTF(e.stringValue()); + break; + case Constants.CONSTANT_Integer: + out.writeInt(((ConstantPool.NumberEntry)e).numberValue().intValue()); + break; + case Constants.CONSTANT_Float: + float fval = ((ConstantPool.NumberEntry)e).numberValue().floatValue(); + out.writeInt(Float.floatToRawIntBits(fval)); + break; + case Constants.CONSTANT_Long: + out.writeLong(((ConstantPool.NumberEntry)e).numberValue().longValue()); + break; + case Constants.CONSTANT_Double: + double dval = ((ConstantPool.NumberEntry)e).numberValue().doubleValue(); + out.writeLong(Double.doubleToRawLongBits(dval)); + break; + case Constants.CONSTANT_Class: + case Constants.CONSTANT_String: + case Constants.CONSTANT_MethodType: + writeRef(e.getRef(0)); + break; + case Constants.CONSTANT_MethodHandle: + ConstantPool.MethodHandleEntry mhe = (ConstantPool.MethodHandleEntry) e; + out.writeByte(mhe.refKind); + writeRef(mhe.getRef(0)); + break; + case Constants.CONSTANT_Fieldref: + case Constants.CONSTANT_Methodref: + case Constants.CONSTANT_InterfaceMethodref: + case Constants.CONSTANT_NameandType: + writeRef(e.getRef(0)); + writeRef(e.getRef(1)); + break; + case Constants.CONSTANT_InvokeDynamic: + writeRef(e.getRef(0), bsmIndex); + writeRef(e.getRef(1)); + break; + case Constants.CONSTANT_BootstrapMethod: + throw new AssertionError("CP should have BootstrapMethods moved to side-table"); + default: + throw new IOException("Bad constant pool tag "+tag); + } + } + } + + void writeHeader() throws IOException { + writeShort(cls.flags); + writeRef(cls.thisClass); + writeRef(cls.superClass); + writeShort(cls.interfaces.length); + for (int i = 0; i < cls.interfaces.length; i++) { + writeRef(cls.interfaces[i]); + } + } + + void writeMembers(boolean doMethods) throws IOException { + List mems; + if (!doMethods) + mems = cls.getFields(); + else + mems = cls.getMethods(); + writeShort(mems.size()); + for (Package.Class.Member m : mems) { + writeMember(m, doMethods); + } + } + + void writeMember(Package.Class.Member m, boolean doMethod) throws IOException { + if (verbose > 2) Utils.log.fine("writeMember "+m); + writeShort(m.flags); + writeRef(m.getDescriptor().nameRef); + writeRef(m.getDescriptor().typeRef); + writeAttributes(!doMethod ? Constants.ATTR_CONTEXT_FIELD : Constants.ATTR_CONTEXT_METHOD, + m); + } + + private void reorderBSMandICS(Attribute.Holder h) { + Attribute bsmAttr = h.getAttribute(Package.attrBootstrapMethodsEmpty); + if (bsmAttr == null) return; + + Attribute icsAttr = h.getAttribute(Package.attrInnerClassesEmpty); + if (icsAttr == null) return; + + int bsmidx = h.attributes.indexOf(bsmAttr); + int icsidx = h.attributes.indexOf(icsAttr); + if (bsmidx > icsidx) { + h.attributes.remove(bsmAttr); + h.attributes.add(icsidx, bsmAttr); + } + return; + } + + // handy buffer for collecting attrs + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + DataOutputStream bufOut = new DataOutputStream(buf); + + void writeAttributes(int ctype, Attribute.Holder h) throws IOException { + if (h.attributes == null) { + writeShort(0); // attribute size + return; + } + // there may be cases if an InnerClass attribute is explicit, then the + // ordering could be wrong, fix the ordering before we write it out. + if (h instanceof Package.Class) + reorderBSMandICS(h); + + writeShort(h.attributes.size()); + for (Attribute a : h.attributes) { + a.finishRefs(cpIndex); + writeRef(a.getNameRef()); + if (a.layout() == Package.attrCodeEmpty || + a.layout() == Package.attrBootstrapMethodsEmpty || + a.layout() == Package.attrInnerClassesEmpty) { + // These are hardwired. + DataOutputStream savedOut = out; + assert(out != bufOut); + buf.reset(); + out = bufOut; + if ("Code".equals(a.name())) { + Package.Class.Method m = (Package.Class.Method) h; + writeCode(m.code); + } else if ("BootstrapMethods".equals(a.name())) { + assert(h == cls); + writeBootstrapMethods(cls); + } else if ("InnerClasses".equals(a.name())) { + assert(h == cls); + writeInnerClasses(cls); + } else { + throw new AssertionError(); + } + out = savedOut; + if (verbose > 2) + Utils.log.fine("Attribute "+a.name()+" ["+buf.size()+"]"); + writeInt(buf.size()); + buf.writeTo(out); + } else { + if (verbose > 2) + Utils.log.fine("Attribute "+a.name()+" ["+a.size()+"]"); + writeInt(a.size()); + out.write(a.bytes()); + } + } + } + + void writeCode(Code code) throws IOException { + code.finishRefs(cpIndex); + writeShort(code.max_stack); + writeShort(code.max_locals); + writeInt(code.bytes.length); + out.write(code.bytes); + int nh = code.getHandlerCount(); + writeShort(nh); + for (int i = 0; i < nh; i++) { + writeShort(code.handler_start[i]); + writeShort(code.handler_end[i]); + writeShort(code.handler_catch[i]); + writeRef(code.handler_class[i]); + } + writeAttributes(Constants.ATTR_CONTEXT_CODE, code); + } + + void writeBootstrapMethods(Package.Class cls) throws IOException { + List bsms = cls.getBootstrapMethods(); + writeShort(bsms.size()); + for (ConstantPool.BootstrapMethodEntry e : bsms) { + writeRef(e.bsmRef); + writeShort(e.argRefs.length); + for (ConstantPool.Entry argRef : e.argRefs) { + writeRef(argRef); + } + } + } + + void writeInnerClasses(Package.Class cls) throws IOException { + List ics = cls.getInnerClasses(); + writeShort(ics.size()); + for (Package.InnerClass ic : ics) { + writeRef(ic.thisClass); + writeRef(ic.outerClass); + writeRef(ic.name); + writeShort(ic.flags); + } + } +} diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Code.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Code.java new file mode 100644 index 000000000..424bd7f57 --- /dev/null +++ b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Code.java @@ -0,0 +1,396 @@ +/* + * Copyright (c) 2001, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package net.fabricmc.shade.com.sun.java.util.jar.pack; + +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.Collection; + +/** + * Represents a chunk of bytecodes. + * @author John Rose + */ +class Code extends Attribute.Holder { + Package.Class.Method m; + + public Code(Package.Class.Method m) { + this.m = m; + } + + public Package.Class.Method getMethod() { + return m; + } + public Package.Class thisClass() { + return m.thisClass(); + } + public Package getPackage() { + return m.thisClass().getPackage(); + } + + public ConstantPool.Entry[] getCPMap() { + return m.getCPMap(); + } + + private static final ConstantPool.Entry[] noRefs = ConstantPool.noRefs; + + // The following fields are used directly by the ClassReader, etc. + int max_stack; + int max_locals; + + ConstantPool.Entry handler_class[] = noRefs; + int handler_start[] = Constants.noInts; + int handler_end[] = Constants.noInts; + int handler_catch[] = Constants.noInts; + + byte[] bytes; + Fixups fixups; // reference relocations, if any are required + Object insnMap; // array of instruction boundaries + + int getLength() { return bytes.length; } + + int getMaxStack() { + return max_stack; + } + void setMaxStack(int ms) { + max_stack = ms; + } + + int getMaxNALocals() { + int argsize = m.getArgumentSize(); + return max_locals - argsize; + } + void setMaxNALocals(int ml) { + int argsize = m.getArgumentSize(); + max_locals = argsize + ml; + } + + int getHandlerCount() { + assert(handler_class.length == handler_start.length); + assert(handler_class.length == handler_end.length); + assert(handler_class.length == handler_catch.length); + return handler_class.length; + } + void setHandlerCount(int h) { + if (h > 0) { + handler_class = new ConstantPool.Entry[h]; + handler_start = new int[h]; + handler_end = new int[h]; + handler_catch = new int[h]; + // caller must fill these in ASAP + } + } + + void setBytes(byte[] bytes) { + this.bytes = bytes; + if (fixups != null) + fixups.setBytes(bytes); + } + + void setInstructionMap(int[] insnMap, int mapLen) { + //int[] oldMap = null; + //assert((oldMap = getInstructionMap()) != null); + this.insnMap = allocateInstructionMap(insnMap, mapLen); + //assert(Arrays.equals(oldMap, getInstructionMap())); + } + void setInstructionMap(int[] insnMap) { + setInstructionMap(insnMap, insnMap.length); + } + + int[] getInstructionMap() { + return expandInstructionMap(getInsnMap()); + } + + void addFixups(Collection moreFixups) { + if (fixups == null) { + fixups = new Fixups(bytes); + } + assert(fixups.getBytes() == bytes); + fixups.addAll(moreFixups); + } + + public void trimToSize() { + if (fixups != null) { + fixups.trimToSize(); + if (fixups.size() == 0) + fixups = null; + } + super.trimToSize(); + } + + protected void visitRefs(int mode, Collection refs) { + int verbose = getPackage().verbose; + if (verbose > 2) + System.out.println("Reference scan "+this); + refs.addAll(Arrays.asList(handler_class)); + if (fixups != null) { + fixups.visitRefs(refs); + } else { + // References (to a local cpMap) are embedded in the bytes. + ConstantPool.Entry[] cpMap = getCPMap(); + for (Instruction i = instructionAt(0); i != null; i = i.next()) { + if (verbose > 4) + System.out.println(i); + int cpref = i.getCPIndex(); + if (cpref >= 0) { + refs.add(cpMap[cpref]); + } + } + } + // Handle attribute list: + super.visitRefs(mode, refs); + } + + // Since bytecodes are the single largest contributor to + // package size, it's worth a little bit of trouble + // to reduce the per-bytecode memory footprint. + // In the current scheme, half of the bulk of these arrays + // due to bytes, and half to shorts. (Ints are insignificant.) + // Given an average of 1.8 bytes per instruction, this means + // instruction boundary arrays are about a 75% overhead--tolerable. + // (By using bytes, we get 33% savings over just shorts and ints. + // Using both bytes and shorts gives 66% savings over just ints.) + static final boolean shrinkMaps = true; + + private Object allocateInstructionMap(int[] insnMap, int mapLen) { + int PClimit = getLength(); + if (shrinkMaps && PClimit <= Byte.MAX_VALUE - Byte.MIN_VALUE) { + byte[] map = new byte[mapLen+1]; + for (int i = 0; i < mapLen; i++) { + map[i] = (byte)(insnMap[i] + Byte.MIN_VALUE); + } + map[mapLen] = (byte)(PClimit + Byte.MIN_VALUE); + return map; + } else if (shrinkMaps && PClimit < Short.MAX_VALUE - Short.MIN_VALUE) { + short[] map = new short[mapLen+1]; + for (int i = 0; i < mapLen; i++) { + map[i] = (short)(insnMap[i] + Short.MIN_VALUE); + } + map[mapLen] = (short)(PClimit + Short.MIN_VALUE); + return map; + } else { + int[] map = Arrays.copyOf(insnMap, mapLen + 1); + map[mapLen] = PClimit; + return map; + } + } + private int[] expandInstructionMap(Object map0) { + int[] imap; + if (map0 instanceof byte[]) { + byte[] map = (byte[]) map0; + imap = new int[map.length-1]; + for (int i = 0; i < imap.length; i++) { + imap[i] = map[i] - Byte.MIN_VALUE; + } + } else if (map0 instanceof short[]) { + short[] map = (short[]) map0; + imap = new int[map.length-1]; + for (int i = 0; i < imap.length; i++) { + imap[i] = map[i] - Byte.MIN_VALUE; + } + } else { + int[] map = (int[]) map0; + imap = Arrays.copyOfRange(map, 0, map.length - 1); + } + return imap; + } + + Object getInsnMap() { + // Build a map of instruction boundaries. + if (insnMap != null) { + return insnMap; + } + int[] map = new int[getLength()]; + int fillp = 0; + for (Instruction i = instructionAt(0); i != null; i = i.next()) { + map[fillp++] = i.getPC(); + } + // Make it byte[], short[], or int[] according to the max BCI. + insnMap = allocateInstructionMap(map, fillp); + //assert(assertBCICodingsOK()); + return insnMap; + } + + /** Encode the given BCI as an instruction boundary number. + * For completeness, irregular (non-boundary) BCIs are + * encoded compactly immediately after the boundary numbers. + * This encoding is the identity mapping outside 0..length, + * and it is 1-1 everywhere. All by itself this technique + * improved zipped rt.jar compression by 2.6%. + */ + public int encodeBCI(int bci) { + if (bci <= 0 || bci > getLength()) return bci; + Object map0 = getInsnMap(); + int i, len; + if (shrinkMaps && map0 instanceof byte[]) { + byte[] map = (byte[]) map0; + len = map.length; + i = Arrays.binarySearch(map, (byte)(bci + Byte.MIN_VALUE)); + } else if (shrinkMaps && map0 instanceof short[]) { + short[] map = (short[]) map0; + len = map.length; + i = Arrays.binarySearch(map, (short)(bci + Short.MIN_VALUE)); + } else { + int[] map = (int[]) map0; + len = map.length; + i = Arrays.binarySearch(map, bci); + } + assert(i != -1); + assert(i != 0); + assert(i != len); + assert(i != -len-1); + return (i >= 0) ? i : len + bci - (-i-1); + } + public int decodeBCI(int bciCode) { + if (bciCode <= 0 || bciCode > getLength()) return bciCode; + Object map0 = getInsnMap(); + int i, len; + // len == map.length + // If bciCode < len, result is map[bciCode], the common and fast case. + // Otherwise, let map[i] be the smallest map[*] larger than bci. + // Then, required by the return statement of encodeBCI: + // bciCode == len + bci - i + // Thus: + // bci-i == bciCode-len + // map[i]-adj-i == bciCode-len ; adj in (0..map[i]-map[i-1]) + // We can solve this by searching for adjacent entries + // map[i-1], map[i] such that: + // map[i-1]-(i-1) <= bciCode-len < map[i]-i + // This can be approximated by searching map[i] for bciCode and then + // linear searching backward. Given the right i, we then have: + // bci == bciCode-len + i + // This linear search is at its worst case for indexes in the beginning + // of a large method, but it's not clear that this is a problem in + // practice, since BCIs are usually on instruction boundaries. + if (shrinkMaps && map0 instanceof byte[]) { + byte[] map = (byte[]) map0; + len = map.length; + if (bciCode < len) + return map[bciCode] - Byte.MIN_VALUE; + i = Arrays.binarySearch(map, (byte)(bciCode + Byte.MIN_VALUE)); + if (i < 0) i = -i-1; + int key = bciCode-len + Byte.MIN_VALUE; + for (;; i--) { + if (map[i-1]-(i-1) <= key) break; + } + } else if (shrinkMaps && map0 instanceof short[]) { + short[] map = (short[]) map0; + len = map.length; + if (bciCode < len) + return map[bciCode] - Short.MIN_VALUE; + i = Arrays.binarySearch(map, (short)(bciCode + Short.MIN_VALUE)); + if (i < 0) i = -i-1; + int key = bciCode-len + Short.MIN_VALUE; + for (;; i--) { + if (map[i-1]-(i-1) <= key) break; + } + } else { + int[] map = (int[]) map0; + len = map.length; + if (bciCode < len) + return map[bciCode]; + i = Arrays.binarySearch(map, bciCode); + if (i < 0) i = -i-1; + int key = bciCode-len; + for (;; i--) { + if (map[i-1]-(i-1) <= key) break; + } + } + return bciCode-len + i; + } + + public void finishRefs(ConstantPool.Index ix) { + if (fixups != null) { + fixups.finishRefs(ix); + fixups = null; + } + // Code attributes are finished in ClassWriter.writeAttributes. + } + + Instruction instructionAt(int pc) { + return Instruction.at(bytes, pc); + } + + static boolean flagsRequireCode(int flags) { + // A method's flags force it to have a Code attribute, + // if the flags are neither native nor abstract. + return (flags & (Modifier.NATIVE | Modifier.ABSTRACT)) == 0; + } + + public String toString() { + return m+".Code"; + } + + /// Fetching values from my own array. + public int getInt(int pc) { return Instruction.getInt(bytes, pc); } + public int getShort(int pc) { return Instruction.getShort(bytes, pc); } + public int getByte(int pc) { return Instruction.getByte(bytes, pc); } + void setInt(int pc, int x) { Instruction.setInt(bytes, pc, x); } + void setShort(int pc, int x) { Instruction.setShort(bytes, pc, x); } + void setByte(int pc, int x) { Instruction.setByte(bytes, pc, x); } + +/* TEST CODE ONLY + private boolean assertBCICodingsOK() { + boolean ok = true; + int len = java.lang.reflect.Array.getLength(insnMap); + int base = 0; + if (insnMap.getClass().getComponentType() == Byte.TYPE) + base = Byte.MIN_VALUE; + if (insnMap.getClass().getComponentType() == Short.TYPE) + base = Short.MIN_VALUE; + for (int i = -1, imax = getLength()+1; i <= imax; i++) { + int bci = i; + int enc = Math.min(-999, bci-1); + int dec = enc; + try { + enc = encodeBCI(bci); + dec = decodeBCI(enc); + } catch (RuntimeException ee) { + ee.printStackTrace(); + } + if (dec == bci) { + //System.out.println("BCI="+bci+(enc, CodingMethod, Histogram.BitMetric { + /* + Coding schema for single integers, parameterized by (B,H,S): + + Let B in [1,5], H in [1,256], S in [0,3]. + (S limit is arbitrary. B follows the 32-bit limit. H is byte size.) + + A given (B,H,S) code varies in length from 1 to B bytes. + + The 256 values a byte may take on are divided into L=(256-H) and H + values, with all the H values larger than the L values. + (That is, the L values are [0,L) and the H are [L,256).) + + The last byte is always either the B-th byte, a byte with "L value" + (=L). + + Therefore, if L==0, the code always has the full length of B bytes. + The coding then becomes a classic B-byte little-endian unsigned integer. + (Also, if L==128, the high bit of each byte acts signals the presence + of a following byte, up to the maximum length.) + + In the unsigned case (S==0), the coding is compact and monotonic + in the ordering of byte sequences defined by appending zero bytes + to pad them to a common length B, reversing them, and ordering them + lexicographically. (This agrees with "little-endian" byte order.) + + Therefore, the unsigned value of a byte sequence may be defined as: +
+        U(b0)           == b0
+                           in [0..L)
+                           or [0..256) if B==1 (**)
+
+        U(b0,b1)        == b0 + b1*H
+                           in [L..L*(1+H))
+                           or [L..L*(1+H) + H^2) if B==2
+
+        U(b0,b1,b2)     == b0 + b1*H + b2*H^2
+                           in [L*(1+H)..L*(1+H+H^2))
+                           or [L*(1+H)..L*(1+H+H^2) + H^3) if B==3
+
+        U(b[i]: i
+
+      (**) If B==1, the values H,L play no role in the coding.
+      As a convention, we require that any (1,H,S) code must always
+      encode values less than H.  Thus, a simple unsigned byte is coded
+      specifically by the code (1,256,0).
+
+      (Properly speaking, the unsigned case should be parameterized as
+      S==Infinity.  If the schema were regular, the case S==0 would really
+      denote a numbering in which all coded values are negative.)
+
+      If S>0, the unsigned value of a byte sequence is regarded as a binary
+      integer.  If any of the S low-order bits are zero, the corresponding
+      signed value will be non-negative.  If all of the S low-order bits
+      (S>0) are one, the corresponding signed value will be negative.
+
+      The non-negative signed values are compact and monotonically increasing
+      (from 0) in the ordering of the corresponding unsigned values.
+
+      The negative signed values are compact and monotonically decreasing
+      (from -1) in the ordering of the corresponding unsigned values.
+
+      In essence, the low-order S bits function as a collective sign bit
+      for negative signed numbers, and as a low-order base-(2^S-1) digit
+      for non-negative signed numbers.
+
+      Therefore, the signed value corresponding to an unsigned value is:
+      
+        Sgn(x)  == x                               if S==0
+        Sgn(x)  == (x / 2^S)*(2^S-1) + (x % 2^S),  if S>0, (x % 2^S) < 2^S-1
+        Sgn(x)  == -(x / 2^S)-1,                   if S>0, (x % 2^S) == 2^S-1
+      
+ + Finally, the value of a byte sequence, given the coding parameters + (B,H,S), is defined as: +
+        V(b[i]: i
+
+      The extremal positive and negative signed value for a given range
+      of unsigned values may be found by sign-encoding the largest unsigned
+      value which is not 2^S-1 mod 2^S, and that which is, respectively.
+
+      Because B,H,S are variable, this is not a single coding but a schema
+      of codings.  For optimal compression, it is necessary to adaptively
+      select specific codings to the data being compressed.
+
+      For example, if a sequence of values happens never to be negative,
+      S==0 is the best choice.  If the values are equally balanced between
+      negative and positive, S==1.  If negative values are rare, then S>1
+      is more appropriate.
+
+      A (B,H,S) encoding is called a "subrange" if it does not encode
+      the largest 32-bit value, and if the number R of values it does
+      encode can be expressed as a positive 32-bit value.  (Note that
+      B=1 implies R<=256, B=2 implies R<=65536, etc.)
+
+      A delta version of a given (B,H,S) coding encodes an array of integers
+      by writing their successive differences in the (B,H,S) coding.
+      The original integers themselves may be recovered by making a
+      running accumulation of sum of the differences as they are read.
+
+      As a special case, if a (B,H,S) encoding is a subrange, its delta
+      version will only encode arrays of numbers in the coding's unsigned
+      range, [0..R-1].  The coding of deltas is still in the normal signed
+      range, if S!=0.  During delta encoding, all subtraction results are
+      reduced to the signed range, by adding multiples of R.  Likewise,
+.     during encoding, all addition results are reduced to the unsigned range.
+      This special case for subranges allows the benefits of wraparound
+      when encoding correlated sequences of very small positive numbers.
+     */
+
+    // Code-specific limits:
+    private static int saturate32(long x) {
+        if (x > Integer.MAX_VALUE)   return Integer.MAX_VALUE;
+        if (x < Integer.MIN_VALUE)   return Integer.MIN_VALUE;
+        return (int)x;
+    }
+    private static long codeRangeLong(int B, int H) {
+        return codeRangeLong(B, H, B);
+    }
+    private static long codeRangeLong(int B, int H, int nMax) {
+        // Code range for a all (B,H) codes of length <=nMax (<=B).
+        // n < B:   L*Sum[i= 0 && nMax <= B);
+        assert(B >= 1 && B <= 5);
+        assert(H >= 1 && H <= 256);
+        if (nMax == 0)  return 0;  // no codes of zero length
+        if (B == 1)     return H;  // special case; see (**) above
+        int L = 256-H;
+        long sum = 0;
+        long H_i = 1;
+        for (int n = 1; n <= nMax; n++) {
+            sum += H_i;
+            H_i *= H;
+        }
+        sum *= L;
+        if (nMax == B)
+            sum += H_i;
+        return sum;
+    }
+    /** Largest int representable by (B,H,S) in up to nMax bytes. */
+    public static int codeMax(int B, int H, int S, int nMax) {
+        //assert(S >= 0 && S <= S_MAX);
+        long range = codeRangeLong(B, H, nMax);
+        if (range == 0)
+            return -1;  // degenerate max value for empty set of codes
+        if (S == 0 || range >= (long)1<<32)
+            return saturate32(range-1);
+        long maxPos = range-1;
+        while (isNegativeCode(maxPos, S)) {
+            --maxPos;
+        }
+        if (maxPos < 0)  return -1;  // No positive codings at all.
+        int smax = decodeSign32(maxPos, S);
+        // check for 32-bit wraparound:
+        if (smax < 0)
+            return Integer.MAX_VALUE;
+        return smax;
+    }
+    /** Smallest int representable by (B,H,S) in up to nMax bytes.
+        Returns Integer.MIN_VALUE if 32-bit wraparound covers
+        the entire negative range.
+     */
+    public static int codeMin(int B, int H, int S, int nMax) {
+        //assert(S >= 0 && S <= S_MAX);
+        long range = codeRangeLong(B, H, nMax);
+        if (range >= (long)1<<32 && nMax == B) {
+            // Can code negative values via 32-bit wraparound.
+            return Integer.MIN_VALUE;
+        }
+        if (S == 0) {
+            return 0;
+        }
+        long maxNeg = range-1;
+        while (!isNegativeCode(maxNeg, S))
+            --maxNeg;
+
+        if (maxNeg < 0)  return 0;  // No negative codings at all.
+        return decodeSign32(maxNeg, S);
+    }
+
+    // Some of the arithmetic below is on unsigned 32-bit integers.
+    // These must be represented in Java as longs in the range [0..2^32-1].
+    // The conversion to a signed int is just the Java cast (int), but
+    // the conversion to an unsigned int is the following little method:
+    private static long toUnsigned32(int sx) {
+        return ((long)sx << 32) >>> 32;
+    }
+
+    // Sign encoding:
+    private static boolean isNegativeCode(long ux, int S) {
+        assert(S > 0);
+        assert(ux >= -1);  // can be out of 32-bit range; who cares
+        int Smask = (1< 0);
+        // If S>=2 very low negatives are coded by 32-bit-wrapped positives.
+        // The lowest negative representable by a negative coding is
+        // ~(umax32 >> S), and the next lower number is coded by wrapping
+        // the highest positive:
+        //    CodePos(umax32-1)  ->  (umax32-1)-((umax32-1)>>S)
+        // which simplifies to ~(umax32 >> S)-1.
+        return (0 > sx) && (sx >= ~(-1>>>S));
+    }
+    private static int decodeSign32(long ux, int S) {
+        assert(ux == toUnsigned32((int)ux))  // must be unsigned 32-bit number
+            : (Long.toHexString(ux));
+        if (S == 0) {
+            return (int) ux;  // cast to signed int
+        }
+        int sx;
+        if (isNegativeCode(ux, S)) {
+            // Sgn(x)  == -(x / 2^S)-1
+            sx = ~((int)ux >>> S);
+        } else {
+            // Sgn(x)  == (x / 2^S)*(2^S-1) + (x % 2^S)
+            sx = (int)ux - ((int)ux >>> S);
+        }
+        // Assert special case of S==1:
+        assert(!(S == 1) || sx == (((int)ux >>> 1) ^ -((int)ux & 1)));
+        return sx;
+    }
+    private static long encodeSign32(int sx, int S) {
+        if (S == 0) {
+            return toUnsigned32(sx);  // unsigned 32-bit int
+        }
+        int Smask = (1< "+
+               Integer.toHexString(sx)+" != "+
+               Integer.toHexString(decodeSign32(ux, S)));
+        return ux;
+    }
+
+    // Top-level coding of single integers:
+    public static void writeInt(byte[] out, int[] outpos, int sx, int B, int H, int S) {
+        long ux = encodeSign32(sx, S);
+        assert(ux == toUnsigned32((int)ux));
+        assert(ux < codeRangeLong(B, H))
+            : Long.toHexString(ux);
+        int L = 256-H;
+        long sum = ux;
+        int pos = outpos[0];
+        for (int i = 0; i < B-1; i++) {
+            if (sum < L)
+                break;
+            sum -= L;
+            int b_i = (int)( L + (sum % H) );
+            sum /= H;
+            out[pos++] = (byte)b_i;
+        }
+        out[pos++] = (byte)sum;
+        // Report number of bytes written by updating outpos[0]:
+        outpos[0] = pos;
+        // Check right away for mis-coding.
+        //assert(sx == readInt(out, new int[1], B, H, S));
+    }
+    public static int readInt(byte[] in, int[] inpos, int B, int H, int S) {
+        // U(b[i]: i= 0 && sum < codeRangeLong(B, H));
+        // Report number of bytes read by updating inpos[0]:
+        inpos[0] = pos;
+        return decodeSign32(sum, S);
+    }
+    // The Stream version doesn't fetch a byte unless it is needed for coding.
+    public static int readIntFrom(InputStream in, int B, int H, int S) throws IOException {
+        // U(b[i]: i= 0 && sum < codeRangeLong(B, H));
+        return decodeSign32(sum, S);
+    }
+
+    public static final int B_MAX = 5;    /* B: [1,5] */
+    public static final int H_MAX = 256;  /* H: [1,256] */
+    public static final int S_MAX = 2;    /* S: [0,2] */
+
+    // END OF STATICS.
+
+    private final int B; /*1..5*/       // # bytes (1..5)
+    private final int H; /*1..256*/     // # codes requiring a higher byte
+    private final int L; /*0..255*/     // # codes requiring a higher byte
+    private final int S; /*0..3*/       // # low-order bits representing sign
+    private final int del; /*0..2*/     // type of delta encoding (0 == none)
+    private final int min;              // smallest representable value
+    private final int max;              // largest representable value
+    private final int umin;             // smallest representable uns. value
+    private final int umax;             // largest representable uns. value
+    private final int[] byteMin;        // smallest repr. value, given # bytes
+    private final int[] byteMax;        // largest repr. value, given # bytes
+
+    private Coding(int B, int H, int S) {
+        this(B, H, S, 0);
+    }
+    private Coding(int B, int H, int S, int del) {
+        this.B = B;
+        this.H = H;
+        this.L = 256-H;
+        this.S = S;
+        this.del = del;
+        this.min = codeMin(B, H, S, B);
+        this.max = codeMax(B, H, S, B);
+        this.umin = codeMin(B, H, 0, B);
+        this.umax = codeMax(B, H, 0, B);
+        this.byteMin = new int[B];
+        this.byteMax = new int[B];
+
+        for (int nMax = 1; nMax <= B; nMax++) {
+            byteMin[nMax-1] = codeMin(B, H, S, nMax);
+            byteMax[nMax-1] = codeMax(B, H, S, nMax);
+        }
+    }
+
+    public boolean equals(Object x) {
+        if (!(x instanceof Coding))  return false;
+        Coding that = (Coding) x;
+        if (this.B != that.B)  return false;
+        if (this.H != that.H)  return false;
+        if (this.S != that.S)  return false;
+        if (this.del != that.del)  return false;
+        return true;
+    }
+
+    public int hashCode() {
+        return (del<<14)+(S<<11)+(B<<8)+(H<<0);
+    }
+
+    private static Map codeMap;
+
+    private static synchronized Coding of(int B, int H, int S, int del) {
+        if (codeMap == null)  codeMap = new HashMap<>();
+        Coding x0 = new Coding(B, H, S, del);
+        Coding x1 = codeMap.get(x0);
+        if (x1 == null)  codeMap.put(x0, x1 = x0);
+        return x1;
+    }
+
+    public static Coding of(int B, int H) {
+        return of(B, H, 0, 0);
+    }
+
+    public static Coding of(int B, int H, int S) {
+        return of(B, H, S, 0);
+    }
+
+    public boolean canRepresentValue(int x) {
+        if (isSubrange())
+            return canRepresentUnsigned(x);
+        else
+            return canRepresentSigned(x);
+    }
+    /** Can this coding represent a single value, possibly a delta?
+     *  This ignores the D property.  That is, for delta codings,
+     *  this tests whether a delta value of 'x' can be coded.
+     *  For signed delta codings which produce unsigned end values,
+     *  use canRepresentUnsigned.
+     */
+    public boolean canRepresentSigned(int x) {
+        return (x >= min && x <= max);
+    }
+    /** Can this coding, apart from its S property,
+     *  represent a single value?  (Negative values
+     *  can only be represented via 32-bit overflow,
+     *  so this returns true for negative values
+     *  if isFullRange is true.)
+     */
+    public boolean canRepresentUnsigned(int x) {
+        return (x >= umin && x <= umax);
+    }
+
+    // object-oriented code/decode
+    public int readFrom(byte[] in, int[] inpos) {
+        return readInt(in, inpos, B, H, S);
+    }
+    public void writeTo(byte[] out, int[] outpos, int x) {
+        writeInt(out, outpos, x, B, H, S);
+    }
+
+    // Stream versions
+    public int readFrom(InputStream in) throws IOException {
+        return readIntFrom(in, B, H, S);
+    }
+    public void writeTo(OutputStream out, int x) throws IOException {
+        byte[] buf = new byte[B];
+        int[] pos = new int[1];
+        writeInt(buf, pos, x, B, H, S);
+        out.write(buf, 0, pos[0]);
+    }
+
+    // Stream/array versions
+    public void readArrayFrom(InputStream in, int[] a, int start, int end) throws IOException {
+        // %%% use byte[] buffer
+        for (int i = start; i < end; i++)
+            a[i] = readFrom(in);
+
+        for (int dstep = 0; dstep < del; dstep++) {
+            long state = 0;
+            for (int i = start; i < end; i++) {
+                state += a[i];
+                // Reduce array values to the required range.
+                if (isSubrange()) {
+                    state = reduceToUnsignedRange(state);
+                }
+                a[i] = (int) state;
+            }
+        }
+    }
+    public void writeArrayTo(OutputStream out, int[] a, int start, int end) throws IOException {
+        if (end <= start)  return;
+        for (int dstep = 0; dstep < del; dstep++) {
+            int[] deltas;
+            if (!isSubrange())
+                deltas = makeDeltas(a, start, end, 0, 0);
+            else
+                deltas = makeDeltas(a, start, end, min, max);
+            a = deltas;
+            start = 0;
+            end = deltas.length;
+        }
+        // The following code is a buffered version of this loop:
+        //    for (int i = start; i < end; i++)
+        //        writeTo(out, a[i]);
+        byte[] buf = new byte[1<<8];
+        final int bufmax = buf.length-B;
+        int[] pos = { 0 };
+        for (int i = start; i < end; ) {
+            while (pos[0] <= bufmax) {
+                writeTo(buf, pos, a[i++]);
+                if (i >= end)  break;
+            }
+            out.write(buf, 0, pos[0]);
+            pos[0] = 0;
+        }
+    }
+
+    /** Tell if the range of this coding (number of distinct
+     *  representable values) can be expressed in 32 bits.
+     */
+    boolean isSubrange() {
+        return max < Integer.MAX_VALUE
+            && ((long)max - (long)min + 1) <= Integer.MAX_VALUE;
+    }
+
+    /** Tell if this coding can represent all 32-bit values.
+     *  Note:  Some codings, such as unsigned ones, can be neither
+     *  subranges nor full-range codings.
+     */
+    boolean isFullRange() {
+        return max == Integer.MAX_VALUE && min == Integer.MIN_VALUE;
+    }
+
+    /** Return the number of values this coding (a subrange) can represent. */
+    int getRange() {
+        assert(isSubrange());
+        return (max - min) + 1;  // range includes both min & max
+    }
+
+    Coding setB(int B) { return Coding.of(B, H, S, del); }
+    Coding setH(int H) { return Coding.of(B, H, S, del); }
+    Coding setS(int S) { return Coding.of(B, H, S, del); }
+    Coding setL(int L) { return setH(256-L); }
+    Coding setD(int del) { return Coding.of(B, H, S, del); }
+    Coding getDeltaCoding() { return setD(del+1); }
+
+    /** Return a coding suitable for representing summed, modulo-reduced values. */
+    Coding getValueCoding() {
+        if (isDelta())
+            return Coding.of(B, H, 0, del-1);
+        else
+            return this;
+    }
+
+    /** Reduce the given value to be within this coding's unsigned range,
+     *  by adding or subtracting a multiple of (max-min+1).
+     */
+    int reduceToUnsignedRange(long value) {
+        if (value == (int)value && canRepresentUnsigned((int)value))
+            // already in unsigned range
+            return (int)value;
+        int range = getRange();
+        assert(range > 0);
+        value %= range;
+        if (value < 0)  value += range;
+        assert(canRepresentUnsigned((int)value));
+        return (int)value;
+    }
+
+    int reduceToSignedRange(int value) {
+        if (canRepresentSigned(value))
+            // already in signed range
+            return value;
+        return reduceToSignedRange(value, min, max);
+    }
+    static int reduceToSignedRange(int value, int min, int max) {
+        int range = (max-min+1);
+        assert(range > 0);
+        int value0 = value;
+        value -= min;
+        if (value < 0 && value0 >= 0) {
+            // 32-bit overflow, but the next '%=' op needs to be unsigned
+            value -= range;
+            assert(value >= 0);
+        }
+        value %= range;
+        if (value < 0)  value += range;
+        value += min;
+        assert(min <= value && value <= max);
+        return value;
+    }
+
+    /** Does this coding support at least one negative value?
+        Includes codings that can do so via 32-bit wraparound.
+     */
+    boolean isSigned() {
+        return min < 0;
+    }
+    /** Does this coding code arrays by making successive differences? */
+    boolean isDelta() {
+        return del != 0;
+    }
+
+    public int B() { return B; }
+    public int H() { return H; }
+    public int L() { return L; }
+    public int S() { return S; }
+    public int del() { return del; }
+    public int min() { return min; }
+    public int max() { return max; }
+    public int umin() { return umin; }
+    public int umax() { return umax; }
+    public int byteMin(int b) { return byteMin[b-1]; }
+    public int byteMax(int b) { return byteMax[b-1]; }
+
+    public int compareTo(Coding that) {
+        int dkey = this.del - that.del;
+        if (dkey == 0)
+            dkey = this.B - that.B;
+        if (dkey == 0)
+            dkey = this.H - that.H;
+        if (dkey == 0)
+            dkey = this.S - that.S;
+        return dkey;
+    }
+
+    /** Heuristic measure of the difference between two codings. */
+    public int distanceFrom(Coding that) {
+        int diffdel = this.del - that.del;
+        if (diffdel < 0)  diffdel = -diffdel;
+        int diffS = this.S - that.S;
+        if (diffS < 0)  diffS = -diffS;
+        int diffB = this.B - that.B;
+        if (diffB < 0)  diffB = -diffB;
+        int diffHL;
+        if (this.H == that.H) {
+            diffHL = 0;
+        } else {
+            // Distance in log space of H (<=128) and L (<128).
+            int thisHL = this.getHL();
+            int thatHL = that.getHL();
+            // Double the accuracy of the log:
+            thisHL *= thisHL;
+            thatHL *= thatHL;
+            if (thisHL > thatHL)
+                diffHL = ceil_lg2(1+(thisHL-1)/thatHL);
+            else
+                diffHL = ceil_lg2(1+(thatHL-1)/thisHL);
+        }
+        int norm = 5*(diffdel + diffS + diffB) + diffHL;
+        assert(norm != 0 || this.compareTo(that) == 0);
+        return norm;
+    }
+    private int getHL() {
+        // Follow H in log space by the multiplicative inverse of L.
+        if (H <= 128)  return H;
+        if (L >= 1)    return 128*128/L;
+        return 128*256;
+    }
+
+    /** ceiling(log[2](x)): {1->0, 2->1, 3->2, 4->2, ...} */
+    static int ceil_lg2(int x) {
+        assert(x-1 >= 0);  // x in range (int.MIN_VALUE -> 32)
+        x -= 1;
+        int lg = 0;
+        while (x != 0) {
+            lg++;
+            x >>= 1;
+        }
+        return lg;
+    }
+
+    private static final byte[] byteBitWidths = new byte[0x100];
+    static {
+        for (int b = 0; b < byteBitWidths.length; b++) {
+            byteBitWidths[b] = (byte) ceil_lg2(b + 1);
+        }
+        for (int i = 10; i >= 0; i = (i << 1) - (i >> 3)) {
+            assert(bitWidth(i) == ceil_lg2(i + 1));
+        }
+    }
+
+    /** Number of significant bits in i, not counting sign bits.
+     *  For positive i, it is ceil_lg2(i + 1).
+     */
+    static int bitWidth(int i) {
+        if (i < 0)  i = ~i;  // change sign
+        int w = 0;
+        int lo = i;
+        if (lo < byteBitWidths.length)
+            return byteBitWidths[lo];
+        int hi;
+        hi = (lo >>> 16);
+        if (hi != 0) {
+            lo = hi;
+            w += 16;
+        }
+        hi = (lo >>> 8);
+        if (hi != 0) {
+            lo = hi;
+            w += 8;
+        }
+        w += byteBitWidths[lo];
+        //assert(w == ceil_lg2(i + 1));
+        return w;
+    }
+
+    /** Create an array of successive differences.
+     *  If min==max, accept any and all 32-bit overflow.
+     *  Otherwise, avoid 32-bit overflow, and reduce all differences
+     *  to a value in the given range, by adding or subtracting
+     *  multiples of the range cardinality (max-min+1).
+     *  Also, the values are assumed to be in the range [0..(max-min)].
+     */
+    static int[] makeDeltas(int[] values, int start, int end,
+                            int min, int max) {
+        assert(max >= min);
+        int count = end-start;
+        int[] deltas = new int[count];
+        int state = 0;
+        if (min == max) {
+            for (int i = 0; i < count; i++) {
+                int value = values[start+i];
+                deltas[i] = value - state;
+                state = value;
+            }
+        } else {
+            for (int i = 0; i < count; i++) {
+                int value = values[start+i];
+                assert(value >= 0 && value+min <= max);
+                int delta = value - state;
+                assert(delta == (long)value - (long)state); // no overflow
+                state = value;
+                // Reduce delta values to the required range.
+                delta = reduceToSignedRange(delta, min, max);
+                deltas[i] = delta;
+            }
+        }
+        return deltas;
+    }
+
+    boolean canRepresent(int minValue, int maxValue) {
+        assert(minValue <= maxValue);
+        if (del > 0) {
+            if (isSubrange()) {
+                // We will force the values to reduce to the right subrange.
+                return canRepresentUnsigned(maxValue)
+                    && canRepresentUnsigned(minValue);
+            } else {
+                // Huge range; delta values must assume full 32-bit range.
+                return isFullRange();
+            }
+        }
+        else
+            // final values must be representable
+            return canRepresentSigned(maxValue)
+                && canRepresentSigned(minValue);
+    }
+
+    boolean canRepresent(int[] values, int start, int end) {
+        int len = end-start;
+        if (len == 0)       return true;
+        if (isFullRange())  return true;
+        // Calculate max, min:
+        int lmax = values[start];
+        int lmin = lmax;
+        for (int i = 1; i < len; i++) {
+            int value = values[start+i];
+            if (lmax < value)  lmax = value;
+            if (lmin > value)  lmin = value;
+        }
+        return canRepresent(lmin, lmax);
+    }
+
+    public double getBitLength(int value) {  // implements BitMetric
+        return (double) getLength(value) * 8;
+    }
+
+    /** How many bytes are in the coding of this value?
+     *  Returns Integer.MAX_VALUE if the value has no coding.
+     *  The coding must not be a delta coding, since there is no
+     *  definite size for a single value apart from its context.
+     */
+    public int getLength(int value) {
+        if (isDelta() && isSubrange()) {
+            if (!canRepresentUnsigned(value))
+                return Integer.MAX_VALUE;
+            value = reduceToSignedRange(value);
+        }
+        if (value >= 0) {
+            for (int n = 0; n < B; n++) {
+                if (value <= byteMax[n])  return n+1;
+            }
+        } else {
+            for (int n = 0; n < B; n++) {
+                if (value >= byteMin[n])  return n+1;
+            }
+        }
+        return Integer.MAX_VALUE;
+    }
+
+    public int getLength(int[] values, int start, int end) {
+        int len = end-start;
+        if (B == 1)  return len;
+        if (L == 0)  return len * B;
+        if (isDelta()) {
+            int[] deltas;
+            if (!isSubrange())
+                deltas = makeDeltas(values, start, end, 0, 0);
+            else
+                deltas = makeDeltas(values, start, end, min, max);
+            //return Coding.of(B, H, S).getLength(deltas, 0, len);
+            values = deltas;
+            start = 0;
+        }
+        int sum = len;  // at least 1 byte per
+        // add extra bytes for extra-long values
+        for (int n = 1; n <= B; n++) {
+            // what is the coding interval [min..max] for n bytes?
+            int lmax = byteMax[n-1];
+            int lmin = byteMin[n-1];
+            int longer = 0;  // count of guys longer than n bytes
+            for (int i = 0; i < len; i++) {
+                int value = values[start+i];
+                if (value >= 0) {
+                    if (value > lmax)  longer++;
+                } else {
+                    if (value < lmin)  longer++;
+                }
+            }
+            if (longer == 0)  break;  // no more passes needed
+            if (n == B)  return Integer.MAX_VALUE;  // cannot represent!
+            sum += longer;
+        }
+        return sum;
+    }
+
+    public byte[] getMetaCoding(Coding dflt) {
+        if (dflt == this)  return new byte[]{ (byte) Constants._meta_default };
+        int canonicalIndex = BandStructure.indexOf(this);
+        if (canonicalIndex > 0)
+            return new byte[]{ (byte) canonicalIndex };
+        return new byte[]{
+            (byte)Constants._meta_arb,
+            (byte)(del + 2*S + 8*(B-1)),
+            (byte)(H-1)
+        };
+    }
+    public static int parseMetaCoding(byte[] bytes, int pos, Coding dflt, CodingMethod res[]) {
+        int op = bytes[pos++] & 0xFF;
+        if (Constants._meta_canon_min <= op && op <= Constants._meta_canon_max) {
+            Coding c = BandStructure.codingForIndex(op);
+            assert(c != null);
+            res[0] = c;
+            return pos;
+        }
+        if (op == Constants._meta_arb) {
+            int dsb = bytes[pos++] & 0xFF;
+            int H_1 = bytes[pos++] & 0xFF;
+            int del = dsb % 2;
+            int S = (dsb / 2) % 4;
+            int B = (dsb / 8)+1;
+            int H = H_1+1;
+            if (!((1 <= B && B <= B_MAX) &&
+                  (0 <= S && S <= S_MAX) &&
+                  (1 <= H && H <= H_MAX) &&
+                  (0 <= del && del <= 1))
+                || (B == 1 && H != 256)
+                || (B == 5 && H == 256)) {
+                throw new RuntimeException("Bad arb. coding: ("+B+","+H+","+S+","+del);
+            }
+            res[0] = Coding.of(B, H, S, del);
+            return pos;
+        }
+        return pos-1;  // backup
+    }
+
+
+    public String keyString() {
+        return "("+B+","+H+","+S+","+del+")";
+    }
+
+    public String toString() {
+        String str = "Coding"+keyString();
+        // If -ea, print out more informative strings!
+        //assert((str = stringForDebug()) != null);
+        return str;
+    }
+
+    static boolean verboseStringForDebug = false;
+    String stringForDebug() {
+        String minS = (min == Integer.MIN_VALUE ? "min" : ""+min);
+        String maxS = (max == Integer.MAX_VALUE ? "max" : ""+max);
+        String str = keyString()+" L="+L+" r=["+minS+","+maxS+"]";
+        if (isSubrange())
+            str += " subrange";
+        else if (!isFullRange())
+            str += " MIDRANGE";
+        if (verboseStringForDebug) {
+            str += " {";
+            int prev_range = 0;
+            for (int n = 1; n <= B; n++) {
+                int range_n = saturate32((long)byteMax[n-1] - byteMin[n-1] + 1);
+                assert(range_n == saturate32(codeRangeLong(B, H, n)));
+                range_n -= prev_range;
+                prev_range = range_n;
+                String rngS = (range_n == Integer.MAX_VALUE ? "max" : ""+range_n);
+                str += " #"+n+"="+rngS;
+            }
+            str += " }";
+        }
+        return str;
+    }
+}
diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/CodingChooser.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/CodingChooser.java
new file mode 100644
index 000000000..829b60a4f
--- /dev/null
+++ b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/CodingChooser.java
@@ -0,0 +1,1489 @@
+/*
+ * Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package net.fabricmc.shade.com.sun.java.util.jar.pack;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+import java.util.zip.Deflater;
+import java.util.zip.DeflaterOutputStream;
+
+/**
+ * Heuristic chooser of basic encodings.
+ * Runs "zip" to measure the apparent information content after coding.
+ * @author John Rose
+ */
+class CodingChooser {
+    int verbose;
+    int effort;
+    boolean optUseHistogram = true;
+    boolean optUsePopulationCoding = true;
+    boolean optUseAdaptiveCoding = true;
+    boolean disablePopCoding;
+    boolean disableRunCoding;
+    boolean topLevel = true;
+
+    // Derived from effort; >1 (<1) means try more (less) experiments
+    // when looking to beat a best score.
+    double fuzz;
+
+    Coding[] allCodingChoices;
+    Choice[] choices;
+    ByteArrayOutputStream context;
+    CodingChooser popHelper;
+    CodingChooser runHelper;
+
+    Random stress;  // If not null, stress mode oracle.
+
+    // Element in sorted set of coding choices:
+    static
+    class Choice {
+        final Coding coding;
+        final int index;       // index in choices
+        final int[] distance;  // cache of distance
+        Choice(Coding coding, int index, int[] distance) {
+            this.coding   = coding;
+            this.index    = index;
+            this.distance = distance;
+        }
+        // These variables are reset and reused:
+        int searchOrder; // order in which it is checked
+        int minDistance; // min distance from already-checked choices
+        int zipSize;     // size of encoding in sample, zipped output
+        int byteSize;    // size of encoding in sample (debug only)
+        int histSize;    // size of encoding, according to histogram
+
+        void reset() {
+            searchOrder = Integer.MAX_VALUE;
+            minDistance = Integer.MAX_VALUE;
+            zipSize = byteSize = histSize = -1;
+        }
+
+        boolean isExtra() {
+            return index < 0;
+        }
+
+        public String toString() {
+            return stringForDebug();
+        }
+
+        private String stringForDebug() {
+            String s = "";
+            if (searchOrder < Integer.MAX_VALUE)
+                s += " so: "+searchOrder;
+            if (minDistance < Integer.MAX_VALUE)
+                s += " md: "+minDistance;
+            if (zipSize > 0)
+                s += " zs: "+zipSize;
+            if (byteSize > 0)
+                s += " bs: "+byteSize;
+            if (histSize > 0)
+                s += " hs: "+histSize;
+            return "Choice["+index+"] "+s+" "+coding;
+        }
+    }
+
+    CodingChooser(int effort, Coding[] allCodingChoices) {
+        PropMap p200 = Utils.currentPropMap();
+        if (p200 != null) {
+            this.verbose
+                = Math.max(p200.getInteger(Utils.DEBUG_VERBOSE),
+                           p200.getInteger(Utils.COM_PREFIX+"verbose.coding"));
+            this.optUseHistogram
+                = !p200.getBoolean(Utils.COM_PREFIX+"no.histogram");
+            this.optUsePopulationCoding
+                = !p200.getBoolean(Utils.COM_PREFIX+"no.population.coding");
+            this.optUseAdaptiveCoding
+                = !p200.getBoolean(Utils.COM_PREFIX+"no.adaptive.coding");
+            int lstress
+                = p200.getInteger(Utils.COM_PREFIX+"stress.coding");
+            if (lstress != 0)
+                this.stress = new Random(lstress);
+        }
+
+        this.effort = effort;
+        // The following line "makes sense" but is too much
+        // work for a simple heuristic.
+        //if (effort > 5)  zipDef.setLevel(effort);
+
+        this.allCodingChoices = allCodingChoices;
+
+        // If effort = 9, look carefully at any solution
+        // whose initial metrics are within 1% of the best
+        // so far.  If effort = 1, look carefully only at
+        // solutions whose initial metrics promise a 1% win.
+        this.fuzz = 1 + (0.0025 * (effort-MID_EFFORT));
+
+        int nc = 0;
+        for (int i = 0; i < allCodingChoices.length; i++) {
+            if (allCodingChoices[i] == null)  continue;
+            nc++;
+        }
+        choices = new Choice[nc];
+        nc = 0;
+        for (int i = 0; i < allCodingChoices.length; i++) {
+            if (allCodingChoices[i] == null)  continue;
+            int[] distance = new int[choices.length];
+            choices[nc++] = new Choice(allCodingChoices[i], i, distance);
+        }
+        for (int i = 0; i < choices.length; i++) {
+            Coding ci = choices[i].coding;
+            assert(ci.distanceFrom(ci) == 0);
+            for (int j = 0; j < i; j++) {
+                Coding cj = choices[j].coding;
+                int dij = ci.distanceFrom(cj);
+                assert(dij > 0);
+                assert(dij == cj.distanceFrom(ci));
+                choices[i].distance[j] = dij;
+                choices[j].distance[i] = dij;
+            }
+        }
+    }
+
+    Choice makeExtraChoice(Coding coding) {
+        int[] distance = new int[choices.length];
+        for (int i = 0; i < distance.length; i++) {
+            Coding ci = choices[i].coding;
+            int dij = coding.distanceFrom(ci);
+            assert(dij > 0);
+            assert(dij == ci.distanceFrom(coding));
+            distance[i] = dij;
+        }
+        Choice c = new Choice(coding, -1, distance);
+        c.reset();
+        return c;
+    }
+
+    ByteArrayOutputStream getContext() {
+        if (context == null)
+            context = new ByteArrayOutputStream(1 << 16);
+        return context;
+    }
+
+    // These variables are reset and reused:
+    private int[] values;
+    private int start, end;  // slice of values
+    private int[] deltas;
+    private int min, max;
+    private Histogram vHist;
+    private Histogram dHist;
+    private int searchOrder;
+    private Choice regularChoice;
+    private Choice bestChoice;
+    private CodingMethod bestMethod;
+    private int bestByteSize;
+    private int bestZipSize;
+    private int targetSize;   // fuzzed target byte size
+
+    private void reset(int[] values, int start, int end) {
+        this.values = values;
+        this.start = start;
+        this.end = end;
+        this.deltas = null;
+        this.min = Integer.MAX_VALUE;
+        this.max = Integer.MIN_VALUE;
+        this.vHist = null;
+        this.dHist = null;
+        this.searchOrder = 0;
+        this.regularChoice = null;
+        this.bestChoice = null;
+        this.bestMethod = null;
+        this.bestZipSize = Integer.MAX_VALUE;
+        this.bestByteSize = Integer.MAX_VALUE;
+        this.targetSize = Integer.MAX_VALUE;
+    }
+
+    public static final int MIN_EFFORT = 1;
+    public static final int MID_EFFORT = 5;
+    public static final int MAX_EFFORT = 9;
+
+    public static final int POP_EFFORT = MID_EFFORT-1;
+    public static final int RUN_EFFORT = MID_EFFORT-2;
+
+    public static final int BYTE_SIZE = 0;
+    public static final int ZIP_SIZE = 1;
+
+    CodingMethod choose(int[] values, int start, int end, Coding regular, int[] sizes) {
+        // Save the value array
+        reset(values, start, end);
+
+        if (effort <= MIN_EFFORT || start >= end) {
+            if (sizes != null) {
+                int[] computed = computeSizePrivate(regular);
+                sizes[BYTE_SIZE] = computed[BYTE_SIZE];
+                sizes[ZIP_SIZE]  = computed[ZIP_SIZE];
+            }
+            return regular;
+        }
+
+        if (optUseHistogram) {
+            getValueHistogram();
+            getDeltaHistogram();
+        }
+
+        for (int i = start; i < end; i++) {
+            int val = values[i];
+            if (min > val)  min = val;
+            if (max < val)  max = val;
+        }
+
+        // Find all the preset choices that might be worth looking at:
+        int numChoices = markUsableChoices(regular);
+
+        if (stress != null) {
+            // Make a random choice.
+            int rand = stress.nextInt(numChoices*2 + 4);
+            CodingMethod coding = null;
+            for (int i = 0; i < choices.length; i++) {
+                Choice c = choices[i];
+                if (c.searchOrder >= 0 && rand-- == 0) {
+                    coding = c.coding;
+                    break;
+                }
+            }
+            if (coding == null) {
+                if ((rand & 7) != 0) {
+                    coding = regular;
+                } else {
+                    // Pick a totally random coding 6% of the time.
+                    coding = stressCoding(min, max);
+                }
+            }
+            if (!disablePopCoding
+                && optUsePopulationCoding
+                && effort >= POP_EFFORT) {
+                coding = stressPopCoding(coding);
+            }
+            if (!disableRunCoding
+                && optUseAdaptiveCoding
+                && effort >= RUN_EFFORT) {
+                coding = stressAdaptiveCoding(coding);
+            }
+            return coding;
+        }
+
+        double searchScale = 1.0;
+        for (int x = effort; x < MAX_EFFORT; x++) {
+            searchScale /= 1.414;  // every 2 effort points doubles work
+        }
+        int searchOrderLimit = (int)Math.ceil( numChoices * searchScale );
+
+        // Start by evaluating the "regular" choice.
+        bestChoice = regularChoice;
+        evaluate(regularChoice);
+        int maxd = updateDistances(regularChoice);
+
+        // save these first-cut numbers for later
+        int zipSize1 = bestZipSize;
+        int byteSize1 = bestByteSize;
+
+        if (regularChoice.coding == regular && topLevel) {
+            // Give credit for being the default; no band header is needed.
+            // Rather than increasing every other size value by the band
+            // header amount, we decrement this one metric, to give it an edge.
+            // Decreasing zipSize by a byte length is conservatively correct,
+            // especially considering that the escape byte is not likely to
+            // zip well with other bytes in the band.
+            int X = BandStructure.encodeEscapeValue(Constants._meta_canon_max, regular);
+            if (regular.canRepresentSigned(X)) {
+                int Xlen = regular.getLength(X);  // band coding header
+                //regularChoice.histSize -= Xlen; // keep exact byteSize
+                //regularChoice.byteSize -= Xlen; // keep exact byteSize
+                regularChoice.zipSize -= Xlen;
+                bestByteSize = regularChoice.byteSize;
+                bestZipSize = regularChoice.zipSize;
+            }
+        }
+
+        int dscale = 1;
+        // Continually select a new choice to evaluate.
+        while (searchOrder < searchOrderLimit) {
+            Choice nextChoice;
+            if (dscale > maxd)  dscale = 1;  // cycle dscale values!
+            int dhi = maxd / dscale;
+            int dlo = maxd / (dscale *= 2) + 1;
+            nextChoice = findChoiceNear(bestChoice, dhi, dlo);
+            if (nextChoice == null)  continue;
+            assert(nextChoice.coding.canRepresent(min, max));
+            evaluate(nextChoice);
+            int nextMaxd = updateDistances(nextChoice);
+            if (nextChoice == bestChoice) {
+                maxd = nextMaxd;
+                if (verbose > 5)  Utils.log.info("maxd = "+maxd);
+            }
+        }
+
+        // Record best "plain coding" choice.
+        Coding plainBest = bestChoice.coding;
+        assert(plainBest == bestMethod);
+
+        if (verbose > 2) {
+            Utils.log.info("chooser: plain result="+bestChoice+" after "+bestChoice.searchOrder+" rounds, "+(regularChoice.zipSize-bestZipSize)+" fewer bytes than regular "+regular);
+        }
+        bestChoice = null;
+
+        if (!disablePopCoding
+            && optUsePopulationCoding
+            && effort >= POP_EFFORT
+            && bestMethod instanceof Coding) {
+            tryPopulationCoding(plainBest);
+        }
+
+        if (!disableRunCoding
+            && optUseAdaptiveCoding
+            && effort >= RUN_EFFORT
+            && bestMethod instanceof Coding) {
+            tryAdaptiveCoding(plainBest);
+        }
+
+        // Pass back the requested information:
+        if (sizes != null) {
+            sizes[BYTE_SIZE] = bestByteSize;
+            sizes[ZIP_SIZE]  = bestZipSize;
+        }
+        if (verbose > 1) {
+            Utils.log.info("chooser: result="+bestMethod+" "+
+                             (zipSize1-bestZipSize)+
+                             " fewer bytes than regular "+regular+
+                             "; win="+pct(zipSize1-bestZipSize, zipSize1));
+        }
+        CodingMethod lbestMethod = this.bestMethod;
+        reset(null, 0, 0);  // for GC
+        return lbestMethod;
+    }
+    CodingMethod choose(int[] values, int start, int end, Coding regular) {
+        return choose(values, start, end, regular, null);
+    }
+    CodingMethod choose(int[] values, Coding regular, int[] sizes) {
+        return choose(values, 0, values.length, regular, sizes);
+    }
+    CodingMethod choose(int[] values, Coding regular) {
+        return choose(values, 0, values.length, regular, null);
+    }
+
+    private int markUsableChoices(Coding regular) {
+        int numChoices = 0;
+        for (int i = 0; i < choices.length; i++) {
+            Choice c = choices[i];
+            c.reset();
+            if (!c.coding.canRepresent(min, max)) {
+                // Mark as already visited:
+                c.searchOrder = -1;
+                if (verbose > 1 && c.coding == regular) {
+                    Utils.log.info("regular coding cannot represent ["+min+".."+max+"]: "+regular);
+                }
+                continue;
+            }
+            if (c.coding == regular)
+                regularChoice = c;
+            numChoices++;
+        }
+        if (regularChoice == null && regular.canRepresent(min, max)) {
+            regularChoice = makeExtraChoice(regular);
+            if (verbose > 1) {
+                Utils.log.info("*** regular choice is extra: "+regularChoice.coding);
+            }
+        }
+        if (regularChoice == null) {
+            for (int i = 0; i < choices.length; i++) {
+                Choice c = choices[i];
+                if (c.searchOrder != -1) {
+                    regularChoice = c;  // arbitrary pick
+                    break;
+                }
+            }
+            if (verbose > 1) {
+                Utils.log.info("*** regular choice does not apply "+regular);
+                Utils.log.info("    using instead "+regularChoice.coding);
+            }
+        }
+        if (verbose > 2) {
+            Utils.log.info("chooser: #choices="+numChoices+" ["+min+".."+max+"]");
+            if (verbose > 4) {
+                for (int i = 0; i < choices.length; i++) {
+                    Choice c = choices[i];
+                    if (c.searchOrder >= 0)
+                        Utils.log.info("  "+c);
+                }
+            }
+        }
+        return numChoices;
+    }
+
+    // Find an arbitrary choice at least dlo away from a previously
+    // evaluated choices, and at most dhi.  Try also to regulate its
+    // min distance to all previously evaluated choices, in this range.
+    private Choice findChoiceNear(Choice near, int dhi, int dlo) {
+        if (verbose > 5)
+            Utils.log.info("findChoice "+dhi+".."+dlo+" near: "+near);
+        int[] distance = near.distance;
+        Choice found = null;
+        for (int i = 0; i < choices.length; i++) {
+            Choice c = choices[i];
+            if (c.searchOrder < searchOrder)
+                continue;  // already searched
+            // Distance from "near" guy must be in bounds:
+            if (distance[i] >= dlo && distance[i] <= dhi) {
+                // Try also to keep min-distance from other guys in bounds:
+                if (c.minDistance >= dlo && c.minDistance <= dhi) {
+                    if (verbose > 5)
+                        Utils.log.info("findChoice => good "+c);
+                    return c;
+                }
+                found = c;
+            }
+        }
+        if (verbose > 5)
+            Utils.log.info("findChoice => found "+found);
+        return found;
+    }
+
+    private void evaluate(Choice c) {
+        assert(c.searchOrder == Integer.MAX_VALUE);
+        c.searchOrder = searchOrder++;
+        boolean mustComputeSize;
+        if (c == bestChoice || c.isExtra()) {
+            mustComputeSize = true;
+        } else if (optUseHistogram) {
+            Histogram hist = getHistogram(c.coding.isDelta());
+            c.histSize = (int)Math.ceil(hist.getBitLength(c.coding) / 8);
+            c.byteSize = c.histSize;
+            mustComputeSize = (c.byteSize <= targetSize);
+        } else {
+            mustComputeSize = true;
+        }
+        if (mustComputeSize) {
+            int[] sizes = computeSizePrivate(c.coding);
+            c.byteSize = sizes[BYTE_SIZE];
+            c.zipSize  = sizes[ZIP_SIZE];
+            if (noteSizes(c.coding, c.byteSize, c.zipSize))
+                bestChoice = c;
+        }
+        if (c.histSize >= 0) {
+            assert(c.byteSize == c.histSize);  // models should agree
+        }
+        if (verbose > 4) {
+            Utils.log.info("evaluated "+c);
+        }
+    }
+
+    private boolean noteSizes(CodingMethod c, int byteSize, int zipSize) {
+        assert(zipSize > 0 && byteSize > 0);
+        boolean better = (zipSize < bestZipSize);
+        if (verbose > 3)
+            Utils.log.info("computed size "+c+" "+byteSize+"/zs="+zipSize+
+                             ((better && bestMethod != null)?
+                              (" better by "+
+                               pct(bestZipSize - zipSize, zipSize)): ""));
+        if (better) {
+            bestMethod = c;
+            bestZipSize = zipSize;
+            bestByteSize = byteSize;
+            targetSize = (int)(byteSize * fuzz);
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+
+    private int updateDistances(Choice c) {
+        // update all minDistance values in still unevaluated choices
+        int[] distance = c.distance;
+        int maxd = 0;  // how far is c from everybody else?
+        for (int i = 0; i < choices.length; i++) {
+            Choice c2 = choices[i];
+            if (c2.searchOrder < searchOrder)
+                continue;
+            int d = distance[i];
+            if (verbose > 5)
+                Utils.log.info("evaluate dist "+d+" to "+c2);
+            int mind = c2.minDistance;
+            if (mind > d)
+                c2.minDistance = mind = d;
+            if (maxd < d)
+                maxd = d;
+        }
+        // Now maxd has the distance of the farthest outlier
+        // from all evaluated choices.
+        if (verbose > 5)
+            Utils.log.info("evaluate maxd => "+maxd);
+        return maxd;
+    }
+
+    // Compute the coded size of a sequence of values.
+    // The first int is the size in uncompressed bytes.
+    // The second is an estimate of the compressed size of these bytes.
+    public void computeSize(CodingMethod c, int[] values, int start, int end, int[] sizes) {
+        if (end <= start) {
+            sizes[BYTE_SIZE] = sizes[ZIP_SIZE] = 0;
+            return;
+        }
+        try {
+            resetData();
+            c.writeArrayTo(byteSizer, values, start, end);
+            sizes[BYTE_SIZE] = getByteSize();
+            sizes[ZIP_SIZE] = getZipSize();
+        } catch (IOException ee) {
+            throw new RuntimeException(ee); // cannot happen
+        }
+    }
+    public void computeSize(CodingMethod c, int[] values, int[] sizes) {
+        computeSize(c, values, 0, values.length, sizes);
+    }
+    public int[] computeSize(CodingMethod c, int[] values, int start, int end) {
+        int[] sizes = { 0, 0 };
+        computeSize(c, values, start, end, sizes);
+        return sizes;
+    }
+    public int[] computeSize(CodingMethod c, int[] values) {
+        return computeSize(c, values, 0, values.length);
+    }
+    // This version uses the implicit local arguments
+    private int[] computeSizePrivate(CodingMethod c) {
+        int[] sizes = { 0, 0 };
+        computeSize(c, values, start, end, sizes);
+        return sizes;
+    }
+    public int computeByteSize(CodingMethod cm, int[] values, int start, int end) {
+        int len = end-start;
+        if (len < 0) {
+            return 0;
+        }
+        if (cm instanceof Coding) {
+            Coding c = (Coding) cm;
+            int size = c.getLength(values, start, end);
+            int size2;
+            assert(size == (size2=countBytesToSizer(cm, values, start, end)))
+                : (cm+" : "+size+" != "+size2);
+            return size;
+        }
+        return countBytesToSizer(cm, values, start, end);
+    }
+    private int countBytesToSizer(CodingMethod cm, int[] values, int start, int end) {
+        try {
+            byteOnlySizer.reset();
+            cm.writeArrayTo(byteOnlySizer, values, start, end);
+            return byteOnlySizer.getSize();
+        } catch (IOException ee) {
+            throw new RuntimeException(ee); // cannot happen
+        }
+    }
+
+    int[] getDeltas(int min, int max) {
+        if ((min|max) != 0)
+            return Coding.makeDeltas(values, start, end, min, max);
+        if (deltas == null) {
+            deltas = Coding.makeDeltas(values, start, end, 0, 0);
+        }
+        return deltas;
+    }
+    Histogram getValueHistogram() {
+        if (vHist == null) {
+            vHist = new Histogram(values, start, end);
+            if (verbose > 3) {
+                vHist.print("vHist", System.out);
+            } else if (verbose > 1) {
+                vHist.print("vHist", null, System.out);
+            }
+        }
+        return vHist;
+    }
+    Histogram getDeltaHistogram() {
+        if (dHist == null) {
+            dHist = new Histogram(getDeltas(0, 0));
+            if (verbose > 3) {
+                dHist.print("dHist", System.out);
+            } else if (verbose > 1) {
+                dHist.print("dHist", null, System.out);
+            }
+        }
+        return dHist;
+    }
+    Histogram getHistogram(boolean isDelta) {
+        return isDelta ? getDeltaHistogram(): getValueHistogram();
+    }
+
+    private void tryPopulationCoding(Coding plainCoding) {
+        // assert(plainCoding.canRepresent(min, max));
+        Histogram hist = getValueHistogram();
+        // Start with "reasonable" default codings.
+        final int approxL = 64;
+        Coding favoredCoding = plainCoding.getValueCoding();
+        Coding tokenCoding = BandStructure.UNSIGNED5.setL(approxL);
+        Coding unfavoredCoding = plainCoding.getValueCoding();
+        // There's going to be a band header.  Estimate conservatively large.
+        final int BAND_HEADER = 4;
+        // Keep a running model of the predicted sizes of the F/T/U sequences.
+        int currentFSize;
+        int currentTSize;
+        int currentUSize;
+        // Start by assuming a degenerate favored-value length of 0,
+        // which looks like a bunch of zero tokens followed by the
+        // original sequence.
+        // The {F} list ends with a repeated F value; find worst case:
+        currentFSize =
+            BAND_HEADER + Math.max(favoredCoding.getLength(min),
+                                   favoredCoding.getLength(max));
+        // The {T} list starts out a bunch of zeros, each of length 1.
+        final int ZERO_LEN = tokenCoding.getLength(0);
+        currentTSize = ZERO_LEN * (end-start);
+        // The {U} list starts out a copy of the plainCoding:
+        currentUSize = (int) Math.ceil(hist.getBitLength(unfavoredCoding) / 8);
+
+        int bestPopSize = (currentFSize + currentTSize + currentUSize);
+        int bestPopFVC  = 0;
+
+        // Record all the values, in decreasing order of favor.
+        int[] allFavoredValues = new int[1+hist.getTotalLength()];
+        //int[] allPopSizes    = new int[1+hist.getTotalLength()];
+
+        // What sizes are "interesting"?
+        int targetLowFVC = -1;
+        int targetHighFVC = -1;
+
+        // For each length, adjust the currentXSize model, and look for a win.
+        int[][] matrix = hist.getMatrix();
+        int mrow = -1;
+        int mcol = 1;
+        int mrowFreq = 0;
+        for (int fvcount = 1; fvcount <= hist.getTotalLength(); fvcount++) {
+            // The {F} list gets an additional member.
+            // Take it from the end of the current matrix row.
+            // (It's the end, so that we get larger favored values first.)
+            if (mcol == 1) {
+                mrow += 1;
+                mrowFreq = matrix[mrow][0];
+                mcol = matrix[mrow].length;
+            }
+            int thisValue = matrix[mrow][--mcol];
+            allFavoredValues[fvcount] = thisValue;
+            int thisVLen = favoredCoding.getLength(thisValue);
+            currentFSize += thisVLen;
+            // The token list replaces occurrences of zero with a new token:
+            int thisVCount = mrowFreq;
+            int thisToken = fvcount;
+            currentTSize += (tokenCoding.getLength(thisToken)
+                             - ZERO_LEN) * thisVCount;
+            // The unfavored list loses occurrences of the newly favored value.
+            // (This is the whole point of the exercise!)
+            currentUSize -= thisVLen * thisVCount;
+            int currentSize = (currentFSize + currentTSize + currentUSize);
+            //allPopSizes[fvcount] = currentSize;
+            if (bestPopSize > currentSize) {
+                if (currentSize <= targetSize) {
+                    targetHighFVC = fvcount;
+                    if (targetLowFVC < 0)
+                        targetLowFVC = fvcount;
+                    if (verbose > 4)
+                        Utils.log.info("better pop-size at fvc="+fvcount+
+                                         " by "+pct(bestPopSize-currentSize,
+                                                    bestPopSize));
+                }
+                bestPopSize = currentSize;
+                bestPopFVC = fvcount;
+            }
+        }
+        if (targetLowFVC < 0) {
+            if (verbose > 1) {
+                // Complete loss.
+                if (verbose > 1)
+                    Utils.log.info("no good pop-size; best was "+
+                                     bestPopSize+" at "+bestPopFVC+
+                                     " worse by "+
+                                     pct(bestPopSize-bestByteSize,
+                                         bestByteSize));
+            }
+            return;
+        }
+        if (verbose > 1)
+            Utils.log.info("initial best pop-size at fvc="+bestPopFVC+
+                             " in ["+targetLowFVC+".."+targetHighFVC+"]"+
+                             " by "+pct(bestByteSize-bestPopSize,
+                                        bestByteSize));
+        int oldZipSize = bestZipSize;
+        // Now close onto a specific coding, testing more rigorously
+        // with the zipSize metric.
+        // Questions to decide:
+        //   1. How many favored values?
+        //   2. What token coding (TC)?
+        //   3. Sort favored values by value within length brackets?
+        //   4. What favored coding?
+        //   5. What unfavored coding?
+        // Steps 1/2/3 are interdependent, and may be iterated.
+        // Steps 4 and 5 may be decided independently afterward.
+        int[] LValuesCoded = PopulationCoding.LValuesCoded;
+        List bestFits = new ArrayList<>();
+        List fullFits = new ArrayList<>();
+        List longFits = new ArrayList<>();
+        final int PACK_TO_MAX_S = 1;
+        if (bestPopFVC <= 255) {
+            bestFits.add(BandStructure.BYTE1);
+        } else {
+            int bestB = Coding.B_MAX;
+            boolean doFullAlso = (effort > POP_EFFORT);
+            if (doFullAlso)
+                fullFits.add(BandStructure.BYTE1.setS(PACK_TO_MAX_S));
+            for (int i = LValuesCoded.length-1; i >= 1; i--) {
+                int L = LValuesCoded[i];
+                Coding c0 = PopulationCoding.fitTokenCoding(targetLowFVC,  L);
+                Coding c1 = PopulationCoding.fitTokenCoding(bestPopFVC,    L);
+                Coding c3 = PopulationCoding.fitTokenCoding(targetHighFVC, L);
+                if (c1 != null) {
+                    if (!bestFits.contains(c1))
+                        bestFits.add(c1);
+                    if (bestB > c1.B())
+                        bestB = c1.B();
+                }
+                if (doFullAlso) {
+                    if (c3 == null)  c3 = c1;
+                    for (int B = c0.B(); B <= c3.B(); B++) {
+                        if (B == c1.B())  continue;
+                        if (B == 1)  continue;
+                        Coding c2 = c3.setB(B).setS(PACK_TO_MAX_S);
+                        if (!fullFits.contains(c2))
+                            fullFits.add(c2);
+                    }
+                }
+            }
+            // interleave all B greater than bestB with best and full fits
+            for (Iterator i = bestFits.iterator(); i.hasNext(); ) {
+                Coding c = i.next();
+                if (c.B() > bestB) {
+                    i.remove();
+                    longFits.add(0, c);
+                }
+            }
+        }
+        List allFits = new ArrayList<>();
+        for (Iterator i = bestFits.iterator(),
+                      j = fullFits.iterator(),
+                      k = longFits.iterator();
+             i.hasNext() || j.hasNext() || k.hasNext(); ) {
+            if (i.hasNext())  allFits.add(i.next());
+            if (j.hasNext())  allFits.add(j.next());
+            if (k.hasNext())  allFits.add(k.next());
+        }
+        bestFits.clear();
+        fullFits.clear();
+        longFits.clear();
+        int maxFits = allFits.size();
+        if (effort == POP_EFFORT)
+            maxFits = 2;
+        else if (maxFits > 4) {
+            maxFits -= 4;
+            maxFits = (maxFits * (effort-POP_EFFORT)
+                       ) / (MAX_EFFORT-POP_EFFORT);
+            maxFits += 4;
+        }
+        if (allFits.size() > maxFits) {
+            if (verbose > 4)
+                Utils.log.info("allFits before clip: "+allFits);
+            allFits.subList(maxFits, allFits.size()).clear();
+        }
+        if (verbose > 3)
+            Utils.log.info("allFits: "+allFits);
+        for (Coding tc : allFits) {
+            boolean packToMax = false;
+            if (tc.S() == PACK_TO_MAX_S) {
+                // Kludge:  setS(PACK_TO_MAX_S) means packToMax here.
+                packToMax = true;
+                tc = tc.setS(0);
+            }
+            int fVlen;
+            if (!packToMax) {
+                fVlen = bestPopFVC;
+                assert(tc.umax() >= fVlen);
+                assert(tc.B() == 1 || tc.setB(tc.B()-1).umax() < fVlen);
+            } else {
+                fVlen = Math.min(tc.umax(), targetHighFVC);
+                if (fVlen < targetLowFVC)
+                    continue;
+                if (fVlen == bestPopFVC)
+                    continue;  // redundant test
+            }
+            PopulationCoding pop = new PopulationCoding();
+            pop.setHistogram(hist);
+            pop.setL(tc.L());
+            pop.setFavoredValues(allFavoredValues, fVlen);
+            assert(pop.tokenCoding == tc);  // predict correctly
+            pop.resortFavoredValues();
+            int[] tcsizes =
+                computePopSizePrivate(pop,
+                                      favoredCoding, unfavoredCoding);
+            noteSizes(pop, tcsizes[BYTE_SIZE], BAND_HEADER+tcsizes[ZIP_SIZE]);
+        }
+        if (verbose > 3) {
+            Utils.log.info("measured best pop, size="+bestByteSize+
+                             "/zs="+bestZipSize+
+                             " better by "+
+                             pct(oldZipSize-bestZipSize, oldZipSize));
+            if (bestZipSize < oldZipSize) {
+                Utils.log.info(">>> POP WINS BY "+
+                                 (oldZipSize - bestZipSize));
+            }
+        }
+    }
+
+    private
+    int[] computePopSizePrivate(PopulationCoding pop,
+                                Coding favoredCoding,
+                                Coding unfavoredCoding) {
+        if (popHelper == null) {
+            popHelper = new CodingChooser(effort, allCodingChoices);
+            if (stress != null)
+                popHelper.addStressSeed(stress.nextInt());
+            popHelper.topLevel = false;
+            popHelper.verbose -= 1;
+            popHelper.disablePopCoding = true;
+            popHelper.disableRunCoding = this.disableRunCoding;
+            if (effort < MID_EFFORT)
+                // No nested run codings.
+                popHelper.disableRunCoding = true;
+        }
+        int fVlen = pop.fVlen;
+        if (verbose > 2) {
+            Utils.log.info("computePopSizePrivate fvlen="+fVlen+
+                             " tc="+pop.tokenCoding);
+            Utils.log.info("{ //BEGIN");
+        }
+
+        // Find good coding choices for the token and unfavored sequences.
+        int[] favoredValues = pop.fValues;
+        int[][] vals = pop.encodeValues(values, start, end);
+        int[] tokens = vals[0];
+        int[] unfavoredValues = vals[1];
+        if (verbose > 2)
+            Utils.log.info("-- refine on fv["+fVlen+"] fc="+favoredCoding);
+        pop.setFavoredCoding(popHelper.choose(favoredValues, 1, 1+fVlen, favoredCoding));
+        if (pop.tokenCoding instanceof Coding &&
+            (stress == null || stress.nextBoolean())) {
+            if (verbose > 2)
+                Utils.log.info("-- refine on tv["+tokens.length+"] tc="+pop.tokenCoding);
+            CodingMethod tc = popHelper.choose(tokens, (Coding) pop.tokenCoding);
+            if (tc != pop.tokenCoding) {
+                if (verbose > 2)
+                    Utils.log.info(">>> refined tc="+tc);
+                pop.setTokenCoding(tc);
+            }
+        }
+        if (unfavoredValues.length == 0)
+            pop.setUnfavoredCoding(null);
+        else {
+            if (verbose > 2)
+                Utils.log.info("-- refine on uv["+unfavoredValues.length+"] uc="+pop.unfavoredCoding);
+            pop.setUnfavoredCoding(popHelper.choose(unfavoredValues, unfavoredCoding));
+        }
+        if (verbose > 3) {
+            Utils.log.info("finish computePopSizePrivate fvlen="+fVlen+
+                             " fc="+pop.favoredCoding+
+                             " tc="+pop.tokenCoding+
+                             " uc="+pop.unfavoredCoding);
+            //pop.hist.print("pop-hist", null, System.out);
+            StringBuilder sb = new StringBuilder();
+            sb.append("fv = {");
+            for (int i = 1; i <= fVlen; i++) {
+                if ((i % 10) == 0)
+                    sb.append('\n');
+                sb.append(" ").append(favoredValues[i]);
+            }
+            sb.append('\n');
+            sb.append("}");
+            Utils.log.info(sb.toString());
+        }
+        if (verbose > 2) {
+            Utils.log.info("} //END");
+        }
+        if (stress != null) {
+            return null;  // do not bother with size computation
+        }
+        int[] sizes;
+        try {
+            resetData();
+            // Write the array of favored values.
+            pop.writeSequencesTo(byteSizer, tokens, unfavoredValues);
+            sizes = new int[] { getByteSize(), getZipSize() };
+        } catch (IOException ee) {
+            throw new RuntimeException(ee); // cannot happen
+        }
+        int[] checkSizes = null;
+        assert((checkSizes = computeSizePrivate(pop)) != null);
+        assert(checkSizes[BYTE_SIZE] == sizes[BYTE_SIZE])
+            : (checkSizes[BYTE_SIZE]+" != "+sizes[BYTE_SIZE]);
+        return sizes;
+    }
+
+    private void tryAdaptiveCoding(Coding plainCoding) {
+        int oldZipSize = bestZipSize;
+        // Scan the value sequence, determining whether an interesting
+        // run occupies too much space.  ("Too much" means, say 5% more
+        // than the average integer size of the band as a whole.)
+        // Try to find a better coding for those segments.
+        int   lstart  = this.start;
+        int   lend    = this.end;
+        int[] lvalues = this.values;
+        int len = lend-lstart;
+        if (plainCoding.isDelta()) {
+            lvalues = getDeltas(0,0); //%%% not quite right!
+            lstart = 0;
+            lend = lvalues.length;
+        }
+        int[] sizes = new int[len+1];
+        int fillp = 0;
+        int totalSize = 0;
+        for (int i = lstart; i < lend; i++) {
+            int val = lvalues[i];
+            sizes[fillp++] = totalSize;
+            int size = plainCoding.getLength(val);
+            assert(size < Integer.MAX_VALUE);
+            //System.out.println("len "+val+" = "+size);
+            totalSize += size;
+        }
+        sizes[fillp++] = totalSize;
+        assert(fillp == sizes.length);
+        double avgSize = (double)totalSize / len;
+        double sizeFuzz;
+        double sizeFuzz2;
+        double sizeFuzz3;
+        if (effort >= MID_EFFORT) {
+            if (effort > MID_EFFORT+1)
+                sizeFuzz = 1.001;
+            else
+                sizeFuzz = 1.003;
+        } else {
+            if (effort > RUN_EFFORT)
+                sizeFuzz = 1.01;
+            else
+                sizeFuzz = 1.03;
+        }
+        // for now:
+        sizeFuzz *= sizeFuzz; // double the thresh
+        sizeFuzz2 = (sizeFuzz*sizeFuzz);
+        sizeFuzz3 = (sizeFuzz*sizeFuzz*sizeFuzz);
+        // Find some mesh scales we like.
+        double[] dmeshes = new double[1 + (effort-RUN_EFFORT)];
+        double logLen = Math.log(len);
+        for (int i = 0; i < dmeshes.length; i++) {
+            dmeshes[i] = Math.exp(logLen*(i+1)/(dmeshes.length+1));
+        }
+        int[] meshes = new int[dmeshes.length];
+        int mfillp = 0;
+        for (int i = 0; i < dmeshes.length; i++) {
+            int m = (int)Math.round(dmeshes[i]);
+            m = AdaptiveCoding.getNextK(m-1);
+            if (m <= 0 || m >= len)  continue;
+            if (mfillp > 0 && m == meshes[mfillp-1])  continue;
+            meshes[mfillp++] = m;
+        }
+        meshes = BandStructure.realloc(meshes, mfillp);
+        // There's going to be a band header.  Estimate conservatively large.
+        final int BAND_HEADER = 4; // op, KB, A, B
+        // Threshold values for a "too big" mesh.
+        int[]    threshes = new int[meshes.length];
+        double[] fuzzes   = new double[meshes.length];
+        for (int i = 0; i < meshes.length; i++) {
+            int mesh = meshes[i];
+            double lfuzz;
+            if (mesh < 10)
+                lfuzz = sizeFuzz3;
+            else if (mesh < 100)
+                lfuzz = sizeFuzz2;
+            else
+                lfuzz = sizeFuzz;
+            fuzzes[i] = lfuzz;
+            threshes[i] = BAND_HEADER + (int)Math.ceil(mesh * avgSize * lfuzz);
+        }
+        if (verbose > 1) {
+            System.out.print("tryAdaptiveCoding ["+len+"]"+
+                             " avgS="+avgSize+" fuzz="+sizeFuzz+
+                             " meshes: {");
+            for (int i = 0; i < meshes.length; i++) {
+                System.out.print(" " + meshes[i] + "(" + threshes[i] + ")");
+            }
+            Utils.log.info(" }");
+        }
+        if (runHelper == null) {
+            runHelper = new CodingChooser(effort, allCodingChoices);
+            if (stress != null)
+                runHelper.addStressSeed(stress.nextInt());
+            runHelper.topLevel = false;
+            runHelper.verbose -= 1;
+            runHelper.disableRunCoding = true;
+            runHelper.disablePopCoding = this.disablePopCoding;
+            if (effort < MID_EFFORT)
+                // No nested pop codings.
+                runHelper.disablePopCoding = true;
+        }
+        for (int i = 0; i < len; i++) {
+            i = AdaptiveCoding.getNextK(i-1);
+            if (i > len)  i = len;
+            for (int j = meshes.length-1; j >= 0; j--) {
+                int mesh   = meshes[j];
+                int thresh = threshes[j];
+                if (i+mesh > len)  continue;
+                int size = sizes[i+mesh] - sizes[i];
+                if (size >= thresh) {
+                    // Found a size bulge.
+                    int bend  = i+mesh;
+                    int bsize = size;
+                    double bigSize = avgSize * fuzzes[j];
+                    while (bend < len && (bend-i) <= len/2) {
+                        int bend0 = bend;
+                        int bsize0 = bsize;
+                        bend += mesh;
+                        bend = i+AdaptiveCoding.getNextK(bend-i-1);
+                        if (bend < 0 || bend > len)
+                            bend = len;
+                        bsize = sizes[bend]-sizes[i];
+                        if (bsize < BAND_HEADER + (bend-i) * bigSize) {
+                            bsize = bsize0;
+                            bend = bend0;
+                            break;
+                        }
+                    }
+                    int nexti = bend;
+                    if (verbose > 2) {
+                        Utils.log.info("bulge at "+i+"["+(bend-i)+"] of "+
+                                         pct(bsize - avgSize*(bend-i),
+                                             avgSize*(bend-i)));
+                        Utils.log.info("{ //BEGIN");
+                    }
+                    CodingMethod begcm, midcm, endcm;
+                    midcm = runHelper.choose(this.values,
+                                             this.start+i,
+                                             this.start+bend,
+                                             plainCoding);
+                    if (midcm == plainCoding) {
+                        // No use working further.
+                        begcm = plainCoding;
+                        endcm = plainCoding;
+                    } else {
+                        begcm = runHelper.choose(this.values,
+                                                 this.start,
+                                                 this.start+i,
+                                                 plainCoding);
+                        endcm = runHelper.choose(this.values,
+                                                 this.start+bend,
+                                                 this.start+len,
+                                                 plainCoding);
+                    }
+                    if (verbose > 2)
+                        Utils.log.info("} //END");
+                    if (begcm == midcm && i > 0 &&
+                        AdaptiveCoding.isCodableLength(bend)) {
+                        i = 0;
+                    }
+                    if (midcm == endcm && bend < len) {
+                        bend = len;
+                    }
+                    if (begcm != plainCoding ||
+                        midcm != plainCoding ||
+                        endcm != plainCoding) {
+                        CodingMethod chain;
+                        int hlen = 0;
+                        if (bend == len) {
+                            chain = midcm;
+                        } else {
+                            chain = new AdaptiveCoding(bend-i, midcm, endcm);
+                            hlen += BAND_HEADER;
+                        }
+                        if (i > 0) {
+                            chain = new AdaptiveCoding(i, begcm, chain);
+                            hlen += BAND_HEADER;
+                        }
+                        int[] chainSize = computeSizePrivate(chain);
+                        noteSizes(chain,
+                                  chainSize[BYTE_SIZE],
+                                  chainSize[ZIP_SIZE]+hlen);
+                    }
+                    i = nexti;
+                    break;
+                }
+            }
+        }
+        if (verbose > 3) {
+            if (bestZipSize < oldZipSize) {
+                Utils.log.info(">>> RUN WINS BY "+
+                                 (oldZipSize - bestZipSize));
+            }
+        }
+    }
+
+    private static
+    String pct(double num, double den) {
+        return (Math.round((num / den)*10000)/100.0)+"%";
+    }
+
+    static
+    class Sizer extends OutputStream {
+        final OutputStream out;  // if non-null, copy output here also
+        Sizer(OutputStream out) {
+            this.out = out;
+        }
+        Sizer() {
+            this(null);
+        }
+        private int count;
+        public void write(int b) throws IOException {
+            count++;
+            if (out != null)  out.write(b);
+        }
+        public void write(byte b[], int off, int len) throws IOException {
+            count += len;
+            if (out != null)  out.write(b, off, len);
+        }
+        public void reset() {
+            count = 0;
+        }
+        public int getSize() { return count; }
+
+        public String toString() {
+            String str = super.toString();
+            // If -ea, print out more informative strings!
+            assert((str = stringForDebug()) != null);
+            return str;
+        }
+        String stringForDebug() {
+            return "";
+        }
+    }
+
+    private Sizer zipSizer  = new Sizer();
+    private Deflater zipDef = new Deflater();
+    private DeflaterOutputStream zipOut = new DeflaterOutputStream(zipSizer, zipDef);
+    private Sizer byteSizer = new Sizer(zipOut);
+    private Sizer byteOnlySizer = new Sizer();
+
+    private void resetData() {
+        flushData();
+        zipDef.reset();
+        if (context != null) {
+            // Prepend given salt to the test output.
+            try {
+                context.writeTo(byteSizer);
+            } catch (IOException ee) {
+                throw new RuntimeException(ee); // cannot happen
+            }
+        }
+        zipSizer.reset();
+        byteSizer.reset();
+    }
+    private void flushData() {
+        try {
+            zipOut.finish();
+        } catch (IOException ee) {
+            throw new RuntimeException(ee); // cannot happen
+        }
+    }
+    private int getByteSize() {
+        return byteSizer.getSize();
+    }
+    private int getZipSize() {
+        flushData();
+        return zipSizer.getSize();
+    }
+
+
+    /// Stress-test helpers.
+
+    void addStressSeed(int x) {
+        if (stress == null)  return;
+        stress.setSeed(x + ((long)stress.nextInt() << 32));
+    }
+
+    // Pick a random pop-coding.
+    private CodingMethod stressPopCoding(CodingMethod coding) {
+        assert(stress != null);  // this method is only for testing
+        // Don't turn it into a pop coding if it's already something special.
+        if (!(coding instanceof Coding))  return coding;
+        Coding valueCoding = ((Coding)coding).getValueCoding();
+        Histogram hist = getValueHistogram();
+        int fVlen = stressLen(hist.getTotalLength());
+        if (fVlen == 0)  return coding;
+        List popvals = new ArrayList<>();
+        if (stress.nextBoolean()) {
+            // Build the population from the value list.
+            Set popset = new HashSet<>();
+            for (int i = start; i < end; i++) {
+                if (popset.add(values[i]))  popvals.add(values[i]);
+            }
+        } else {
+            int[][] matrix = hist.getMatrix();
+            for (int mrow = 0; mrow < matrix.length; mrow++) {
+                int[] row = matrix[mrow];
+                for (int mcol = 1; mcol < row.length; mcol++) {
+                    popvals.add(row[mcol]);
+                }
+            }
+        }
+        int reorder = stress.nextInt();
+        if ((reorder & 7) <= 2) {
+            // Lose the order.
+            Collections.shuffle(popvals, stress);
+        } else {
+            // Keep the order, mostly.
+            if (((reorder >>>= 3) & 7) <= 2)  Collections.sort(popvals);
+            if (((reorder >>>= 3) & 7) <= 2)  Collections.reverse(popvals);
+            if (((reorder >>>= 3) & 7) <= 2)  Collections.rotate(popvals, stressLen(popvals.size()));
+        }
+        if (popvals.size() > fVlen) {
+            // Cut the list down.
+            if (((reorder >>>= 3) & 7) <= 2) {
+                // Cut at end.
+                popvals.subList(fVlen,   popvals.size()).clear();
+            } else {
+                // Cut at start.
+                popvals.subList(0, popvals.size()-fVlen).clear();
+            }
+        }
+        fVlen = popvals.size();
+        int[] fvals = new int[1+fVlen];
+        for (int i = 0; i < fVlen; i++) {
+            fvals[1+i] = (popvals.get(i)).intValue();
+        }
+        PopulationCoding pop = new PopulationCoding();
+        pop.setFavoredValues(fvals, fVlen);
+        int[] lvals = PopulationCoding.LValuesCoded;
+        for (int i = 0; i < lvals.length / 2; i++) {
+            int popl = lvals[stress.nextInt(lvals.length)];
+            if (popl < 0)  continue;
+            if (PopulationCoding.fitTokenCoding(fVlen, popl) != null) {
+                pop.setL(popl);
+                break;
+            }
+        }
+        if (pop.tokenCoding == null) {
+            int lmin = fvals[1], lmax = lmin;
+            for (int i = 2; i <= fVlen; i++) {
+                int val = fvals[i];
+                if (lmin > val)  lmin = val;
+                if (lmax < val)  lmax = val;
+            }
+            pop.tokenCoding = stressCoding(lmin, lmax);
+        }
+
+        computePopSizePrivate(pop, valueCoding, valueCoding);
+        return pop;
+    }
+
+    // Pick a random adaptive coding.
+    private CodingMethod stressAdaptiveCoding(CodingMethod coding) {
+        assert(stress != null);  // this method is only for testing
+        // Don't turn it into a run coding if it's already something special.
+        if (!(coding instanceof Coding))  return coding;
+        Coding plainCoding = (Coding)coding;
+        int len = end-start;
+        if (len < 2)  return coding;
+        // Decide how many spans we'll create.
+        int spanlen = stressLen(len-1)+1;
+        if (spanlen == len)  return coding;
+        try {
+            assert(!disableRunCoding);
+            disableRunCoding = true;  // temporary, while I decide spans
+            int[] allValues = values.clone();
+            CodingMethod result = null;
+            int scan  = this.end;
+            int lstart = this.start;
+            for (int split; scan > lstart; scan = split) {
+                int thisspan;
+                int rand = (scan - lstart < 100)? -1: stress.nextInt();
+                if ((rand & 7) != 0) {
+                    thisspan = (spanlen==1? spanlen: stressLen(spanlen-1)+1);
+                } else {
+                    // Every so often generate a value based on KX/KB format.
+                    int KX = (rand >>>= 3) & AdaptiveCoding.KX_MAX;
+                    int KB = (rand >>>= 3) & AdaptiveCoding.KB_MAX;
+                    for (;;) {
+                        thisspan = AdaptiveCoding.decodeK(KX, KB);
+                        if (thisspan <= scan - lstart)  break;
+                        // Try smaller and smaller codings:
+                        if (KB != AdaptiveCoding.KB_DEFAULT)
+                            KB = AdaptiveCoding.KB_DEFAULT;
+                        else
+                            KX -= 1;
+                    }
+                    //System.out.println("KX="+KX+" KB="+KB+" K="+thisspan);
+                    assert(AdaptiveCoding.isCodableLength(thisspan));
+                }
+                if (thisspan > scan - lstart)  thisspan = scan - lstart;
+                while (!AdaptiveCoding.isCodableLength(thisspan)) {
+                    --thisspan;
+                }
+                split = scan - thisspan;
+                assert(split < scan);
+                assert(split >= lstart);
+                // Choose a coding for the span [split..scan).
+                CodingMethod sc = choose(allValues, split, scan, plainCoding);
+                if (result == null) {
+                    result = sc;  // the caboose
+                } else {
+                    result = new AdaptiveCoding(scan-split, sc, result);
+                }
+            }
+            return result;
+        } finally {
+            disableRunCoding = false; // return to normal value
+        }
+    }
+
+    // Return a random value in [0..len], gently biased toward extremes.
+    private Coding stressCoding(int min, int max) {
+        assert(stress != null);  // this method is only for testing
+        for (int i = 0; i < 100; i++) {
+            Coding c = Coding.of(stress.nextInt(Coding.B_MAX)+1,
+                                 stress.nextInt(Coding.H_MAX)+1,
+                                 stress.nextInt(Coding.S_MAX+1));
+            if (c.B() == 1)  c = c.setH(256);
+            if (c.H() == 256 && c.B() >= 5)  c = c.setB(4);
+            if (stress.nextBoolean()) {
+                Coding dc = c.setD(1);
+                if (dc.canRepresent(min, max))  return dc;
+            }
+            if (c.canRepresent(min, max))  return c;
+        }
+        return BandStructure.UNSIGNED5;
+    }
+
+    // Return a random value in [0..len], gently biased toward extremes.
+    private int stressLen(int len) {
+        assert(stress != null);  // this method is only for testing
+        assert(len >= 0);
+        int rand = stress.nextInt(100);
+        if (rand < 20)
+            return Math.min(len/5, rand);
+        else if (rand < 40)
+            return len;
+        else
+            return stress.nextInt(len);
+    }
+
+    // For debug only.
+/*
+    public static
+    int[] readValuesFrom(InputStream instr) {
+        return readValuesFrom(new InputStreamReader(instr));
+    }
+    public static
+    int[] readValuesFrom(Reader inrdr) {
+        inrdr = new BufferedReader(inrdr);
+        final StreamTokenizer in = new StreamTokenizer(inrdr);
+        final int TT_NOTHING = -99;
+        in.commentChar('#');
+        return readValuesFrom(new Iterator() {
+            int token = TT_NOTHING;
+            private int getToken() {
+                if (token == TT_NOTHING) {
+                    try {
+                        token = in.nextToken();
+                        assert(token != TT_NOTHING);
+                    } catch (IOException ee) {
+                        throw new RuntimeException(ee);
+                    }
+                }
+                return token;
+            }
+            public boolean hasNext() {
+                return getToken() != StreamTokenizer.TT_EOF;
+            }
+            public Object next() {
+                int ntok = getToken();
+                token = TT_NOTHING;
+                switch (ntok) {
+                case StreamTokenizer.TT_EOF:
+                    throw new NoSuchElementException();
+                case StreamTokenizer.TT_NUMBER:
+                    return Integer.valueOf((int) in.nval);
+                default:
+                    assert(false);
+                    return null;
+                }
+            }
+            public void remove() {
+                throw new UnsupportedOperationException();
+            }
+        });
+    }
+    public static
+    int[] readValuesFrom(Iterator iter) {
+        return readValuesFrom(iter, 0);
+    }
+    public static
+    int[] readValuesFrom(Iterator iter, int initSize) {
+        int[] na = new int[Math.max(10, initSize)];
+        int np = 0;
+        while (iter.hasNext()) {
+            Integer val = (Integer) iter.next();
+            if (np == na.length) {
+                na = BandStructure.realloc(na);
+            }
+            na[np++] = val.intValue();
+        }
+        if (np != na.length) {
+            na = BandStructure.realloc(na, np);
+        }
+        return na;
+    }
+
+    public static
+    void main(String[] av) throws IOException {
+        int effort = MID_EFFORT;
+        int ap = 0;
+        if (ap < av.length && av[ap].equals("-e")) {
+            ap++;
+            effort = Integer.parseInt(av[ap++]);
+        }
+        int verbose = 1;
+        if (ap < av.length && av[ap].equals("-v")) {
+            ap++;
+            verbose = Integer.parseInt(av[ap++]);
+        }
+        Coding[] bcs = BandStructure.getBasicCodings();
+        CodingChooser cc = new CodingChooser(effort, bcs);
+        if (ap < av.length && av[ap].equals("-p")) {
+            ap++;
+            cc.optUsePopulationCoding = false;
+        }
+        if (ap < av.length && av[ap].equals("-a")) {
+            ap++;
+            cc.optUseAdaptiveCoding = false;
+        }
+        cc.verbose = verbose;
+        int[] values = readValuesFrom(System.in);
+        int[] sizes = {0,0};
+        CodingMethod cm = cc.choose(values, BandStructure.UNSIGNED5, sizes);
+        System.out.println("size: "+sizes[BYTE_SIZE]+"/zs="+sizes[ZIP_SIZE]);
+        System.out.println(cm);
+    }
+//*/
+
+}
diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/CodingMethod.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/CodingMethod.java
new file mode 100644
index 000000000..884885a97
--- /dev/null
+++ b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/CodingMethod.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package net.fabricmc.shade.com.sun.java.util.jar.pack;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Interface for encoding and decoding int arrays using bytewise codes.
+ * @author John Rose
+ */
+interface CodingMethod {
+    // Read and write an array of ints from/to a stream.
+    public void readArrayFrom(InputStream in, int[] a, int start, int end) throws IOException;
+    public void writeArrayTo(OutputStream out, int[] a, int start, int end) throws IOException;
+
+    // how to express me in a band header?
+    public byte[] getMetaCoding(Coding dflt);
+}
diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/ConstantPool.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/ConstantPool.java
new file mode 100644
index 000000000..4d44494ed
--- /dev/null
+++ b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/ConstantPool.java
@@ -0,0 +1,1657 @@
+/*
+ * Copyright (c) 2001, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package net.fabricmc.shade.com.sun.java.util.jar.pack;
+
+import java.util.AbstractList;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Representation of constant pool entries and indexes.
+ * @author John Rose
+ */
+abstract
+class ConstantPool {
+    private ConstantPool() {}  // do not instantiate
+
+    static int verbose() {
+        return Utils.currentPropMap().getInteger(Utils.DEBUG_VERBOSE);
+    }
+
+    /** Factory for Utf8 string constants.
+     *  Used for well-known strings like "SourceFile", "", etc.
+     *  Also used to back up more complex constant pool entries, like Class.
+     */
+    public static synchronized Utf8Entry getUtf8Entry(String value) {
+        Map utf8Entries  = Utils.getTLGlobals().getUtf8Entries();
+        Utf8Entry e = utf8Entries.get(value);
+        if (e == null) {
+            e = new Utf8Entry(value);
+            utf8Entries.put(e.stringValue(), e);
+        }
+        return e;
+    }
+    /** Factory for Class constants. */
+    public static ClassEntry getClassEntry(String name) {
+        Map classEntries = Utils.getTLGlobals().getClassEntries();
+        ClassEntry e = classEntries.get(name);
+        if (e == null) {
+            e = new ClassEntry(getUtf8Entry(name));
+            assert(name.equals(e.stringValue()));
+            classEntries.put(e.stringValue(), e);
+        }
+        return e;
+    }
+    /** Factory for literal constants (String, Integer, etc.). */
+    public static LiteralEntry getLiteralEntry(Comparable value) {
+        Map literalEntries = Utils.getTLGlobals().getLiteralEntries();
+        LiteralEntry e = literalEntries.get(value);
+        if (e == null) {
+            if (value instanceof String)
+                e = new StringEntry(getUtf8Entry((String)value));
+            else
+                e = new NumberEntry((Number)value);
+            literalEntries.put(value, e);
+        }
+        return e;
+    }
+    /** Factory for literal constants (String, Integer, etc.). */
+    public static StringEntry getStringEntry(String value) {
+        return (StringEntry) getLiteralEntry(value);
+    }
+
+    /** Factory for signature (type) constants. */
+    public static SignatureEntry getSignatureEntry(String type) {
+        Map signatureEntries = Utils.getTLGlobals().getSignatureEntries();
+        SignatureEntry e = signatureEntries.get(type);
+        if (e == null) {
+            e = new SignatureEntry(type);
+            assert(e.stringValue().equals(type));
+            signatureEntries.put(type, e);
+        }
+        return e;
+    }
+    // Convenience overloading.
+    public static SignatureEntry getSignatureEntry(Utf8Entry formRef, ClassEntry[] classRefs) {
+        return getSignatureEntry(SignatureEntry.stringValueOf(formRef, classRefs));
+    }
+
+    /** Factory for descriptor (name-and-type) constants. */
+    public static DescriptorEntry getDescriptorEntry(Utf8Entry nameRef, SignatureEntry typeRef) {
+        Map descriptorEntries = Utils.getTLGlobals().getDescriptorEntries();
+        String key = DescriptorEntry.stringValueOf(nameRef, typeRef);
+        DescriptorEntry e = descriptorEntries.get(key);
+        if (e == null) {
+            e = new DescriptorEntry(nameRef, typeRef);
+            assert(e.stringValue().equals(key))
+                : (e.stringValue()+" != "+(key));
+            descriptorEntries.put(key, e);
+        }
+        return e;
+    }
+    // Convenience overloading.
+    public static DescriptorEntry getDescriptorEntry(Utf8Entry nameRef, Utf8Entry typeRef) {
+        return getDescriptorEntry(nameRef, getSignatureEntry(typeRef.stringValue()));
+    }
+
+    /** Factory for member reference constants. */
+    public static MemberEntry getMemberEntry(byte tag, ClassEntry classRef, DescriptorEntry descRef) {
+        Map memberEntries = Utils.getTLGlobals().getMemberEntries();
+        String key = MemberEntry.stringValueOf(tag, classRef, descRef);
+        MemberEntry e = memberEntries.get(key);
+        if (e == null) {
+            e = new MemberEntry(tag, classRef, descRef);
+            assert(e.stringValue().equals(key))
+                : (e.stringValue()+" != "+(key));
+            memberEntries.put(key, e);
+        }
+        return e;
+    }
+
+    /** Factory for MethodHandle constants. */
+    public static MethodHandleEntry getMethodHandleEntry(byte refKind, MemberEntry memRef) {
+        Map methodHandleEntries = Utils.getTLGlobals().getMethodHandleEntries();
+        String key = MethodHandleEntry.stringValueOf(refKind, memRef);
+        MethodHandleEntry e = methodHandleEntries.get(key);
+        if (e == null) {
+            e = new MethodHandleEntry(refKind, memRef);
+            assert(e.stringValue().equals(key));
+            methodHandleEntries.put(key, e);
+        }
+        return e;
+    }
+
+    /** Factory for MethodType constants. */
+    public static MethodTypeEntry getMethodTypeEntry(SignatureEntry sigRef) {
+        Map methodTypeEntries = Utils.getTLGlobals().getMethodTypeEntries();
+        String key = sigRef.stringValue();
+        MethodTypeEntry e = methodTypeEntries.get(key);
+        if (e == null) {
+            e = new MethodTypeEntry(sigRef);
+            assert(e.stringValue().equals(key));
+            methodTypeEntries.put(key, e);
+        }
+        return e;
+    }
+    public static MethodTypeEntry getMethodTypeEntry(Utf8Entry typeRef) {
+        return getMethodTypeEntry(getSignatureEntry(typeRef.stringValue()));
+    }
+
+    /** Factory for InvokeDynamic constants. */
+    public static InvokeDynamicEntry getInvokeDynamicEntry(BootstrapMethodEntry bssRef, DescriptorEntry descRef) {
+        Map invokeDynamicEntries = Utils.getTLGlobals().getInvokeDynamicEntries();
+        String key = InvokeDynamicEntry.stringValueOf(bssRef, descRef);
+        InvokeDynamicEntry e = invokeDynamicEntries.get(key);
+        if (e == null) {
+            e = new InvokeDynamicEntry(bssRef, descRef);
+            assert(e.stringValue().equals(key));
+            invokeDynamicEntries.put(key, e);
+        }
+        return e;
+    }
+
+    /** Factory for BootstrapMethod pseudo-constants. */
+    public static BootstrapMethodEntry getBootstrapMethodEntry(MethodHandleEntry bsmRef, Entry[] argRefs) {
+        Map bootstrapMethodEntries = Utils.getTLGlobals().getBootstrapMethodEntries();
+        String key = BootstrapMethodEntry.stringValueOf(bsmRef, argRefs);
+        BootstrapMethodEntry e = bootstrapMethodEntries.get(key);
+        if (e == null) {
+            e = new BootstrapMethodEntry(bsmRef, argRefs);
+            assert(e.stringValue().equals(key));
+            bootstrapMethodEntries.put(key, e);
+        }
+        return e;
+    }
+
+
+    /** Entries in the constant pool. */
+    public abstract static
+    class Entry implements Comparable {
+        protected final byte tag;       // a CONSTANT_foo code
+        protected int valueHash;        // cached hashCode
+
+        protected Entry(byte tag) {
+            this.tag = tag;
+        }
+
+        public final byte getTag() {
+            return tag;
+        }
+
+        public final boolean tagEquals(int tag) {
+            return getTag() == tag;
+        }
+
+        public Entry getRef(int i) {
+            return null;
+        }
+
+        public boolean eq(Entry that) {  // same reference
+            assert(that != null);
+            return this == that || this.equals(that);
+        }
+
+        // Equality of Entries is value-based.
+        public abstract boolean equals(Object o);
+        public final int hashCode() {
+            if (valueHash == 0) {
+                valueHash = computeValueHash();
+                if (valueHash == 0)  valueHash = 1;
+            }
+            return valueHash;
+        }
+        protected abstract int computeValueHash();
+
+        public abstract int compareTo(Object o);
+
+        protected int superCompareTo(Object o) {
+            Entry that = (Entry) o;
+
+            if (this.tag != that.tag) {
+                return TAG_ORDER[this.tag] - TAG_ORDER[that.tag];
+            }
+
+            return 0;  // subclasses must refine this
+        }
+
+        public final boolean isDoubleWord() {
+            return tag == Constants.CONSTANT_Double || tag == Constants.CONSTANT_Long;
+        }
+
+        public final boolean tagMatches(int matchTag) {
+            if (tag == matchTag)
+                return true;
+            byte[] allowedTags;
+            switch (matchTag) {
+                case Constants.CONSTANT_All:
+                    return true;
+                case Constants.CONSTANT_Signature:
+                    return tag == Constants.CONSTANT_Utf8;  // format check also?
+                case Constants.CONSTANT_LoadableValue:
+                    allowedTags = LOADABLE_VALUE_TAGS;
+                    break;
+                case Constants.CONSTANT_AnyMember:
+                    allowedTags = ANY_MEMBER_TAGS;
+                    break;
+                case Constants.CONSTANT_FieldSpecific:
+                    allowedTags = FIELD_SPECIFIC_TAGS;
+                    break;
+                default:
+                    return false;
+            }
+            for (byte b : allowedTags) {
+                if (b == tag)
+                    return true;
+            }
+            return false;
+        }
+
+        public String toString() {
+            String valuePrint = stringValue();
+            if (verbose() > 4) {
+                if (valueHash != 0)
+                    valuePrint += " hash="+valueHash;
+                valuePrint += " id="+System.identityHashCode(this);
+            }
+            return tagName(tag)+"="+valuePrint;
+        }
+        public abstract String stringValue();
+    }
+
+    public static
+    class Utf8Entry extends Entry {
+        final String value;
+
+        Utf8Entry(String value) {
+            super(Constants.CONSTANT_Utf8);
+            this.value = value.intern();
+            hashCode();  // force computation of valueHash
+        }
+        protected int computeValueHash() {
+            return value.hashCode();
+        }
+        public boolean equals(Object o) {
+            // Use reference equality of interned strings:
+            return (o != null && o.getClass() == Utf8Entry.class
+                    && ((Utf8Entry) o).value.equals(value));
+        }
+        public int compareTo(Object o) {
+            int x = superCompareTo(o);
+            if (x == 0) {
+                x = value.compareTo(((Utf8Entry)o).value);
+            }
+            return x;
+        }
+        public String stringValue() {
+            return value;
+        }
+    }
+
+    static boolean isMemberTag(byte tag) {
+        switch (tag) {
+        case Constants.CONSTANT_Fieldref:
+        case Constants.CONSTANT_Methodref:
+        case Constants.CONSTANT_InterfaceMethodref:
+            return true;
+        }
+        return false;
+    }
+
+    static byte numberTagOf(Number value) {
+        if (value instanceof Integer)  return Constants.CONSTANT_Integer;
+        if (value instanceof Float)    return Constants.CONSTANT_Float;
+        if (value instanceof Long)     return Constants.CONSTANT_Long;
+        if (value instanceof Double)   return Constants.CONSTANT_Double;
+        throw new RuntimeException("bad literal value "+value);
+    }
+
+    static boolean isRefKind(byte refKind) {
+        return (Constants.REF_getField <= refKind && refKind <= Constants.REF_invokeInterface);
+    }
+
+    public abstract static
+    class LiteralEntry extends Entry {
+        protected LiteralEntry(byte tag) {
+            super(tag);
+        }
+
+        public abstract Comparable literalValue();
+    }
+
+    public static
+    class NumberEntry extends LiteralEntry {
+        final Number value;
+        NumberEntry(Number value) {
+            super(numberTagOf(value));
+            this.value = value;
+            hashCode();  // force computation of valueHash
+        }
+        protected int computeValueHash() {
+            return value.hashCode();
+        }
+
+        public boolean equals(Object o) {
+            return (o != null && o.getClass() == NumberEntry.class
+                    && ((NumberEntry) o).value.equals(value));
+
+        }
+        public int compareTo(Object o) {
+            int x = superCompareTo(o);
+            if (x == 0) {
+                @SuppressWarnings("unchecked")
+                Comparable compValue = (Comparable)value;
+                x = compValue.compareTo(((NumberEntry)o).value);
+            }
+            return x;
+        }
+        public Number numberValue() {
+            return value;
+        }
+        public Comparable literalValue() {
+            return (Comparable) value;
+        }
+        public String stringValue() {
+            return value.toString();
+        }
+    }
+
+    public static
+    class StringEntry extends LiteralEntry {
+        final Utf8Entry ref;
+        public Entry getRef(int i) { return i == 0 ? ref : null; }
+
+        StringEntry(Entry ref) {
+            super(Constants.CONSTANT_String);
+            this.ref = (Utf8Entry) ref;
+            hashCode();  // force computation of valueHash
+        }
+        protected int computeValueHash() {
+            return ref.hashCode() + tag;
+        }
+        public boolean equals(Object o) {
+            return (o != null && o.getClass() == StringEntry.class &&
+                    ((StringEntry)o).ref.eq(ref));
+        }
+        public int compareTo(Object o) {
+            int x = superCompareTo(o);
+            if (x == 0) {
+                x = ref.compareTo(((StringEntry)o).ref);
+            }
+            return x;
+        }
+        public Comparable literalValue() {
+            return ref.stringValue();
+        }
+        public String stringValue() {
+            return ref.stringValue();
+        }
+    }
+
+    public static
+    class ClassEntry extends Entry {
+        final Utf8Entry ref;
+        public Entry getRef(int i) { return i == 0 ? ref : null; }
+
+        protected int computeValueHash() {
+            return ref.hashCode() + tag;
+        }
+        ClassEntry(Entry ref) {
+            super(Constants.CONSTANT_Class);
+            this.ref = (Utf8Entry) ref;
+            hashCode();  // force computation of valueHash
+        }
+        public boolean equals(Object o) {
+            return (o != null && o.getClass() == ClassEntry.class
+                    && ((ClassEntry) o).ref.eq(ref));
+        }
+        public int compareTo(Object o) {
+            int x = superCompareTo(o);
+            if (x == 0) {
+                x = ref.compareTo(((ClassEntry)o).ref);
+            }
+            return x;
+        }
+        public String stringValue() {
+            return ref.stringValue();
+        }
+    }
+
+    public static
+    class DescriptorEntry extends Entry {
+        final Utf8Entry      nameRef;
+        final SignatureEntry typeRef;
+        public Entry getRef(int i) {
+            if (i == 0)  return nameRef;
+            if (i == 1)  return typeRef;
+            return null;
+        }
+        DescriptorEntry(Entry nameRef, Entry typeRef) {
+            super(Constants.CONSTANT_NameandType);
+            if (typeRef instanceof Utf8Entry) {
+                typeRef = getSignatureEntry(typeRef.stringValue());
+            }
+            this.nameRef = (Utf8Entry) nameRef;
+            this.typeRef = (SignatureEntry) typeRef;
+            hashCode();  // force computation of valueHash
+        }
+        protected int computeValueHash() {
+            int hc2 = typeRef.hashCode();
+            return (nameRef.hashCode() + (hc2 << 8)) ^ hc2;
+        }
+        public boolean equals(Object o) {
+            if (o == null || o.getClass() != DescriptorEntry.class) {
+                return false;
+            }
+            DescriptorEntry that = (DescriptorEntry)o;
+            return this.nameRef.eq(that.nameRef)
+                && this.typeRef.eq(that.typeRef);
+        }
+        public int compareTo(Object o) {
+            int x = superCompareTo(o);
+            if (x == 0) {
+                DescriptorEntry that = (DescriptorEntry)o;
+                // Primary key is typeRef, not nameRef.
+                x = this.typeRef.compareTo(that.typeRef);
+                if (x == 0)
+                    x = this.nameRef.compareTo(that.nameRef);
+            }
+            return x;
+        }
+        public String stringValue() {
+            return stringValueOf(nameRef, typeRef);
+        }
+        static
+        String stringValueOf(Entry nameRef, Entry typeRef) {
+            return qualifiedStringValue(typeRef, nameRef);
+        }
+
+        public String prettyString() {
+            return nameRef.stringValue()+typeRef.prettyString();
+        }
+
+        public boolean isMethod() {
+            return typeRef.isMethod();
+        }
+
+        public byte getLiteralTag() {
+            return typeRef.getLiteralTag();
+        }
+    }
+
+    static String qualifiedStringValue(Entry e1, Entry e2) {
+        return qualifiedStringValue(e1.stringValue(), e2.stringValue());
+    }
+    static String qualifiedStringValue(String s1, String s234) {
+        // Qualification by dot must decompose uniquely.  Second string might already be qualified.
+        assert(s1.indexOf('.') < 0);
+        return s1+"."+s234;
+    }
+
+    public static
+    class MemberEntry extends Entry {
+        final ClassEntry classRef;
+        final DescriptorEntry descRef;
+        public Entry getRef(int i) {
+            if (i == 0)  return classRef;
+            if (i == 1)  return descRef;
+            return null;
+        }
+        protected int computeValueHash() {
+            int hc2 = descRef.hashCode();
+            return (classRef.hashCode() + (hc2 << 8)) ^ hc2;
+        }
+
+        MemberEntry(byte tag, ClassEntry classRef, DescriptorEntry descRef) {
+            super(tag);
+            assert(isMemberTag(tag));
+            this.classRef = classRef;
+            this.descRef  = descRef;
+            hashCode();  // force computation of valueHash
+        }
+        public boolean equals(Object o) {
+            if (o == null || o.getClass() != MemberEntry.class) {
+                return false;
+            }
+            MemberEntry that = (MemberEntry)o;
+            return this.classRef.eq(that.classRef)
+                && this.descRef.eq(that.descRef);
+        }
+        public int compareTo(Object o) {
+            int x = superCompareTo(o);
+            if (x == 0) {
+                MemberEntry that = (MemberEntry)o;
+                if (Utils.SORT_MEMBERS_DESCR_MAJOR)
+                    // descRef is transmitted as UDELTA5; sort it first?
+                    x = this.descRef.compareTo(that.descRef);
+                // Primary key is classRef.
+                if (x == 0)
+                    x = this.classRef.compareTo(that.classRef);
+                if (x == 0)
+                    x = this.descRef.compareTo(that.descRef);
+            }
+            return x;
+        }
+        public String stringValue() {
+            return stringValueOf(tag, classRef, descRef);
+        }
+        static
+        String stringValueOf(byte tag, ClassEntry classRef, DescriptorEntry descRef) {
+            assert(isMemberTag(tag));
+            String pfx;
+            switch (tag) {
+            case Constants.CONSTANT_Fieldref:            pfx = "Field:";   break;
+            case Constants.CONSTANT_Methodref:           pfx = "Method:";  break;
+            case Constants.CONSTANT_InterfaceMethodref:  pfx = "IMethod:"; break;
+            default:                           pfx = tag+"???";  break;
+            }
+            return pfx+qualifiedStringValue(classRef, descRef);
+        }
+
+        public boolean isMethod() {
+            return descRef.isMethod();
+        }
+    }
+
+    public static
+    class SignatureEntry extends Entry {
+        final Utf8Entry    formRef;
+        final ClassEntry[] classRefs;
+        String             value;
+        Utf8Entry          asUtf8Entry;
+        public Entry getRef(int i) {
+            if (i == 0)  return formRef;
+            return i-1 < classRefs.length ? classRefs[i-1] : null;
+        }
+        SignatureEntry(String value) {
+            super(Constants.CONSTANT_Signature);
+            value = value.intern();  // always do this
+            this.value = value;
+            String[] parts = structureSignature(value);
+            formRef = getUtf8Entry(parts[0]);
+            classRefs = new ClassEntry[parts.length-1];
+            for (int i = 1; i < parts.length; i++) {
+                classRefs[i - 1] = getClassEntry(parts[i]);
+            }
+            hashCode();  // force computation of valueHash
+        }
+        protected int computeValueHash() {
+            stringValue();  // force computation of value
+            return value.hashCode() + tag;
+        }
+
+        public Utf8Entry asUtf8Entry() {
+            if (asUtf8Entry == null) {
+                asUtf8Entry = getUtf8Entry(stringValue());
+            }
+            return asUtf8Entry;
+        }
+
+        public boolean equals(Object o) {
+            return (o != null && o.getClass() == SignatureEntry.class &&
+                    ((SignatureEntry)o).value.equals(value));
+        }
+        public int compareTo(Object o) {
+            int x = superCompareTo(o);
+            if (x == 0) {
+                SignatureEntry that = (SignatureEntry)o;
+                x = compareSignatures(this.value, that.value);
+            }
+            return x;
+        }
+        public String stringValue() {
+            if (value == null) {
+                value = stringValueOf(formRef, classRefs);
+            }
+            return value;
+        }
+        static
+        String stringValueOf(Utf8Entry formRef, ClassEntry[] classRefs) {
+            String[] parts = new String[1+classRefs.length];
+            parts[0] = formRef.stringValue();
+            for (int i = 1; i < parts.length; i++) {
+                parts[i] = classRefs[i - 1].stringValue();
+            }
+            return flattenSignature(parts).intern();
+        }
+
+        public int computeSize(boolean countDoublesTwice) {
+            String form = formRef.stringValue();
+            int min = 0;
+            int max = 1;
+            if (isMethod()) {
+                min = 1;
+                max = form.indexOf(')');
+            }
+            int size = 0;
+            for (int i = min; i < max; i++) {
+                switch (form.charAt(i)) {
+                    case 'D':
+                    case 'J':
+                        if (countDoublesTwice) {
+                            size++;
+                        }
+                        break;
+                    case '[':
+                        // Skip rest of array info.
+                        while (form.charAt(i) == '[') {
+                            ++i;
+                        }
+                        break;
+                    case ';':
+                        continue;
+                    default:
+                        assert (0 <= Constants.JAVA_SIGNATURE_CHARS.indexOf(form.charAt(i)));
+                        break;
+                }
+                size++;
+            }
+            return size;
+        }
+        public boolean isMethod() {
+            return formRef.stringValue().charAt(0) == '(';
+        }
+        public byte getLiteralTag() {
+            switch (formRef.stringValue().charAt(0)) {
+            case 'I': return Constants.CONSTANT_Integer;
+            case 'J': return Constants.CONSTANT_Long;
+            case 'F': return Constants.CONSTANT_Float;
+            case 'D': return Constants.CONSTANT_Double;
+            case 'B': case 'S': case 'C': case 'Z':
+                return Constants.CONSTANT_Integer;
+            case 'L':
+                /*
+                switch (classRefs[0].stringValue()) {
+                case "java/lang/String":
+                    return CONSTANT_String;
+                case "java/lang/invoke/MethodHandle":
+                    return CONSTANT_MethodHandle;
+                case "java/lang/invoke/MethodType":
+                    return CONSTANT_MethodType;
+                default:  // java/lang/Object, etc.
+                    return CONSTANT_LoadableValue;
+                }
+                */
+                return Constants.CONSTANT_String;  // JDK 7 ConstantValue limited to String
+            }
+            assert(false);
+            return Constants.CONSTANT_None;
+        }
+        public String prettyString() {
+            String s;
+            if (isMethod()) {
+                s = formRef.stringValue();
+                s = s.substring(0, 1+s.indexOf(')'));
+            } else {
+                s = "/" + formRef.stringValue();
+            }
+            int i;
+            while ((i = s.indexOf(';')) >= 0) {
+                s = s.substring(0, i) + s.substring(i + 1);
+            }
+            return s;
+        }
+    }
+
+    static int compareSignatures(String s1, String s2) {
+        return compareSignatures(s1, s2, null, null);
+    }
+    static int compareSignatures(String s1, String s2, String[] p1, String[] p2) {
+        final int S1_COMES_FIRST = -1;
+        final int S2_COMES_FIRST = +1;
+        char c1 = s1.charAt(0);
+        char c2 = s2.charAt(0);
+        // fields before methods (because there are fewer of them)
+        if (c1 != '(' && c2 == '(')  return S1_COMES_FIRST;
+        if (c2 != '(' && c1 == '(')  return S2_COMES_FIRST;
+        if (p1 == null)  p1 = structureSignature(s1);
+        if (p2 == null)  p2 = structureSignature(s2);
+        /*
+         // non-classes before classes (because there are fewer of them)
+         if (p1.length == 1 && p2.length > 1)  return S1_COMES_FIRST;
+         if (p2.length == 1 && p1.length > 1)  return S2_COMES_FIRST;
+         // all else being equal, use the same comparison as for Utf8 strings
+         return s1.compareTo(s2);
+         */
+        if (p1.length != p2.length)  return p1.length - p2.length;
+        int length = p1.length;
+        for (int i = length; --i >= 0; ) {
+            int res = p1[i].compareTo(p2[i]);
+            if (res != 0)  return res;
+        }
+        assert(s1.equals(s2));
+        return 0;
+    }
+
+    static int countClassParts(Utf8Entry formRef) {
+        int num = 0;
+        String s = formRef.stringValue();
+        for (int i = 0; i < s.length(); i++) {
+            if (s.charAt(i) == 'L')  ++num;
+        }
+        return num;
+    }
+
+    static String flattenSignature(String[] parts) {
+        String form = parts[0];
+        if (parts.length == 1)  return form;
+        int len = form.length();
+        for (int i = 1; i < parts.length; i++) {
+            len += parts[i].length();
+        }
+        char[] sig = new char[len];
+        int j = 0;
+        int k = 1;
+        for (int i = 0; i < form.length(); i++) {
+            char ch = form.charAt(i);
+            sig[j++] = ch;
+            if (ch == 'L') {
+                String cls = parts[k++];
+                cls.getChars(0, cls.length(), sig, j);
+                j += cls.length();
+                //sig[j++] = ';';
+            }
+        }
+        assert(j == len);
+        assert(k == parts.length);
+        return new String(sig);
+    }
+
+    private static int skipTo(char semi, String sig, int i) {
+        i = sig.indexOf(semi, i);
+        return (i >= 0) ? i : sig.length();
+    }
+
+    static String[] structureSignature(String sig) {
+        int firstl = sig.indexOf('L');
+        if (firstl < 0) {
+            String[] parts = { sig };
+            return parts;
+        }
+        // Segment the string like sig.split("L\\([^;<]*\\)").
+        // N.B.: Previous version of this code did a more complex match,
+        // to next ch < ' ' or ch in [';'..'@'].  The only important
+        // characters are ';' and '<', since they are part of the
+        // signature syntax.
+        // Examples:
+        //   "(Ljava/lang/Object;IJLLoo;)V" => {"(L;IJL;)V", "java/lang/Object", "Loo"}
+        //   "Ljava/util/List;" => {"L;", "java/util/List", "java/lang/String"}
+        char[] form = null;
+        String[] parts = null;
+        for (int pass = 0; pass <= 1; pass++) {
+            // pass 0 is a sizing pass, pass 1 packs the arrays
+            int formPtr = 0;
+            int partPtr = 1;
+            int nextsemi = 0, nextangl = 0;  // next ';' or '<', or zero, or sigLen
+            int lastj = 0;
+            for (int i = firstl + 1, j; i > 0; i = sig.indexOf('L', j) + 1) {
+                // sig[i-1] is 'L', while sig[j] will be the first ';' or '<' after it
+                // each part is in sig[i .. j-1]
+                if (nextsemi < i)  nextsemi = skipTo(';', sig, i);
+                if (nextangl < i)  nextangl = skipTo('<', sig, i);
+                j = (nextsemi < nextangl ? nextsemi : nextangl);
+                if (pass != 0) {
+                    sig.getChars(lastj, i, form, formPtr);
+                    parts[partPtr] = sig.substring(i, j);
+                }
+                formPtr += (i - lastj);
+                partPtr += 1;
+                lastj = j;
+            }
+            if (pass != 0) {
+                sig.getChars(lastj, sig.length(), form, formPtr);
+                break;
+            }
+            formPtr += (sig.length() - lastj);
+            form = new char[formPtr];
+            parts = new String[partPtr];
+        }
+        parts[0] = new String(form);
+        //assert(flattenSignature(parts).equals(sig));
+        return parts;
+    }
+
+    /** @since 1.7, JSR 292 */
+    public static
+    class MethodHandleEntry extends Entry {
+        final int refKind;
+        final MemberEntry memRef;
+        public Entry getRef(int i) { return i == 0 ? memRef : null; }
+
+        protected int computeValueHash() {
+            int hc2 = refKind;
+            return (memRef.hashCode() + (hc2 << 8)) ^ hc2;
+        }
+
+        MethodHandleEntry(byte refKind, MemberEntry memRef) {
+            super(Constants.CONSTANT_MethodHandle);
+            assert(isRefKind(refKind));
+            this.refKind = refKind;
+            this.memRef  = memRef;
+            hashCode();  // force computation of valueHash
+        }
+        public boolean equals(Object o) {
+            if (o == null || o.getClass() != MethodHandleEntry.class) {
+                return false;
+            }
+            MethodHandleEntry that = (MethodHandleEntry)o;
+            return this.refKind == that.refKind
+                && this.memRef.eq(that.memRef);
+        }
+        public int compareTo(Object o) {
+            int x = superCompareTo(o);
+            if (x == 0) {
+                MethodHandleEntry that = (MethodHandleEntry)o;
+                if (Utils.SORT_HANDLES_KIND_MAJOR)
+                    // Primary key could be refKind.
+                    x = this.refKind - that.refKind;
+                // Primary key is memRef, which is transmitted as UDELTA5.
+                if (x == 0)
+                    x = this.memRef.compareTo(that.memRef);
+                if (x == 0)
+                    x = this.refKind - that.refKind;
+            }
+            return x;
+        }
+        public static String stringValueOf(int refKind, MemberEntry memRef) {
+            return refKindName(refKind)+":"+memRef.stringValue();
+        }
+        public String stringValue() {
+            return stringValueOf(refKind, memRef);
+        }
+    }
+
+    /** @since 1.7, JSR 292 */
+    public static
+    class MethodTypeEntry extends Entry {
+        final SignatureEntry typeRef;
+        public Entry getRef(int i) { return i == 0 ? typeRef : null; }
+
+        protected int computeValueHash() {
+            return typeRef.hashCode() + tag;
+        }
+
+        MethodTypeEntry(SignatureEntry typeRef) {
+            super(Constants.CONSTANT_MethodType);
+            this.typeRef  = typeRef;
+            hashCode();  // force computation of valueHash
+        }
+        public boolean equals(Object o) {
+            if (o == null || o.getClass() != MethodTypeEntry.class) {
+                return false;
+            }
+            MethodTypeEntry that = (MethodTypeEntry)o;
+            return this.typeRef.eq(that.typeRef);
+        }
+        public int compareTo(Object o) {
+            int x = superCompareTo(o);
+            if (x == 0) {
+                MethodTypeEntry that = (MethodTypeEntry)o;
+                x = this.typeRef.compareTo(that.typeRef);
+            }
+            return x;
+        }
+        public String stringValue() {
+            return typeRef.stringValue();
+        }
+    }
+
+    /** @since 1.7, JSR 292 */
+    public static
+    class InvokeDynamicEntry extends Entry {
+        final BootstrapMethodEntry bssRef;
+        final DescriptorEntry descRef;
+        public Entry getRef(int i) {
+            if (i == 0)  return bssRef;
+            if (i == 1)  return descRef;
+            return null;
+        }
+        protected int computeValueHash() {
+            int hc2 = descRef.hashCode();
+            return (bssRef.hashCode() + (hc2 << 8)) ^ hc2;
+        }
+
+        InvokeDynamicEntry(BootstrapMethodEntry bssRef, DescriptorEntry descRef) {
+            super(Constants.CONSTANT_InvokeDynamic);
+            this.bssRef  = bssRef;
+            this.descRef = descRef;
+            hashCode();  // force computation of valueHash
+        }
+        public boolean equals(Object o) {
+            if (o == null || o.getClass() != InvokeDynamicEntry.class) {
+                return false;
+            }
+            InvokeDynamicEntry that = (InvokeDynamicEntry)o;
+            return this.bssRef.eq(that.bssRef)
+                && this.descRef.eq(that.descRef);
+        }
+        public int compareTo(Object o) {
+            int x = superCompareTo(o);
+            if (x == 0) {
+                InvokeDynamicEntry that = (InvokeDynamicEntry)o;
+                if (Utils.SORT_INDY_BSS_MAJOR)
+                    // Primary key could be bsmRef.
+                    x = this.bssRef.compareTo(that.bssRef);
+                // Primary key is descriptor, which is transmitted as UDELTA5.
+                if (x == 0)
+                    x = this.descRef.compareTo(that.descRef);
+                if (x == 0)
+                    x = this.bssRef.compareTo(that.bssRef);
+            }
+            return x;
+        }
+        public String stringValue() {
+            return stringValueOf(bssRef, descRef);
+        }
+        static
+        String stringValueOf(BootstrapMethodEntry bssRef, DescriptorEntry descRef) {
+            return "Indy:"+bssRef.stringValue()+"."+descRef.stringValue();
+        }
+    }
+
+    /** @since 1.7, JSR 292 */
+    public static
+    class BootstrapMethodEntry extends Entry {
+        final MethodHandleEntry bsmRef;
+        final Entry[] argRefs;
+        public Entry getRef(int i) {
+            if (i == 0)  return bsmRef;
+            if (i-1 < argRefs.length)  return argRefs[i-1];
+            return null;
+        }
+        protected int computeValueHash() {
+            int hc2 = bsmRef.hashCode();
+            return (Arrays.hashCode(argRefs) + (hc2 << 8)) ^ hc2;
+        }
+
+        BootstrapMethodEntry(MethodHandleEntry bsmRef, Entry[] argRefs) {
+            super(Constants.CONSTANT_BootstrapMethod);
+            this.bsmRef  = bsmRef;
+            this.argRefs = argRefs.clone();
+            hashCode();  // force computation of valueHash
+        }
+        public boolean equals(Object o) {
+            if (o == null || o.getClass() != BootstrapMethodEntry.class) {
+                return false;
+            }
+            BootstrapMethodEntry that = (BootstrapMethodEntry)o;
+            return this.bsmRef.eq(that.bsmRef)
+                && Arrays.equals(this.argRefs, that.argRefs);
+        }
+        public int compareTo(Object o) {
+            int x = superCompareTo(o);
+            if (x == 0) {
+                BootstrapMethodEntry that = (BootstrapMethodEntry)o;
+                if (Utils.SORT_BSS_BSM_MAJOR)
+                    // Primary key is bsmRef.
+                    x = this.bsmRef.compareTo(that.bsmRef);
+                // Primary key is args array length, which is transmitted as UDELTA5.
+                if (x == 0)
+                    x = compareArgArrays(this.argRefs, that.argRefs);
+                if (x == 0)
+                    x = this.bsmRef.compareTo(that.bsmRef);
+            }
+            return x;
+        }
+        public String stringValue() {
+            return stringValueOf(bsmRef, argRefs);
+        }
+        static
+        String stringValueOf(MethodHandleEntry bsmRef, Entry[] argRefs) {
+            StringBuilder sb = new StringBuilder(bsmRef.stringValue());
+            // Arguments are formatted as "" instead of "[foo,bar,baz]".
+            // This ensures there will be no confusion if "[,]" appear inside of names.
+            char nextSep = '<';
+            boolean didOne = false;
+            for (Entry argRef : argRefs) {
+                sb.append(nextSep).append(argRef.stringValue());
+                nextSep = ';';
+            }
+            if (nextSep == '<')  sb.append(nextSep);
+            sb.append('>');
+            return sb.toString();
+        }
+        static
+        int compareArgArrays(Entry[] a1, Entry[] a2) {
+            int x = a1.length - a2.length;
+            if (x != 0)  return x;
+            for (int i = 0; i < a1.length; i++) {
+                x = a1[i].compareTo(a2[i]);
+                if (x != 0)  break;
+            }
+            return x;
+        }
+    }
+
+    // Handy constants:
+    protected static final Entry[] noRefs = {};
+    protected static final ClassEntry[] noClassRefs = {};
+
+    /** An Index is a mapping between CP entries and small integers. */
+    public static final
+    class Index extends AbstractList {
+        protected String debugName;
+        protected Entry[] cpMap;
+        protected boolean flattenSigs;
+        protected Entry[] getMap() {
+            return cpMap;
+        }
+        protected Index(String debugName) {
+            this.debugName = debugName;
+        }
+        protected Index(String debugName, Entry[] cpMap) {
+            this(debugName);
+            setMap(cpMap);
+        }
+        protected void setMap(Entry[] cpMap) {
+            clearIndex();
+            this.cpMap = cpMap;
+        }
+        protected Index(String debugName, Collection cpMapList) {
+            this(debugName);
+            setMap(cpMapList);
+        }
+        protected void setMap(Collection cpMapList) {
+            cpMap = new Entry[cpMapList.size()];
+            cpMapList.toArray(cpMap);
+            setMap(cpMap);
+        }
+        public int size() {
+            return cpMap.length;
+        }
+        public Entry get(int i) {
+            return cpMap[i];
+        }
+        public Entry getEntry(int i) {
+            // same as get(), with covariant return type
+            return cpMap[i];
+        }
+
+        // Find index of e in cpMap, or return -1 if none.
+        //
+        // As a special hack, if flattenSigs, signatures are
+        // treated as equivalent entries of cpMap.  This is wrong
+        // from a Collection point of view, because contains()
+        // reports true for signatures, but the iterator()
+        // never produces them!
+        private int findIndexOf(Entry e) {
+            if (indexKey == null) {
+                initializeIndex();
+            }
+            int probe = findIndexLocation(e);
+            if (indexKey[probe] != e) {
+                if (flattenSigs && e.tag == Constants.CONSTANT_Signature) {
+                    SignatureEntry se = (SignatureEntry) e;
+                    return findIndexOf(se.asUtf8Entry());
+                }
+                return -1;
+            }
+            int index = indexValue[probe];
+            assert(e.equals(cpMap[index]));
+            return index;
+        }
+        public boolean contains(Entry e) {
+            return findIndexOf(e) >= 0;
+        }
+        // Find index of e in cpMap.  Should not return -1.
+        public int indexOf(Entry e) {
+            int index = findIndexOf(e);
+            if (index < 0 && verbose() > 0) {
+                System.out.println("not found: "+e);
+                System.out.println("       in: "+this.dumpString());
+                Thread.dumpStack();
+            }
+            assert(index >= 0);
+            return index;
+        }
+        public int lastIndexOf(Entry e) {
+            return indexOf(e);
+        }
+
+        public boolean assertIsSorted() {
+            for (int i = 1; i < cpMap.length; i++) {
+                if (cpMap[i-1].compareTo(cpMap[i]) > 0) {
+                    System.out.println("Not sorted at "+(i-1)+"/"+i+": "+this.dumpString());
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        // internal hash table
+        protected Entry[] indexKey;
+        protected int[]   indexValue;
+        protected void clearIndex() {
+            indexKey   = null;
+            indexValue = null;
+        }
+        private int findIndexLocation(Entry e) {
+            int size   = indexKey.length;
+            int hash   = e.hashCode();
+            int probe  = hash & (size - 1);
+            int stride = ((hash >>> 8) | 1) & (size - 1);
+            for (;;) {
+                Entry e1 = indexKey[probe];
+                if (e1 == e || e1 == null)
+                    return probe;
+                probe += stride;
+                if (probe >= size)  probe -= size;
+            }
+        }
+        private void initializeIndex() {
+            if (verbose() > 2)
+                System.out.println("initialize Index "+debugName+" ["+size()+"]");
+            int hsize0 = (int)((cpMap.length + 10) * 1.5);
+            int hsize = 1;
+            while (hsize < hsize0) {
+                hsize <<= 1;
+            }
+            indexKey   = new Entry[hsize];
+            indexValue = new int[hsize];
+            for (int i = 0; i < cpMap.length; i++) {
+                Entry e = cpMap[i];
+                if (e == null)  continue;
+                int probe = findIndexLocation(e);
+                assert(indexKey[probe] == null);  // e has unique index
+                indexKey[probe] = e;
+                indexValue[probe] = i;
+            }
+        }
+        public Entry[] toArray(Entry[] a) {
+            int sz = size();
+            if (a.length < sz)  return super.toArray(a);
+            System.arraycopy(cpMap, 0, a, 0, sz);
+            if (a.length > sz)  a[sz] = null;
+            return a;
+        }
+        public Entry[] toArray() {
+            return toArray(new Entry[size()]);
+        }
+        public Object clone() {
+            return new Index(debugName, cpMap.clone());
+        }
+        public String toString() {
+            return "Index "+debugName+" ["+size()+"]";
+        }
+        public String dumpString() {
+            String s = toString();
+            s += " {\n";
+            for (int i = 0; i < cpMap.length; i++) {
+                s += "    "+i+": "+cpMap[i]+"\n";
+            }
+            s += "}";
+            return s;
+        }
+    }
+
+    // Index methods.
+
+    public static
+    Index makeIndex(String debugName, Entry[] cpMap) {
+        return new Index(debugName, cpMap);
+    }
+
+    public static
+    Index makeIndex(String debugName, Collection cpMapList) {
+        return new Index(debugName, cpMapList);
+    }
+
+    /** Sort this index (destructively) into canonical order. */
+    public static
+    void sort(Index ix) {
+        // %%% Should move this into class Index.
+        ix.clearIndex();
+        Arrays.sort(ix.cpMap);
+        if (verbose() > 2)
+            System.out.println("sorted "+ix.dumpString());
+    }
+
+    /** Return a set of indexes partitioning these entries.
+     *  The keys array must of length this.size(), and marks entries.
+     *  The result array is as long as one plus the largest key value.
+     *  Entries with a negative key are dropped from the partition.
+     */
+    public static
+    Index[] partition(Index ix, int[] keys) {
+        // %%% Should move this into class Index.
+        List> parts = new ArrayList<>();
+        Entry[] cpMap = ix.cpMap;
+        assert(keys.length == cpMap.length);
+        for (int i = 0; i < keys.length; i++) {
+            int key = keys[i];
+            if (key < 0)  continue;
+            while (key >= parts.size()) {
+                parts.add(null);
+            }
+            List part = parts.get(key);
+            if (part == null) {
+                parts.set(key, part = new ArrayList<>());
+            }
+            part.add(cpMap[i]);
+        }
+        Index[] indexes = new Index[parts.size()];
+        for (int key = 0; key < indexes.length; key++) {
+            List part = parts.get(key);
+            if (part == null)  continue;
+            indexes[key] = new Index(ix.debugName+"/part#"+key, part);
+            assert(indexes[key].indexOf(part.get(0)) == 0);
+        }
+        return indexes;
+    }
+    public static
+    Index[] partitionByTag(Index ix) {
+        // Partition by tag.
+        Entry[] cpMap = ix.cpMap;
+        int[] keys = new int[cpMap.length];
+        for (int i = 0; i < keys.length; i++) {
+            Entry e = cpMap[i];
+            keys[i] = (e == null)? -1: e.tag;
+        }
+        Index[] byTag = partition(ix, keys);
+        for (int tag = 0; tag < byTag.length; tag++) {
+            if (byTag[tag] == null)  continue;
+            byTag[tag].debugName = tagName(tag);
+        }
+        if (byTag.length < Constants.CONSTANT_Limit) {
+            Index[] longer = new Index[Constants.CONSTANT_Limit];
+            System.arraycopy(byTag, 0, longer, 0, byTag.length);
+            byTag = longer;
+        }
+        return byTag;
+    }
+
+    /** Coherent group of constant pool indexes. */
+    public static
+    class IndexGroup {
+        private Index[] indexByTag = new Index[Constants.CONSTANT_Limit];
+        private Index[] indexByTagGroup;
+        private int[]   untypedFirstIndexByTag;
+        private int     totalSizeQQ;
+        private Index[][] indexByTagAndClass;
+
+        /** Index of all CP entries of all types, in definition order. */
+        private Index makeTagGroupIndex(byte tagGroupTag, byte[] tagsInGroup) {
+            if (indexByTagGroup == null)
+                indexByTagGroup = new Index[Constants.CONSTANT_GroupLimit - Constants.CONSTANT_GroupFirst];
+            int which = tagGroupTag - Constants.CONSTANT_GroupFirst;
+            assert(indexByTagGroup[which] == null);
+            int fillp = 0;
+            Entry[] cpMap = null;
+            for (int pass = 1; pass <= 2; pass++) {
+                untypedIndexOf(null);  // warm up untypedFirstIndexByTag
+                for (byte tag : tagsInGroup) {
+                    Index ix = indexByTag[tag];
+                    if (ix == null)  continue;
+                    int ixLen = ix.cpMap.length;
+                    if (ixLen == 0)  continue;
+                    assert(tagGroupTag == Constants.CONSTANT_All
+                            ? fillp == untypedFirstIndexByTag[tag]
+                            : fillp  < untypedFirstIndexByTag[tag]);
+                    if (cpMap != null) {
+                        assert(cpMap[fillp] == null);
+                        assert(cpMap[fillp+ixLen-1] == null);
+                        System.arraycopy(ix.cpMap, 0, cpMap, fillp, ixLen);
+                    }
+                    fillp += ixLen;
+                }
+                if (cpMap == null) {
+                    assert(pass == 1);
+                    // get ready for pass 2
+                    cpMap = new Entry[fillp];
+                    fillp = 0;
+                }
+            }
+            indexByTagGroup[which] = new Index(tagName(tagGroupTag), cpMap);
+            return indexByTagGroup[which];
+        }
+
+        public int untypedIndexOf(Entry e) {
+            if (untypedFirstIndexByTag == null) {
+                untypedFirstIndexByTag = new int[Constants.CONSTANT_Limit+1];
+                int fillp = 0;
+                for (int i = 0; i < TAGS_IN_ORDER.length; i++) {
+                    byte tag = TAGS_IN_ORDER[i];
+                    Index ix = indexByTag[tag];
+                    if (ix == null)  continue;
+                    int ixLen = ix.cpMap.length;
+                    untypedFirstIndexByTag[tag] = fillp;
+                    fillp += ixLen;
+                }
+                untypedFirstIndexByTag[Constants.CONSTANT_Limit] = fillp;
+            }
+            if (e == null)  return -1;
+            int tag = e.tag;
+            Index ix = indexByTag[tag];
+            if (ix == null)  return -1;
+            int idx = ix.findIndexOf(e);
+            if (idx >= 0)
+                idx += untypedFirstIndexByTag[tag];
+            return idx;
+        }
+
+        public void initIndexByTag(byte tag, Index ix) {
+            assert(indexByTag[tag] == null);  // do not init twice
+            Entry[] cpMap = ix.cpMap;
+            for (int i = 0; i < cpMap.length; i++) {
+                // It must be a homogeneous Entry set.
+                assert(cpMap[i].tag == tag);
+            }
+            if (tag == Constants.CONSTANT_Utf8) {
+                // Special case:  First Utf8 must always be empty string.
+                assert(cpMap.length == 0 || cpMap[0].stringValue().equals(""));
+            }
+            indexByTag[tag] = ix;
+            // decache indexes derived from this one:
+            untypedFirstIndexByTag = null;
+            indexByTagGroup = null;
+            if (indexByTagAndClass != null)
+                indexByTagAndClass[tag] = null;
+        }
+
+        /** Index of all CP entries of a given tag. */
+        public Index getIndexByTag(byte tag) {
+            if (tag >= Constants.CONSTANT_GroupFirst)
+                return getIndexByTagGroup(tag);
+            Index ix = indexByTag[tag];
+            if (ix == null) {
+                // Make an empty one by default.
+                ix = new Index(tagName(tag), new Entry[0]);
+                indexByTag[tag] = ix;
+            }
+            return ix;
+        }
+
+        private Index getIndexByTagGroup(byte tag) {
+            // pool groups:
+            if (indexByTagGroup != null) {
+                Index ix = indexByTagGroup[tag - Constants.CONSTANT_GroupFirst];
+                if (ix != null)  return ix;
+            }
+            switch (tag) {
+            case Constants.CONSTANT_All:
+                return makeTagGroupIndex(Constants.CONSTANT_All, TAGS_IN_ORDER);
+            case Constants.CONSTANT_LoadableValue:
+                    return makeTagGroupIndex(Constants.CONSTANT_LoadableValue, LOADABLE_VALUE_TAGS);
+            case Constants.CONSTANT_AnyMember:
+                return makeTagGroupIndex(Constants.CONSTANT_AnyMember, ANY_MEMBER_TAGS);
+            case Constants.CONSTANT_FieldSpecific:
+                // This one does not have any fixed index, since it is context-specific.
+                return null;
+            }
+            throw new AssertionError("bad tag group "+tag);
+        }
+
+        /** Index of all CP entries of a given tag and class. */
+        public Index getMemberIndex(byte tag, ClassEntry classRef) {
+            if (classRef == null)
+                throw new RuntimeException("missing class reference for " + tagName(tag));
+            if (indexByTagAndClass == null)
+                indexByTagAndClass = new Index[Constants.CONSTANT_Limit][];
+            Index allClasses =  getIndexByTag(Constants.CONSTANT_Class);
+            Index[] perClassIndexes = indexByTagAndClass[tag];
+            if (perClassIndexes == null) {
+                // Create the partition now.
+                // Divide up all entries of the given tag according to their class.
+                Index allMembers = getIndexByTag(tag);
+                int[] whichClasses = new int[allMembers.size()];
+                for (int i = 0; i < whichClasses.length; i++) {
+                    MemberEntry e = (MemberEntry) allMembers.get(i);
+                    int whichClass = allClasses.indexOf(e.classRef);
+                    whichClasses[i] = whichClass;
+                }
+                perClassIndexes = partition(allMembers, whichClasses);
+                for (int i = 0; i < perClassIndexes.length; i++) {
+                    assert (perClassIndexes[i] == null ||
+                            perClassIndexes[i].assertIsSorted());
+                }
+                indexByTagAndClass[tag] = perClassIndexes;
+            }
+            int whichClass = allClasses.indexOf(classRef);
+            return perClassIndexes[whichClass];
+        }
+
+        // Given the sequence of all methods of the given name and class,
+        // produce the ordinal of this particular given overloading.
+        public int getOverloadingIndex(MemberEntry methodRef) {
+            Index ix = getMemberIndex(methodRef.tag, methodRef.classRef);
+            Utf8Entry nameRef = methodRef.descRef.nameRef;
+            int ord = 0;
+            for (int i = 0; i < ix.cpMap.length; i++) {
+                MemberEntry e = (MemberEntry) ix.cpMap[i];
+                if (e.equals(methodRef))
+                    return ord;
+                if (e.descRef.nameRef.equals(nameRef))
+                    // Found a different overloading.  Increment the ordinal.
+                    ord++;
+            }
+            throw new RuntimeException("should not reach here");
+        }
+
+        // Inverse of getOverloadingIndex
+        public MemberEntry getOverloadingForIndex(byte tag, ClassEntry classRef, String name, int which) {
+            assert(name.equals(name.intern()));
+            Index ix = getMemberIndex(tag, classRef);
+            int ord = 0;
+            for (int i = 0; i < ix.cpMap.length; i++) {
+                MemberEntry e = (MemberEntry) ix.cpMap[i];
+                if (e.descRef.nameRef.stringValue().equals(name)) {
+                    if (ord == which)  return e;
+                    ord++;
+                }
+            }
+            throw new RuntimeException("should not reach here");
+        }
+
+        public boolean haveNumbers() {
+            for (byte tag : NUMBER_TAGS) {
+                if (getIndexByTag(tag).size() > 0)  return true;
+            }
+            return false;
+        }
+
+        public boolean haveExtraTags() {
+            for (byte tag : EXTRA_TAGS) {
+                if (getIndexByTag(tag).size() > 0)  return true;
+            }
+            return false;
+        }
+
+    }
+
+    /** Close the set cpRefs under the getRef(*) relation.
+     *  Also, if flattenSigs, replace all signatures in cpRefs
+     *  by their equivalent Utf8s.
+     *  Also, discard null from cpRefs.
+     */
+    public static void completeReferencesIn(Set cpRefs, boolean flattenSigs) {
+         completeReferencesIn(cpRefs, flattenSigs, null);
+    }
+
+    public static
+    void completeReferencesIn(Set cpRefs, boolean flattenSigs,
+                              Listbsms) {
+        cpRefs.remove(null);
+        for (ListIterator work =
+                 new ArrayList<>(cpRefs).listIterator(cpRefs.size());
+             work.hasPrevious(); ) {
+            Entry e = work.previous();
+            work.remove();          // pop stack
+            assert(e != null);
+            if (flattenSigs && e.tag == Constants.CONSTANT_Signature) {
+                SignatureEntry se = (SignatureEntry) e;
+                Utf8Entry      ue = se.asUtf8Entry();
+                // Totally replace e by se.
+                cpRefs.remove(se);
+                cpRefs.add(ue);
+                e = ue;   // do not descend into the sig
+            }
+            if (bsms != null && e.tag == Constants.CONSTANT_BootstrapMethod) {
+                BootstrapMethodEntry bsm = (BootstrapMethodEntry)e;
+                cpRefs.remove(bsm);
+                // move it away to the side table where it belongs
+                if (!bsms.contains(bsm))
+                    bsms.add(bsm);
+                // fall through to recursively add refs for this entry
+            }
+            // Recursively add the refs of e to cpRefs:
+            for (int i = 0; ; i++) {
+                Entry re = e.getRef(i);
+                if (re == null)
+                    break;          // no more refs in e
+                if (cpRefs.add(re)) // output the ref
+                    work.add(re);   // push stack, if a new ref
+            }
+        }
+    }
+
+    static double percent(int num, int den) {
+        return (int)((10000.0*num)/den + 0.5) / 100.0;
+    }
+
+    public static String tagName(int tag) {
+        switch (tag) {
+            case Constants.CONSTANT_Utf8:                 return "Utf8";
+            case Constants.CONSTANT_Integer:              return "Integer";
+            case Constants.CONSTANT_Float:                return "Float";
+            case Constants.CONSTANT_Long:                 return "Long";
+            case Constants.CONSTANT_Double:               return "Double";
+            case Constants.CONSTANT_Class:                return "Class";
+            case Constants.CONSTANT_String:               return "String";
+            case Constants.CONSTANT_Fieldref:             return "Fieldref";
+            case Constants.CONSTANT_Methodref:            return "Methodref";
+            case Constants.CONSTANT_InterfaceMethodref:   return "InterfaceMethodref";
+            case Constants.CONSTANT_NameandType:          return "NameandType";
+            case Constants.CONSTANT_MethodHandle:         return "MethodHandle";
+            case Constants.CONSTANT_MethodType:           return "MethodType";
+            case Constants.CONSTANT_InvokeDynamic:        return "InvokeDynamic";
+
+                // pseudo-tags:
+            case Constants.CONSTANT_All:                  return "**All";
+            case Constants.CONSTANT_None:                 return "**None";
+            case Constants.CONSTANT_LoadableValue:        return "**LoadableValue";
+            case Constants.CONSTANT_AnyMember:            return "**AnyMember";
+            case Constants.CONSTANT_FieldSpecific:        return "*FieldSpecific";
+            case Constants.CONSTANT_Signature:            return "*Signature";
+            case Constants.CONSTANT_BootstrapMethod:      return "*BootstrapMethod";
+        }
+        return "tag#"+tag;
+    }
+
+    public static String refKindName(int refKind) {
+        switch (refKind) {
+            case Constants.REF_getField:                  return "getField";
+            case Constants.REF_getStatic:                 return "getStatic";
+            case Constants.REF_putField:                  return "putField";
+            case Constants.REF_putStatic:                 return "putStatic";
+            case Constants.REF_invokeVirtual:             return "invokeVirtual";
+            case Constants.REF_invokeStatic:              return "invokeStatic";
+            case Constants.REF_invokeSpecial:             return "invokeSpecial";
+            case Constants.REF_newInvokeSpecial:          return "newInvokeSpecial";
+            case Constants.REF_invokeInterface:           return "invokeInterface";
+        }
+        return "refKind#"+refKind;
+    }
+
+    // archive constant pool definition order
+    static final byte TAGS_IN_ORDER[] = {
+        Constants.CONSTANT_Utf8,
+        Constants.CONSTANT_Integer,           // cp_Int
+        Constants.CONSTANT_Float,
+        Constants.CONSTANT_Long,
+        Constants.CONSTANT_Double,
+        Constants.CONSTANT_String,            // note that String=8 precedes Class=7
+        Constants.CONSTANT_Class,
+        Constants.CONSTANT_Signature,
+        Constants.CONSTANT_NameandType,       // cp_Descr
+        Constants.CONSTANT_Fieldref,          // cp_Field
+        Constants.CONSTANT_Methodref,         // cp_Method
+        Constants.CONSTANT_InterfaceMethodref, // cp_Imethod
+
+        // Constants defined in JDK 7 and later:
+        Constants.CONSTANT_MethodHandle,
+        Constants.CONSTANT_MethodType,
+        Constants.CONSTANT_BootstrapMethod,  // pseudo-tag, really stored in a class attribute
+        Constants.CONSTANT_InvokeDynamic
+    };
+    static final byte TAG_ORDER[];
+    static {
+        TAG_ORDER = new byte[Constants.CONSTANT_Limit];
+        for (int i = 0; i < TAGS_IN_ORDER.length; i++) {
+            TAG_ORDER[TAGS_IN_ORDER[i]] = (byte)(i+1);
+        }
+        /*
+        System.out.println("TAG_ORDER[] = {");
+        for (int i = 0; i < TAG_ORDER.length; i++)
+            System.out.println("  "+TAG_ORDER[i]+",");
+        System.out.println("};");
+        */
+    }
+    static final byte[] NUMBER_TAGS = {
+        Constants.CONSTANT_Integer, Constants.CONSTANT_Float, Constants.CONSTANT_Long, Constants.CONSTANT_Double
+    };
+    static final byte[] EXTRA_TAGS = {
+        Constants.CONSTANT_MethodHandle, Constants.CONSTANT_MethodType,
+        Constants.CONSTANT_BootstrapMethod, // pseudo-tag
+        Constants.CONSTANT_InvokeDynamic
+    };
+    static final byte[] LOADABLE_VALUE_TAGS = { // for CONSTANT_LoadableValue
+        Constants.CONSTANT_Integer, Constants.CONSTANT_Float, Constants.CONSTANT_Long, Constants.CONSTANT_Double,
+        Constants.CONSTANT_String, Constants.CONSTANT_Class,
+        Constants.CONSTANT_MethodHandle, Constants.CONSTANT_MethodType
+    };
+    static final byte[] ANY_MEMBER_TAGS = { // for CONSTANT_AnyMember
+        Constants.CONSTANT_Fieldref, Constants.CONSTANT_Methodref, Constants.CONSTANT_InterfaceMethodref
+    };
+    static final byte[] FIELD_SPECIFIC_TAGS = { // for CONSTANT_FieldSpecific
+        Constants.CONSTANT_Integer, Constants.CONSTANT_Float, Constants.CONSTANT_Long, Constants.CONSTANT_Double,
+        Constants.CONSTANT_String
+    };
+    static {
+        assert(
+            verifyTagOrder(TAGS_IN_ORDER) &&
+            verifyTagOrder(NUMBER_TAGS) &&
+            verifyTagOrder(EXTRA_TAGS) &&
+            verifyTagOrder(LOADABLE_VALUE_TAGS) &&
+            verifyTagOrder(ANY_MEMBER_TAGS) &&
+            verifyTagOrder(FIELD_SPECIFIC_TAGS)
+        );
+    }
+    private static boolean verifyTagOrder(byte[] tags) {
+        int prev = -1;
+        for (byte tag : tags) {
+            int next = TAG_ORDER[tag];
+            assert(next > 0) : "tag not found: "+tag;
+            assert(TAGS_IN_ORDER[next-1] == tag) : "tag repeated: "+tag+" => "+next+" => "+TAGS_IN_ORDER[next-1];
+            assert(prev < next) : "tags not in order: "+Arrays.toString(tags)+" at "+tag;
+            prev = next;
+        }
+        return true;
+    }
+}
diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Constants.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Constants.java
new file mode 100644
index 000000000..33d07c7a5
--- /dev/null
+++ b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Constants.java
@@ -0,0 +1,503 @@
+/*
+ * Copyright (c) 2001, 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package net.fabricmc.shade.com.sun.java.util.jar.pack;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Shared constants
+ * @author John Rose
+ */
+class Constants {
+
+    private Constants(){}
+
+    public static final int JAVA_MAGIC = 0xCAFEBABE;
+
+    /*
+        Java Class Version numbers history
+        1.0 to 1.3.X 45,3
+        1.4 to 1.4.X 46,0
+        1.5 to 1.5.X 49,0
+        1.6 to 1.6.X 50,0
+        1.7 to 1.7.X 51,0
+        1.8 to 1.8.X 52,0
+        1.9 to 1.9.X 53,0
+        1.10 to 1.10.X 54,0
+        1.11 to 1.11.X 55,0
+    */
+
+    public static final Package.Version JAVA_MIN_CLASS_VERSION =
+            Package.Version.of(45, 03);
+
+    public static final Package.Version JAVA5_MAX_CLASS_VERSION =
+            Package.Version.of(49, 00);
+
+    public static final Package.Version JAVA6_MAX_CLASS_VERSION =
+            Package.Version.of(50, 00);
+
+    public static final Package.Version JAVA7_MAX_CLASS_VERSION =
+            Package.Version.of(51, 00);
+
+    public static final Package.Version JAVA8_MAX_CLASS_VERSION =
+            Package.Version.of(52, 00);
+
+    public static final Package.Version JAVA9_MAX_CLASS_VERSION =
+            Package.Version.of(53, 00);
+
+    public static final Package.Version JAVA10_MAX_CLASS_VERSION =
+            Package.Version.of(54, 00);
+
+    public static final Package.Version JAVA11_MAX_CLASS_VERSION =
+            Package.Version.of(55, 00);
+
+    public static final int JAVA_PACKAGE_MAGIC = 0xCAFED00D;
+
+    public static final Package.Version JAVA5_PACKAGE_VERSION =
+            Package.Version.of(150, 7);
+
+    public static final Package.Version JAVA6_PACKAGE_VERSION =
+            Package.Version.of(160, 1);
+
+    public static final Package.Version JAVA7_PACKAGE_VERSION =
+            Package.Version.of(170, 1);
+
+    public static final Package.Version JAVA8_PACKAGE_VERSION =
+            Package.Version.of(171, 0);
+
+    // upper limit, should point to the latest class version
+    public static final Package.Version JAVA_MAX_CLASS_VERSION =
+            JAVA11_MAX_CLASS_VERSION;
+
+    // upper limit should point to the latest package version, for version info!.
+    public static final Package.Version MAX_PACKAGE_VERSION =
+            JAVA7_PACKAGE_VERSION;
+
+    public static final int CONSTANT_POOL_INDEX_LIMIT  = 0x10000;
+    public static final int CONSTANT_POOL_NARROW_LIMIT = 0x00100;
+
+    public static final String JAVA_SIGNATURE_CHARS = "BSCIJFDZLV([";
+
+    public static final byte CONSTANT_Utf8 = 1;
+    public static final byte CONSTANT_unused2 = 2;  // unused, was Unicode
+    public static final byte CONSTANT_Integer = 3;
+    public static final byte CONSTANT_Float = 4;
+    public static final byte CONSTANT_Long = 5;
+    public static final byte CONSTANT_Double = 6;
+    public static final byte CONSTANT_Class = 7;
+    public static final byte CONSTANT_String = 8;
+    public static final byte CONSTANT_Fieldref = 9;
+    public static final byte CONSTANT_Methodref = 10;
+    public static final byte CONSTANT_InterfaceMethodref = 11;
+    public static final byte CONSTANT_NameandType = 12;
+    public static final byte CONSTANT_unused13 = 13;
+    public static final byte CONSTANT_unused14 = 14;
+    public static final byte CONSTANT_MethodHandle = 15;
+    public static final byte CONSTANT_MethodType = 16;
+    public static final byte CONSTANT_unused17 = 17;  // unused
+    public static final byte CONSTANT_InvokeDynamic = 18;
+
+    // pseudo-constants:
+    public static final byte CONSTANT_None = 0;
+    public static final byte CONSTANT_Signature = CONSTANT_unused13;
+    public static final byte CONSTANT_BootstrapMethod = CONSTANT_unused17; // used only in InvokeDynamic constants
+    public static final byte CONSTANT_Limit = 19;
+
+    public static final byte CONSTANT_All = 50;  // combined global map
+    public static final byte CONSTANT_LoadableValue = 51; // used for 'KL' and qldc operands
+    public static final byte CONSTANT_AnyMember = 52; // union of refs to field or (interface) method
+    public static final byte CONSTANT_FieldSpecific = 53; // used only for 'KQ' ConstantValue attrs
+    public static final byte CONSTANT_GroupFirst = CONSTANT_All;
+    public static final byte CONSTANT_GroupLimit = CONSTANT_FieldSpecific+1;
+
+    // CONSTANT_MethodHandle reference kinds
+    public static final byte REF_getField = 1;
+    public static final byte REF_getStatic = 2;
+    public static final byte REF_putField = 3;
+    public static final byte REF_putStatic = 4;
+    public static final byte REF_invokeVirtual = 5;
+    public static final byte REF_invokeStatic = 6;
+    public static final byte REF_invokeSpecial = 7;
+    public static final byte REF_newInvokeSpecial = 8;
+    public static final byte REF_invokeInterface = 9;
+
+    // pseudo-access bits
+    public static final int ACC_IC_LONG_FORM   = (1<<16); //for ic_flags
+
+    // attribute "context types"
+    public static final int ATTR_CONTEXT_CLASS  = 0;
+    public static final int ATTR_CONTEXT_FIELD  = 1;
+    public static final int ATTR_CONTEXT_METHOD = 2;
+    public static final int ATTR_CONTEXT_CODE   = 3;
+    public static final int ATTR_CONTEXT_LIMIT  = 4;
+    public static final String[] ATTR_CONTEXT_NAME
+        = { "class", "field", "method", "code" };
+
+    // predefined attr bits
+    public static final int
+        X_ATTR_OVERFLOW = 16,
+        CLASS_ATTR_SourceFile = 17,
+        METHOD_ATTR_Code = 17,
+        FIELD_ATTR_ConstantValue = 17,
+        CLASS_ATTR_EnclosingMethod = 18,
+        METHOD_ATTR_Exceptions = 18,
+        X_ATTR_Signature = 19,
+        X_ATTR_Deprecated = 20,
+        X_ATTR_RuntimeVisibleAnnotations = 21,
+        X_ATTR_RuntimeInvisibleAnnotations = 22,
+        METHOD_ATTR_RuntimeVisibleParameterAnnotations = 23,
+        CLASS_ATTR_InnerClasses = 23,
+        METHOD_ATTR_RuntimeInvisibleParameterAnnotations = 24,
+        CLASS_ATTR_ClassFile_version = 24,
+        METHOD_ATTR_AnnotationDefault = 25,
+        METHOD_ATTR_MethodParameters = 26,           // JDK8
+        X_ATTR_RuntimeVisibleTypeAnnotations = 27,   // JDK8
+        X_ATTR_RuntimeInvisibleTypeAnnotations = 28, // JDK8
+        CODE_ATTR_StackMapTable = 0,  // new in Java 6
+        CODE_ATTR_LineNumberTable = 1,
+        CODE_ATTR_LocalVariableTable = 2,
+        CODE_ATTR_LocalVariableTypeTable = 3;
+
+    // File option bits, from LSB in ascending bit position.
+    public static final int FO_DEFLATE_HINT           = 1<<0;
+    public static final int FO_IS_CLASS_STUB          = 1<<1;
+
+    // Archive option bits, from LSB in ascending bit position:
+    public static final int AO_HAVE_SPECIAL_FORMATS   = 1<<0;
+    public static final int AO_HAVE_CP_NUMBERS        = 1<<1;
+    public static final int AO_HAVE_ALL_CODE_FLAGS    = 1<<2;
+    public static final int AO_HAVE_CP_EXTRAS         = 1<<3;
+    public static final int AO_HAVE_FILE_HEADERS      = 1<<4;
+    public static final int AO_DEFLATE_HINT           = 1<<5;
+    public static final int AO_HAVE_FILE_MODTIME      = 1<<6;
+    public static final int AO_HAVE_FILE_OPTIONS      = 1<<7;
+    public static final int AO_HAVE_FILE_SIZE_HI      = 1<<8;
+    public static final int AO_HAVE_CLASS_FLAGS_HI    = 1<<9;
+    public static final int AO_HAVE_FIELD_FLAGS_HI    = 1<<10;
+    public static final int AO_HAVE_METHOD_FLAGS_HI   = 1<<11;
+    public static final int AO_HAVE_CODE_FLAGS_HI     = 1<<12;
+    public static final int AO_UNUSED_MBZ          = (-1)<<13;  // option bits reserved for future use
+
+    public static final int LG_AO_HAVE_XXX_FLAGS_HI   = 9;
+
+    // visitRefs modes:
+    static final int VRM_CLASSIC = 0;
+    static final int VRM_PACKAGE = 1;
+
+    public static final int NO_MODTIME = 0;  // null modtime value
+
+    // some comstantly empty containers
+    public static final int[]        noInts = {};
+    public static final byte[]       noBytes = {};
+    public static final Object[]     noValues = {};
+    public static final String[]     noStrings = {};
+    public static final List emptyList = Arrays.asList(noValues);
+
+    // meta-coding
+    public static final int
+        _meta_default = 0,
+        _meta_canon_min = 1,
+        _meta_canon_max = 115,
+        _meta_arb = 116,
+        _meta_run = 117,
+        _meta_pop = 141,
+        _meta_limit = 189;
+
+    // bytecodes
+    public static final int
+        _nop                  =   0, // 0x00
+        _aconst_null          =   1, // 0x01
+        _iconst_m1            =   2, // 0x02
+        _iconst_0             =   3, // 0x03
+        _iconst_1             =   4, // 0x04
+        _iconst_2             =   5, // 0x05
+        _iconst_3             =   6, // 0x06
+        _iconst_4             =   7, // 0x07
+        _iconst_5             =   8, // 0x08
+        _lconst_0             =   9, // 0x09
+        _lconst_1             =  10, // 0x0a
+        _fconst_0             =  11, // 0x0b
+        _fconst_1             =  12, // 0x0c
+        _fconst_2             =  13, // 0x0d
+        _dconst_0             =  14, // 0x0e
+        _dconst_1             =  15, // 0x0f
+        _bipush               =  16, // 0x10
+        _sipush               =  17, // 0x11
+        _ldc                  =  18, // 0x12
+        _ldc_w                =  19, // 0x13
+        _ldc2_w               =  20, // 0x14
+        _iload                =  21, // 0x15
+        _lload                =  22, // 0x16
+        _fload                =  23, // 0x17
+        _dload                =  24, // 0x18
+        _aload                =  25, // 0x19
+        _iload_0              =  26, // 0x1a
+        _iload_1              =  27, // 0x1b
+        _iload_2              =  28, // 0x1c
+        _iload_3              =  29, // 0x1d
+        _lload_0              =  30, // 0x1e
+        _lload_1              =  31, // 0x1f
+        _lload_2              =  32, // 0x20
+        _lload_3              =  33, // 0x21
+        _fload_0              =  34, // 0x22
+        _fload_1              =  35, // 0x23
+        _fload_2              =  36, // 0x24
+        _fload_3              =  37, // 0x25
+        _dload_0              =  38, // 0x26
+        _dload_1              =  39, // 0x27
+        _dload_2              =  40, // 0x28
+        _dload_3              =  41, // 0x29
+        _aload_0              =  42, // 0x2a
+        _aload_1              =  43, // 0x2b
+        _aload_2              =  44, // 0x2c
+        _aload_3              =  45, // 0x2d
+        _iaload               =  46, // 0x2e
+        _laload               =  47, // 0x2f
+        _faload               =  48, // 0x30
+        _daload               =  49, // 0x31
+        _aaload               =  50, // 0x32
+        _baload               =  51, // 0x33
+        _caload               =  52, // 0x34
+        _saload               =  53, // 0x35
+        _istore               =  54, // 0x36
+        _lstore               =  55, // 0x37
+        _fstore               =  56, // 0x38
+        _dstore               =  57, // 0x39
+        _astore               =  58, // 0x3a
+        _istore_0             =  59, // 0x3b
+        _istore_1             =  60, // 0x3c
+        _istore_2             =  61, // 0x3d
+        _istore_3             =  62, // 0x3e
+        _lstore_0             =  63, // 0x3f
+        _lstore_1             =  64, // 0x40
+        _lstore_2             =  65, // 0x41
+        _lstore_3             =  66, // 0x42
+        _fstore_0             =  67, // 0x43
+        _fstore_1             =  68, // 0x44
+        _fstore_2             =  69, // 0x45
+        _fstore_3             =  70, // 0x46
+        _dstore_0             =  71, // 0x47
+        _dstore_1             =  72, // 0x48
+        _dstore_2             =  73, // 0x49
+        _dstore_3             =  74, // 0x4a
+        _astore_0             =  75, // 0x4b
+        _astore_1             =  76, // 0x4c
+        _astore_2             =  77, // 0x4d
+        _astore_3             =  78, // 0x4e
+        _iastore              =  79, // 0x4f
+        _lastore              =  80, // 0x50
+        _fastore              =  81, // 0x51
+        _dastore              =  82, // 0x52
+        _aastore              =  83, // 0x53
+        _bastore              =  84, // 0x54
+        _castore              =  85, // 0x55
+        _sastore              =  86, // 0x56
+        _pop                  =  87, // 0x57
+        _pop2                 =  88, // 0x58
+        _dup                  =  89, // 0x59
+        _dup_x1               =  90, // 0x5a
+        _dup_x2               =  91, // 0x5b
+        _dup2                 =  92, // 0x5c
+        _dup2_x1              =  93, // 0x5d
+        _dup2_x2              =  94, // 0x5e
+        _swap                 =  95, // 0x5f
+        _iadd                 =  96, // 0x60
+        _ladd                 =  97, // 0x61
+        _fadd                 =  98, // 0x62
+        _dadd                 =  99, // 0x63
+        _isub                 = 100, // 0x64
+        _lsub                 = 101, // 0x65
+        _fsub                 = 102, // 0x66
+        _dsub                 = 103, // 0x67
+        _imul                 = 104, // 0x68
+        _lmul                 = 105, // 0x69
+        _fmul                 = 106, // 0x6a
+        _dmul                 = 107, // 0x6b
+        _idiv                 = 108, // 0x6c
+        _ldiv                 = 109, // 0x6d
+        _fdiv                 = 110, // 0x6e
+        _ddiv                 = 111, // 0x6f
+        _irem                 = 112, // 0x70
+        _lrem                 = 113, // 0x71
+        _frem                 = 114, // 0x72
+        _drem                 = 115, // 0x73
+        _ineg                 = 116, // 0x74
+        _lneg                 = 117, // 0x75
+        _fneg                 = 118, // 0x76
+        _dneg                 = 119, // 0x77
+        _ishl                 = 120, // 0x78
+        _lshl                 = 121, // 0x79
+        _ishr                 = 122, // 0x7a
+        _lshr                 = 123, // 0x7b
+        _iushr                = 124, // 0x7c
+        _lushr                = 125, // 0x7d
+        _iand                 = 126, // 0x7e
+        _land                 = 127, // 0x7f
+        _ior                  = 128, // 0x80
+        _lor                  = 129, // 0x81
+        _ixor                 = 130, // 0x82
+        _lxor                 = 131, // 0x83
+        _iinc                 = 132, // 0x84
+        _i2l                  = 133, // 0x85
+        _i2f                  = 134, // 0x86
+        _i2d                  = 135, // 0x87
+        _l2i                  = 136, // 0x88
+        _l2f                  = 137, // 0x89
+        _l2d                  = 138, // 0x8a
+        _f2i                  = 139, // 0x8b
+        _f2l                  = 140, // 0x8c
+        _f2d                  = 141, // 0x8d
+        _d2i                  = 142, // 0x8e
+        _d2l                  = 143, // 0x8f
+        _d2f                  = 144, // 0x90
+        _i2b                  = 145, // 0x91
+        _i2c                  = 146, // 0x92
+        _i2s                  = 147, // 0x93
+        _lcmp                 = 148, // 0x94
+        _fcmpl                = 149, // 0x95
+        _fcmpg                = 150, // 0x96
+        _dcmpl                = 151, // 0x97
+        _dcmpg                = 152, // 0x98
+        _ifeq                 = 153, // 0x99
+        _ifne                 = 154, // 0x9a
+        _iflt                 = 155, // 0x9b
+        _ifge                 = 156, // 0x9c
+        _ifgt                 = 157, // 0x9d
+        _ifle                 = 158, // 0x9e
+        _if_icmpeq            = 159, // 0x9f
+        _if_icmpne            = 160, // 0xa0
+        _if_icmplt            = 161, // 0xa1
+        _if_icmpge            = 162, // 0xa2
+        _if_icmpgt            = 163, // 0xa3
+        _if_icmple            = 164, // 0xa4
+        _if_acmpeq            = 165, // 0xa5
+        _if_acmpne            = 166, // 0xa6
+        _goto                 = 167, // 0xa7
+        _jsr                  = 168, // 0xa8
+        _ret                  = 169, // 0xa9
+        _tableswitch          = 170, // 0xaa
+        _lookupswitch         = 171, // 0xab
+        _ireturn              = 172, // 0xac
+        _lreturn              = 173, // 0xad
+        _freturn              = 174, // 0xae
+        _dreturn              = 175, // 0xaf
+        _areturn              = 176, // 0xb0
+        _return               = 177, // 0xb1
+        _getstatic            = 178, // 0xb2
+        _putstatic            = 179, // 0xb3
+        _getfield             = 180, // 0xb4
+        _putfield             = 181, // 0xb5
+        _invokevirtual        = 182, // 0xb6
+        _invokespecial        = 183, // 0xb7
+        _invokestatic         = 184, // 0xb8
+        _invokeinterface      = 185, // 0xb9
+        _invokedynamic        = 186, // 0xba
+        _new                  = 187, // 0xbb
+        _newarray             = 188, // 0xbc
+        _anewarray            = 189, // 0xbd
+        _arraylength          = 190, // 0xbe
+        _athrow               = 191, // 0xbf
+        _checkcast            = 192, // 0xc0
+        _instanceof           = 193, // 0xc1
+        _monitorenter         = 194, // 0xc2
+        _monitorexit          = 195, // 0xc3
+        _wide                 = 196, // 0xc4
+        _multianewarray       = 197, // 0xc5
+        _ifnull               = 198, // 0xc6
+        _ifnonnull            = 199, // 0xc7
+        _goto_w               = 200, // 0xc8
+        _jsr_w                = 201, // 0xc9
+        _bytecode_limit       = 202; // 0xca
+
+    // End marker, used to terminate bytecode sequences:
+    public static final int _end_marker = 255;
+    // Escapes:
+    public static final int _byte_escape = 254;
+    public static final int _ref_escape = 253;
+
+    // Self-relative pseudo-opcodes for better compression.
+    // A "linker op" is a bytecode which links to a class member.
+    // (But in what follows, "invokeinterface" ops are excluded.)
+    //
+    // A "self linker op" is a variant bytecode which works only
+    // with the current class or its super.  Because the number of
+    // possible targets is small, it admits a more compact encoding.
+    // Self linker ops are allowed to absorb a previous "aload_0" op.
+    // There are (7 * 4) self linker ops (super or not, aload_0 or not).
+    //
+    // For simplicity, we define the full symmetric set of variants.
+    // However, some of them are relatively useless.
+    // Self linker ops are enabled by Pack.selfCallVariants (true).
+    public static final int _first_linker_op = _getstatic;
+    public static final int _last_linker_op  = _invokestatic;
+    public static final int _num_linker_ops  = (_last_linker_op - _first_linker_op) + 1;
+    public static final int _self_linker_op  = _bytecode_limit;
+    public static final int _self_linker_aload_flag = 1*_num_linker_ops;
+    public static final int _self_linker_super_flag = 2*_num_linker_ops;
+    public static final int _self_linker_limit = _self_linker_op + 4*_num_linker_ops;
+    // An "invoke init" op is a variant of invokespecial which works
+    // only with the method name "".  There are variants which
+    // link to the current class, the super class, or the class of the
+    // immediately previous "newinstance" op.  There are 3 of these ops.
+    // They all take method signature references as operands.
+    // Invoke init ops are enabled by Pack.initCallVariants (true).
+    public static final int _invokeinit_op = _self_linker_limit;
+    public static final int _invokeinit_self_option = 0;
+    public static final int _invokeinit_super_option = 1;
+    public static final int _invokeinit_new_option = 2;
+    public static final int _invokeinit_limit = _invokeinit_op+3;
+
+    public static final int _pseudo_instruction_limit = _invokeinit_limit;
+    // linker variant limit == 202+(7*4)+3 == 233
+
+    // Ldc variants support strongly typed references to constants.
+    // This lets us index constant pool entries completely according to tag,
+    // which is a great simplification.
+    // Ldc variants gain us only 0.007% improvement in compression ratio,
+    // but they simplify the file format greatly.
+    public static final int _xldc_op = _invokeinit_limit;
+    public static final int _sldc = _ldc;  // previously named _aldc
+    public static final int _cldc = _xldc_op+0;
+    public static final int _ildc = _xldc_op+1;
+    public static final int _fldc = _xldc_op+2;
+    public static final int _sldc_w = _ldc_w;  // previously named _aldc_w
+    public static final int _cldc_w = _xldc_op+3;
+    public static final int _ildc_w = _xldc_op+4;
+    public static final int _fldc_w = _xldc_op+5;
+    public static final int _lldc2_w = _ldc2_w;
+    public static final int _dldc2_w = _xldc_op+6;
+    // anything other than primitive, string, or class must be handled with qldc:
+    public static final int _qldc   = _xldc_op+7;
+    public static final int _qldc_w = _xldc_op+8;
+    public static final int _xldc_limit = _xldc_op+9;
+
+    // handling of InterfaceMethodRef
+    public static final int _invoke_int_op = _xldc_limit;
+    public static final int _invokespecial_int = _invoke_int_op+0;
+    public static final int _invokestatic_int = _invoke_int_op+1;
+    public static final int _invoke_int_limit = _invoke_int_op+2;
+}
diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Driver.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Driver.java
new file mode 100644
index 000000000..1681f507d
--- /dev/null
+++ b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Driver.java
@@ -0,0 +1,749 @@
+/*
+ * Copyright (c) 2003, 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package net.fabricmc.shade.com.sun.java.util.jar.pack;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
+import java.text.MessageFormat;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Properties;
+import java.util.ResourceBundle;
+import java.util.SortedMap;
+import java.util.TreeMap;
+import java.util.jar.JarFile;
+import java.util.jar.JarOutputStream;
+import net.fabricmc.shade.java.util.jar.Pack200;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+/** Command line interface for Pack200.
+ */
+
+@SuppressWarnings({"removal"})
+class Driver {
+    private static final ResourceBundle RESOURCE =
+        ResourceBundle.getBundle("com.sun.java.util.jar.pack.DriverResource");
+    private static boolean suppressDeprecateMsg = false;
+
+    public static void main(String[] ava) throws IOException {
+        List av = new ArrayList<>(Arrays.asList(ava));
+
+        boolean doPack   = true;
+        boolean doUnpack = false;
+        boolean doRepack = false;
+        boolean doZip = true;
+        suppressDeprecateMsg = av.remove("-XDsuppress-tool-removal-message");
+        String logFile = null;
+        String verboseProp = Utils.DEBUG_VERBOSE;
+
+        {
+            // Non-standard, undocumented "--unpack" switch enables unpack mode.
+            String arg0 = av.isEmpty() ? "" : av.get(0);
+            switch (arg0) {
+                case "--pack":
+                av.remove(0);
+                    break;
+                case "--unpack":
+                av.remove(0);
+                doPack = false;
+                doUnpack = true;
+                    break;
+            }
+        }
+
+        if (!suppressDeprecateMsg) {
+            printDeprecateWarning(doPack, System.out);
+        }
+
+        // Collect engine properties here:
+        Map engProps = new HashMap<>();
+        engProps.put(verboseProp, System.getProperty(verboseProp));
+
+        String optionMap;
+        String[] propTable;
+        if (doPack) {
+            optionMap = PACK200_OPTION_MAP;
+            propTable = PACK200_PROPERTY_TO_OPTION;
+        } else {
+            optionMap = UNPACK200_OPTION_MAP;
+            propTable = UNPACK200_PROPERTY_TO_OPTION;
+        }
+
+        // Collect argument properties here:
+        Map avProps = new HashMap<>();
+        try {
+            for (;;) {
+                String state = parseCommandOptions(av, optionMap, avProps);
+                // Translate command line options to Pack200 properties:
+            eachOpt:
+                for (Iterator opti = avProps.keySet().iterator();
+                     opti.hasNext(); ) {
+                    String opt = opti.next();
+                    String prop = null;
+                    for (int i = 0; i < propTable.length; i += 2) {
+                        if (opt.equals(propTable[1+i])) {
+                            prop = propTable[0+i];
+                            break;
+                        }
+                    }
+                    if (prop != null) {
+                        String val = avProps.get(opt);
+                        opti.remove();  // remove opt from avProps
+                        if (!prop.endsWith(".")) {
+                            // Normal string or boolean.
+                            if (!(opt.equals("--verbose")
+                                  || opt.endsWith("="))) {
+                                // Normal boolean; convert to T/F.
+                                boolean flag = (val != null);
+                                if (opt.startsWith("--no-"))
+                                    flag = !flag;
+                                val = flag? "true": "false";
+                            }
+                            engProps.put(prop, val);
+                        } else if (prop.contains(".attribute.")) {
+                            for (String val1 : val.split("\0")) {
+                                String[] val2 = val1.split("=", 2);
+                                engProps.put(prop+val2[0], val2[1]);
+                            }
+                        } else {
+                            // Collection property: pack.pass.file.cli.NNN
+                            int idx = 1;
+                            for (String val1 : val.split("\0")) {
+                                String prop1;
+                                do {
+                                    prop1 = prop+"cli."+(idx++);
+                                } while (engProps.containsKey(prop1));
+                                engProps.put(prop1, val1);
+                            }
+                        }
+                    }
+                }
+
+                // See if there is any other action to take.
+                if ("--config-file=".equals(state)) {
+                    String propFile = av.remove(0);
+                    Properties fileProps = new Properties();
+                    try (InputStream propIn = new FileInputStream(propFile)) {
+                        fileProps.load(propIn);
+                    }
+                    if (engProps.get(verboseProp) != null)
+                        fileProps.list(System.out);
+                    for (Map.Entry me : fileProps.entrySet()) {
+                        engProps.put((String) me.getKey(), (String) me.getValue());
+                    }
+                } else if ("--version".equals(state)) {
+                        System.out.println(MessageFormat.format(RESOURCE.getString(DriverResource.VERSION),
+                                                                Driver.class.getName(), "1.31, 07/05/05"));
+                    return;
+                } else if ("--help".equals(state)) {
+                    printUsage(doPack, true, System.out);
+                    System.exit(0);
+                    return;
+                } else {
+                    break;
+                }
+            }
+        } catch (IllegalArgumentException ee) {
+                System.err.println(MessageFormat.format(RESOURCE.getString(DriverResource.BAD_ARGUMENT), ee));
+            printUsage(doPack, false, System.err);
+            System.exit(2);
+            return;
+        }
+
+        // Deal with remaining non-engine properties:
+        for (String opt : avProps.keySet()) {
+            String val = avProps.get(opt);
+            switch (opt) {
+                case "--repack":
+                    doRepack = true;
+                    break;
+                case "--no-gzip":
+                    doZip = (val == null);
+                    break;
+                case "--log-file=":
+                    logFile = val;
+                    break;
+                default:
+                    throw new InternalError(MessageFormat.format(
+                            RESOURCE.getString(DriverResource.BAD_OPTION),
+                            opt, avProps.get(opt)));
+            }
+        }
+
+        if (logFile != null && !logFile.equals("")) {
+            if (logFile.equals("-")) {
+                System.setErr(System.out);
+            } else {
+                OutputStream log = new FileOutputStream(logFile);
+                //log = new BufferedOutputStream(out);
+                System.setErr(new PrintStream(log));
+            }
+        }
+
+        boolean verbose = (engProps.get(verboseProp) != null);
+
+        String packfile = "";
+        if (!av.isEmpty())
+            packfile = av.remove(0);
+
+        String jarfile = "";
+        if (!av.isEmpty())
+            jarfile = av.remove(0);
+
+        String newfile = "";  // output JAR file if --repack
+        String bakfile = "";  // temporary backup of input JAR
+        String tmpfile = "";  // temporary file to be deleted
+        if (doRepack) {
+            // The first argument is the target JAR file.
+            // (Note:  *.pac is nonstandard, but may be necessary
+            // if a host OS truncates file extensions.)
+            if (packfile.toLowerCase().endsWith(".pack") ||
+                packfile.toLowerCase().endsWith(".pac") ||
+                packfile.toLowerCase().endsWith(".gz")) {
+                System.err.println(MessageFormat.format(
+                        RESOURCE.getString(DriverResource.BAD_REPACK_OUTPUT),
+                        packfile));
+                printUsage(doPack, false, System.err);
+                System.exit(2);
+            }
+            newfile = packfile;
+            // The optional second argument is the source JAR file.
+            if (jarfile.equals("")) {
+                // If only one file is given, it is the only JAR.
+                // It serves as both input and output.
+                jarfile = newfile;
+            }
+            tmpfile = createTempFile(newfile, ".pack").getPath();
+            packfile = tmpfile;
+            doZip = false;  // no need to zip the temporary file
+        }
+
+        if (!av.isEmpty()
+            // Accept jarfiles ending with .jar or .zip.
+            // Accept jarfile of "-" (stdout), but only if unpacking.
+            || !(jarfile.toLowerCase().endsWith(".jar")
+                 || jarfile.toLowerCase().endsWith(".zip")
+                 || (jarfile.equals("-") && !doPack))) {
+            printUsage(doPack, false, System.err);
+            System.exit(2);
+            return;
+        }
+
+        if (doRepack)
+            doPack = doUnpack = true;
+        else if (doPack)
+            doUnpack = false;
+
+        Pack200.Packer jpack = Pack200.newPacker();
+        Pack200.Unpacker junpack = Pack200.newUnpacker();
+
+        jpack.properties().putAll(engProps);
+        junpack.properties().putAll(engProps);
+        if (doRepack && newfile.equals(jarfile)) {
+            String zipc = getZipComment(jarfile);
+            if (verbose && !zipc.isEmpty())
+                System.out.println(MessageFormat.format(RESOURCE.getString(DriverResource.DETECTED_ZIP_COMMENT), zipc));
+            if (zipc.indexOf(Utils.PACK_ZIP_ARCHIVE_MARKER_COMMENT) >= 0) {
+                    System.out.println(MessageFormat.format(RESOURCE.getString(DriverResource.SKIP_FOR_REPACKED), jarfile));
+                        doPack = false;
+                        doUnpack = false;
+                        doRepack = false;
+            }
+        }
+
+        try {
+
+            if (doPack) {
+                // Mode = Pack.
+                JarFile in = new JarFile(new File(jarfile));
+                OutputStream out;
+                // Packfile must be -, *.gz, *.pack, or *.pac.
+                if (packfile.equals("-")) {
+                    out = System.out;
+                    // Send warnings, etc., to stderr instead of stdout.
+                    System.setOut(System.err);
+                } else if (doZip) {
+                    if (!packfile.endsWith(".gz")) {
+                    System.err.println(MessageFormat.format(RESOURCE.getString(DriverResource.WRITE_PACK_FILE), packfile));
+                        printUsage(doPack, false, System.err);
+                        System.exit(2);
+                    }
+                    out = new FileOutputStream(packfile);
+                    out = new BufferedOutputStream(out);
+                    out = new GZIPOutputStream(out);
+                } else {
+                    if (!packfile.toLowerCase().endsWith(".pack") &&
+                            !packfile.toLowerCase().endsWith(".pac")) {
+                        System.err.println(MessageFormat.format(RESOURCE.getString(DriverResource.WRITE_PACKGZ_FILE),packfile));
+                        printUsage(doPack, false, System.err);
+                        System.exit(2);
+                    }
+                    out = new FileOutputStream(packfile);
+                    out = new BufferedOutputStream(out);
+                }
+                jpack.pack(in, out);
+                //in.close();  // p200 closes in but not out
+                out.close();
+            }
+
+            if (doRepack && newfile.equals(jarfile)) {
+                // If the source and destination are the same,
+                // we will move the input JAR aside while regenerating it.
+                // This allows us to restore it if something goes wrong.
+                File bakf = createTempFile(jarfile, ".bak");
+                // On Windows target must be deleted see 4017593
+                bakf.delete();
+                boolean okBackup = new File(jarfile).renameTo(bakf);
+                if (!okBackup) {
+                        throw new Error(MessageFormat.format(RESOURCE.getString(DriverResource.SKIP_FOR_MOVE_FAILED),bakfile));
+                } else {
+                    // Open jarfile recovery bracket.
+                    bakfile = bakf.getPath();
+                }
+            }
+
+            if (doUnpack) {
+                // Mode = Unpack.
+                InputStream in;
+                if (packfile.equals("-"))
+                    in = System.in;
+                else
+                    in = new FileInputStream(new File(packfile));
+                BufferedInputStream inBuf = new BufferedInputStream(in);
+                in = inBuf;
+                if (Utils.isGZIPMagic(Utils.readMagic(inBuf))) {
+                    in = new GZIPInputStream(in);
+                }
+                String outfile = newfile.equals("")? jarfile: newfile;
+                OutputStream fileOut;
+                if (outfile.equals("-"))
+                    fileOut = System.out;
+                else
+                    fileOut = new FileOutputStream(outfile);
+                fileOut = new BufferedOutputStream(fileOut);
+                try (JarOutputStream out = new JarOutputStream(fileOut)) {
+                    junpack.unpack(in, out);
+                    // p200 closes in but not out
+                }
+                // At this point, we have a good jarfile (or newfile, if -r)
+            }
+
+            if (!bakfile.equals("")) {
+                        // On success, abort jarfile recovery bracket.
+                        new File(bakfile).delete();
+                        bakfile = "";
+            }
+
+        } finally {
+            // Close jarfile recovery bracket.
+            if (!bakfile.equals("")) {
+                File jarFile = new File(jarfile);
+                jarFile.delete(); // Win32 requires this, see above
+                new File(bakfile).renameTo(jarFile);
+            }
+            // In all cases, delete temporary *.pack.
+            if (!tmpfile.equals(""))
+                new File(tmpfile).delete();
+        }
+    }
+
+    private static
+    File createTempFile(String basefile, String suffix) throws IOException {
+        File base = new File(basefile);
+        String prefix = base.getName();
+        if (prefix.length() < 3)  prefix += "tmp";
+
+        File where = (base.getParentFile() == null && suffix.equals(".bak"))
+                ? new File(".").getAbsoluteFile()
+                : base.getParentFile();
+
+        Path tmpfile = (where == null)
+                ? Files.createTempFile(prefix, suffix)
+                : Files.createTempFile(where.toPath(), prefix, suffix);
+
+        return tmpfile.toFile();
+    }
+
+    private static
+    void printDeprecateWarning(boolean doPack, PrintStream out) {
+        String prog = doPack ? "pack200" : "unpack200";
+        out.println(MessageFormat.format(RESOURCE.getString(DriverResource.DEPRECATED), prog));
+    }
+
+    private static
+    void printUsage(boolean doPack, boolean full, PrintStream out) {
+        String prog = doPack ? "pack200" : "unpack200";
+        String[] packUsage = (String[])RESOURCE.getObject(DriverResource.PACK_HELP);
+        String[] unpackUsage = (String[])RESOURCE.getObject(DriverResource.UNPACK_HELP);
+        String[] usage = doPack? packUsage: unpackUsage;
+        for (int i = 0; i < usage.length; i++) {
+            out.println(usage[i]);
+            if (!full) {
+            out.println(MessageFormat.format(RESOURCE.getString(DriverResource.MORE_INFO), prog));
+                break;
+            }
+        }
+        // Print a warning at the end
+        // The full help page is long, the beginning warning could be out of sight
+        if (full && !suppressDeprecateMsg) {
+            printDeprecateWarning(doPack, out);
+        }
+    }
+
+    private static
+        String getZipComment(String jarfile) throws IOException {
+        byte[] tail = new byte[1000];
+        long filelen = new File(jarfile).length();
+        if (filelen <= 0)  return "";
+        long skiplen = Math.max(0, filelen - tail.length);
+        try (InputStream in = new FileInputStream(new File(jarfile))) {
+            in.skip(skiplen);
+            in.read(tail);
+            for (int i = tail.length-4; i >= 0; i--) {
+                if (tail[i+0] == 'P' && tail[i+1] == 'K' &&
+                    tail[i+2] ==  5  && tail[i+3] ==  6) {
+                    // Skip sig4, disks4, entries4, clen4, coff4, cmt2
+                    i += 4+4+4+4+4+2;
+                    if (i < tail.length)
+                        return new String(tail, i, tail.length-i, StandardCharsets.UTF_8);
+                    return "";
+                }
+            }
+            return "";
+        }
+    }
+
+    private static final String PACK200_OPTION_MAP =
+        (""
+         +"--repack                 $ \n  -r +>- @--repack              $ \n"
+         +"--no-gzip                $ \n  -g +>- @--no-gzip             $ \n"
+         +"--strip-debug            $ \n  -G +>- @--strip-debug         $ \n"
+         +"--no-keep-file-order     $ \n  -O +>- @--no-keep-file-order  $ \n"
+         +"--segment-limit=      *> = \n  -S +>  @--segment-limit=      = \n"
+         +"--effort=             *> = \n  -E +>  @--effort=             = \n"
+         +"--deflate-hint=       *> = \n  -H +>  @--deflate-hint=       = \n"
+         +"--modification-time=  *> = \n  -m +>  @--modification-time=  = \n"
+         +"--pass-file=        *> &\0 \n  -P +>  @--pass-file=        &\0 \n"
+         +"--unknown-attribute=  *> = \n  -U +>  @--unknown-attribute=  = \n"
+         +"--class-attribute=  *> &\0 \n  -C +>  @--class-attribute=  &\0 \n"
+         +"--field-attribute=  *> &\0 \n  -F +>  @--field-attribute=  &\0 \n"
+         +"--method-attribute= *> &\0 \n  -M +>  @--method-attribute= &\0 \n"
+         +"--code-attribute=   *> &\0 \n  -D +>  @--code-attribute=   &\0 \n"
+         +"--config-file=      *>   . \n  -f +>  @--config-file=        . \n"
+
+         // Negative options as required by CLIP:
+         +"--no-strip-debug  !--strip-debug         \n"
+         +"--gzip            !--no-gzip             \n"
+         +"--keep-file-order !--no-keep-file-order  \n"
+
+         // Non-Standard Options
+         +"--verbose                $ \n  -v +>- @--verbose             $ \n"
+         +"--quiet        !--verbose  \n  -q +>- !--verbose               \n"
+         +"--log-file=           *> = \n  -l +>  @--log-file=           = \n"
+         //+"--java-option=      *> = \n  -J +>  @--java-option=        = \n"
+         +"--version                . \n  -V +>  @--version             . \n"
+         +"--help               . \n  -? +> @--help . \n  -h +> @--help . \n"
+
+         // Termination:
+         +"--           . \n"  // end option sequence here
+         +"-   +?    >- . \n"  // report error if -XXX present; else use stdout
+         );
+    // Note: Collection options use "\0" as a delimiter between arguments.
+
+    // For Java version of unpacker (used for testing only):
+    private static final String UNPACK200_OPTION_MAP =
+        (""
+         +"--deflate-hint=       *> = \n  -H +>  @--deflate-hint=       = \n"
+         +"--verbose                $ \n  -v +>- @--verbose             $ \n"
+         +"--quiet        !--verbose  \n  -q +>- !--verbose               \n"
+         +"--remove-pack-file       $ \n  -r +>- @--remove-pack-file    $ \n"
+         +"--log-file=           *> = \n  -l +>  @--log-file=           = \n"
+         +"--config-file=        *> . \n  -f +>  @--config-file=        . \n"
+
+         // Termination:
+         +"--           . \n"  // end option sequence here
+         +"-   +?    >- . \n"  // report error if -XXX present; else use stdin
+         +"--version                . \n  -V +>  @--version             . \n"
+         +"--help               . \n  -? +> @--help . \n  -h +> @--help . \n"
+         );
+
+    private static final String[] PACK200_PROPERTY_TO_OPTION = {
+        Pack200.Packer.SEGMENT_LIMIT, "--segment-limit=",
+        Pack200.Packer.KEEP_FILE_ORDER, "--no-keep-file-order",
+        Pack200.Packer.EFFORT, "--effort=",
+        Pack200.Packer.DEFLATE_HINT, "--deflate-hint=",
+        Pack200.Packer.MODIFICATION_TIME, "--modification-time=",
+        Pack200.Packer.PASS_FILE_PFX, "--pass-file=",
+        Pack200.Packer.UNKNOWN_ATTRIBUTE, "--unknown-attribute=",
+        Pack200.Packer.CLASS_ATTRIBUTE_PFX, "--class-attribute=",
+        Pack200.Packer.FIELD_ATTRIBUTE_PFX, "--field-attribute=",
+        Pack200.Packer.METHOD_ATTRIBUTE_PFX, "--method-attribute=",
+        Pack200.Packer.CODE_ATTRIBUTE_PFX, "--code-attribute=",
+        //Pack200.Packer.PROGRESS, "--progress=",
+        Utils.DEBUG_VERBOSE, "--verbose",
+        Utils.COM_PREFIX+"strip.debug", "--strip-debug",
+    };
+
+    private static final String[] UNPACK200_PROPERTY_TO_OPTION = {
+        Pack200.Unpacker.DEFLATE_HINT, "--deflate-hint=",
+        //Pack200.Unpacker.PROGRESS, "--progress=",
+        Utils.DEBUG_VERBOSE, "--verbose",
+        Utils.UNPACK_REMOVE_PACKFILE, "--remove-pack-file",
+    };
+
+    /*-*
+     * Remove a set of command-line options from args,
+     * storing them in the map in a canonicalized form.
+     * 

+ * The options string is a newline-separated series of + * option processing specifiers. + */ + private static + String parseCommandOptions(List args, + String options, + Map properties) { + //System.out.println(args+" // "+properties); + + String resultString = null; + + // Convert options string into optLines dictionary. + TreeMap optmap = new TreeMap<>(); + loadOptmap: + for (String optline : options.split("\n")) { + String[] words = optline.split("\\p{Space}+"); + if (words.length == 0) continue loadOptmap; + String opt = words[0]; + words[0] = ""; // initial word is not a spec + if (opt.isEmpty() && words.length >= 1) { + opt = words[1]; // initial "word" is empty due to leading ' ' + words[1] = ""; + } + if (opt.length() == 0) continue loadOptmap; + String[] prevWords = optmap.put(opt, words); + if (prevWords != null) + throw new RuntimeException(MessageFormat.format(RESOURCE.getString(DriverResource.DUPLICATE_OPTION), optline.trim())); + } + + // State machine for parsing a command line. + ListIterator argp = args.listIterator(); + ListIterator pbp = new ArrayList().listIterator(); + doArgs: + for (;;) { + // One trip through this loop per argument. + // Multiple trips per option only if several options per argument. + String arg; + if (pbp.hasPrevious()) { + arg = pbp.previous(); + pbp.remove(); + } else if (argp.hasNext()) { + arg = argp.next(); + } else { + // No more arguments at all. + break doArgs; + } + tryOpt: + for (int optlen = arg.length(); ; optlen--) { + // One time through this loop for each matching arg prefix. + String opt; + // Match some prefix of the argument to a key in optmap. + findOpt: + for (;;) { + opt = arg.substring(0, optlen); + if (optmap.containsKey(opt)) break findOpt; + if (optlen == 0) break tryOpt; + // Decide on a smaller prefix to search for. + SortedMap pfxmap = optmap.headMap(opt); + // pfxmap.lastKey is no shorter than any prefix in optmap. + int len = pfxmap.isEmpty() ? 0 : pfxmap.lastKey().length(); + optlen = Math.min(len, optlen - 1); + opt = arg.substring(0, optlen); + // (Note: We could cut opt down to its common prefix with + // pfxmap.lastKey, but that wouldn't save many cycles.) + } + opt = opt.intern(); + assert(arg.startsWith(opt)); + assert(opt.length() == optlen); + String val = arg.substring(optlen); // arg == opt+val + + // Execute the option processing specs for this opt. + // If no actions are taken, then look for a shorter prefix. + boolean didAction = false; + boolean isError = false; + + int pbpMark = pbp.nextIndex(); // in case of backtracking + String[] specs = optmap.get(opt); + eachSpec: + for (String spec : specs) { + if (spec.length() == 0) continue eachSpec; + if (spec.startsWith("#")) break eachSpec; + int sidx = 0; + char specop = spec.charAt(sidx++); + + // Deal with '+'/'*' prefixes (spec conditions). + boolean ok; + switch (specop) { + case '+': + // + means we want an non-empty val suffix. + ok = !val.isEmpty(); + specop = spec.charAt(sidx++); + break; + case '*': + // * means we accept empty or non-empty + ok = true; + specop = spec.charAt(sidx++); + break; + default: + // No condition prefix means we require an exact + // match, as indicated by an empty val suffix. + ok = (val.length() == 0); + break; + } + if (!ok) continue eachSpec; + + String specarg = spec.substring(sidx); + switch (specop) { + case '.': // terminate the option sequence + resultString = specarg.isEmpty() ? opt : specarg.intern(); + break doArgs; + case '?': // abort the option sequence + resultString = specarg.isEmpty() ? arg : specarg.intern(); + isError = true; + break eachSpec; + case '@': // change the effective opt name + opt = specarg.intern(); + break; + case '>': // shift remaining arg val to next arg + pbp.add(specarg + val); // push a new argument + val = ""; + break; + case '!': // negation option + String negopt = specarg.isEmpty() ? opt : specarg.intern(); + properties.remove(negopt); + properties.put(negopt, null); // leave placeholder + didAction = true; + break; + case '$': // normal "boolean" option + String boolval; + if (!specarg.isEmpty()) { + // If there is a given spec token, store it. + boolval = specarg; + } else { + String old = properties.get(opt); + if (old == null || old.length() == 0) { + boolval = "1"; + } else { + // Increment any previous value as a numeral. + boolval = ""+(1+Integer.parseInt(old)); + } + } + properties.put(opt, boolval); + didAction = true; + break; + case '=': // "string" option + case '&': // "collection" option + // Read an option. + boolean append = (specop == '&'); + String strval; + if (pbp.hasPrevious()) { + strval = pbp.previous(); + pbp.remove(); + } else if (argp.hasNext()) { + strval = argp.next(); + } else { + resultString = arg + " ?"; + isError = true; + break eachSpec; + } + if (append) { + String old = properties.get(opt); + if (old != null) { + // Append new val to old with embedded delim. + String delim = specarg; + if (delim.length() == 0) delim = " "; + strval = old + specarg + strval; + } + } + properties.put(opt, strval); + didAction = true; + break; + default: + throw new RuntimeException(MessageFormat.format(RESOURCE.getString(DriverResource.BAD_SPEC),opt, spec)); + } + } + + // Done processing specs. + if (didAction && !isError) { + continue doArgs; + } + + // The specs should have done something, but did not. + while (pbp.nextIndex() > pbpMark) { + // Remove anything pushed during these specs. + pbp.previous(); + pbp.remove(); + } + + if (isError) { + throw new IllegalArgumentException(resultString); + } + + if (optlen == 0) { + // We cannot try a shorter matching option. + break tryOpt; + } + } + + // If we come here, there was no matching option. + // So, push back the argument, and return to caller. + pbp.add(arg); + break doArgs; + } + // Report number of arguments consumed. + args.subList(0, argp.nextIndex()).clear(); + // Report any unconsumed partial argument. + while (pbp.hasPrevious()) { + args.add(0, pbp.previous()); + } + //System.out.println(args+" // "+properties+" -> "+resultString); + return resultString; + } +} diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/DriverResource.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/DriverResource.java new file mode 100644 index 000000000..fb86eaa07 --- /dev/null +++ b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/DriverResource.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2005, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package net.fabricmc.shade.com.sun.java.util.jar.pack; + +import java.util.ListResourceBundle; + +public class DriverResource extends ListResourceBundle { + + public static final String VERSION = "VERSION"; + public static final String BAD_ARGUMENT = "BAD_ARGUMENT"; + public static final String BAD_OPTION = "BAD_OPTION"; + public static final String BAD_REPACK_OUTPUT = "BAD_REPACK_OUTPUT"; + public static final String DETECTED_ZIP_COMMENT = "DETECTED_ZIP_COMMENT"; + public static final String SKIP_FOR_REPACKED = "SKIP_FOR_REPACKED"; + public static final String WRITE_PACK_FILE = "WRITE_PACK_FILE"; + public static final String WRITE_PACKGZ_FILE = "WRITE_PACKGZ_FILE"; + public static final String SKIP_FOR_MOVE_FAILED = "SKIP_FOR_MOVE_FAILED"; + public static final String PACK_HELP = "PACK_HELP"; + public static final String UNPACK_HELP = "UNPACK_HELP"; + public static final String MORE_INFO = "MORE_INFO"; + public static final String DUPLICATE_OPTION = "DUPLICATE_OPTION"; + public static final String BAD_SPEC = "BAD_SPEC"; + public static final String DEPRECATED = "DEPRECATED"; + + /* + * The following are the output of 'pack200' and 'unpack200' commands. + * Do not translate command arguments and words with a prefix of '-' or '--'. + */ + private static final Object[][] resource = { + {VERSION, "{0} version {1}"}, // parameter 0:class name;parameter 1: version value + {BAD_ARGUMENT, "Bad argument: {0}"}, + {BAD_OPTION, "Bad option: {0}={1}"}, // parameter 0:option name;parameter 1:option value + {BAD_REPACK_OUTPUT, "Bad --repack output: {0}"}, // parameter 0:filename + {DETECTED_ZIP_COMMENT, "Detected ZIP comment: {0}"}, // parameter 0:comment + {SKIP_FOR_REPACKED, "Skipping because already repacked: {0}"}, // parameter 0:filename + {WRITE_PACK_FILE, "To write a *.pack file, specify --no-gzip: {0}"}, // parameter 0:filename + {WRITE_PACKGZ_FILE, "To write a *.pack.gz file, specify --gzip: {0}"}, // parameter 0:filename + {SKIP_FOR_MOVE_FAILED, "Skipping unpack because move failed: {0}"}, // parameter 0:filename + {PACK_HELP, new String[] { + "Usage: pack200 [-opt... | --option=value]... x.pack[.gz] y.jar", + "", + "Packing Options", + " -r, --repack repack or normalize a jar, suitable for ", + " signing with jarsigner", + " -g, --no-gzip output a plain pack file, suitable to be", + " compressed with a file compression utility", + " --gzip (default) post compress the pack output", + " with gzip", + " -G, --strip-debug remove debugging attributes (SourceFile,", + " LineNumberTable, LocalVariableTable", + " and LocalVariableTypeTable) while packing", + " -O, --no-keep-file-order do not transmit file ordering information", + " --keep-file-order (default) preserve input file ordering", + " -S{N}, --segment-limit={N} limit segment sizes (default unlimited)", + " -E{N}, --effort={N} packing effort (default N=5)", + " -H{h}, --deflate-hint={h} transmit deflate hint: true, false,", + " or keep (default)", + " -m{V}, --modification-time={V} transmit modtimes: latest or keep (default)", + " -P{F}, --pass-file={F} transmit the given input element(s) unchanged", + " -U{a}, --unknown-attribute={a} unknown attribute action: error, strip,", + " or pass (default)", + " -C{N}={L}, --class-attribute={N}={L} (user-defined attribute)", + " -F{N}={L}, --field-attribute={N}={L} (user-defined attribute)", + " -M{N}={L}, --method-attribute={N}={L} (user-defined attribute)", + " -D{N}={L}, --code-attribute={N}={L} (user-defined attribute)", + " -f{F}, --config-file={F} read file F for Pack200.Packer properties", + " -v, --verbose increase program verbosity", + " -q, --quiet set verbosity to lowest level", + " -l{F}, --log-file={F} output to the given log file, ", + " or '-' for System.out", + " -?, -h, --help print this help message", + " -V, --version print program version", + " -J{X} pass option X to underlying Java VM", + "", + "Notes:", + " The -P, -C, -F, -M, and -D options accumulate.", + " Example attribute definition: -C SourceFile=RUH .", + " Config. file properties are defined by the Pack200 API.", + " For meaning of -S, -E, -H-, -m, -U values, see Pack200 API.", + " Layout definitions (like RUH) are defined by JSR 200.", + "", + "Repacking mode updates the JAR file with a pack/unpack cycle:", + " pack200 [-r|--repack] [-opt | --option=value]... [repackedy.jar] y.jar\n", + "", + "Exit Status:", + " 0 if successful, >0 if an error occurred" + } + }, + {UNPACK_HELP, new String[] { + "Usage: unpack200 [-opt... | --option=value]... x.pack[.gz] y.jar\n", + "", + "Unpacking Options", + " -H{h}, --deflate-hint={h} override transmitted deflate hint:", + " true, false, or keep (default)", + " -r, --remove-pack-file remove input file after unpacking", + " -v, --verbose increase program verbosity", + " -q, --quiet set verbosity to lowest level", + " -l{F}, --log-file={F} output to the given log file, or", + " '-' for System.out", + " -?, -h, --help print this help message", + " -V, --version print program version", + " -J{X} pass option X to underlying Java VM" + } + }, + {MORE_INFO, "(For more information, run {0} --help .)"}, // parameter 0:command name + {DUPLICATE_OPTION, "duplicate option: {0}"}, // parameter 0:option + {BAD_SPEC, "bad spec for {0}: {1}"}, // parameter 0:option;parameter 1:specifier + {DEPRECATED, "\nWarning: The {0} tool is deprecated, and is planned for removal in a future JDK release.\n"} // parameter 0:command name + }; + + protected Object[][] getContents() { + return resource; + } +} diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/DriverResource_ja.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/DriverResource_ja.java new file mode 100644 index 000000000..5c0f52323 --- /dev/null +++ b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/DriverResource_ja.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2005, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package net.fabricmc.shade.com.sun.java.util.jar.pack; + +import java.util.ListResourceBundle; + +public class DriverResource_ja extends ListResourceBundle { + + public static final String VERSION = "VERSION"; + public static final String BAD_ARGUMENT = "BAD_ARGUMENT"; + public static final String BAD_OPTION = "BAD_OPTION"; + public static final String BAD_REPACK_OUTPUT = "BAD_REPACK_OUTPUT"; + public static final String DETECTED_ZIP_COMMENT = "DETECTED_ZIP_COMMENT"; + public static final String SKIP_FOR_REPACKED = "SKIP_FOR_REPACKED"; + public static final String WRITE_PACK_FILE = "WRITE_PACK_FILE"; + public static final String WRITE_PACKGZ_FILE = "WRITE_PACKGZ_FILE"; + public static final String SKIP_FOR_MOVE_FAILED = "SKIP_FOR_MOVE_FAILED"; + public static final String PACK_HELP = "PACK_HELP"; + public static final String UNPACK_HELP = "UNPACK_HELP"; + public static final String MORE_INFO = "MORE_INFO"; + public static final String DUPLICATE_OPTION = "DUPLICATE_OPTION"; + public static final String BAD_SPEC = "BAD_SPEC"; + public static final String DEPRECATED = "DEPRECATED"; + + /* + * The following are the output of 'pack200' and 'unpack200' commands. + * Do not translate command arguments and words with a prefix of '-' or '--'. + */ + private static final Object[][] resource = { + {VERSION, "{0}\u30D0\u30FC\u30B8\u30E7\u30F3{1}"}, // parameter 0:class name;parameter 1: version value + {BAD_ARGUMENT, "\u7121\u52B9\u306A\u5F15\u6570: {0}"}, + {BAD_OPTION, "\u7121\u52B9\u306A\u30AA\u30D7\u30B7\u30E7\u30F3: {0}={1}"}, // parameter 0:option name;parameter 1:option value + {BAD_REPACK_OUTPUT, "\u7121\u52B9\u306A--repack\u51FA\u529B: {0}"}, // parameter 0:filename + {DETECTED_ZIP_COMMENT, "\u691C\u51FA\u3055\u308C\u305FZIP\u30B3\u30E1\u30F3\u30C8: {0}"}, // parameter 0:comment + {SKIP_FOR_REPACKED, "\u3059\u3067\u306B\u518D\u5727\u7E2E\u3055\u308C\u3066\u3044\u308B\u305F\u3081\u30B9\u30AD\u30C3\u30D7\u3057\u3066\u3044\u307E\u3059: {0}"}, // parameter 0:filename + {WRITE_PACK_FILE, "*.pack\u30D5\u30A1\u30A4\u30EB\u3092\u66F8\u304D\u8FBC\u3080\u306B\u306F\u3001--no-gzip\u3092\u6307\u5B9A\u3057\u307E\u3059: {0}"}, // parameter 0:filename + {WRITE_PACKGZ_FILE, "*.pack.gz\u30D5\u30A1\u30A4\u30EB\u3092\u66F8\u304D\u8FBC\u3080\u306B\u306F\u3001--gzip\u3092\u6307\u5B9A\u3057\u307E\u3059: {0}"}, // parameter 0:filename + {SKIP_FOR_MOVE_FAILED, "\u79FB\u52D5\u304C\u5931\u6557\u3057\u305F\u305F\u3081\u89E3\u51CD\u3092\u30B9\u30AD\u30C3\u30D7\u3057\u3066\u3044\u307E\u3059: {0}"}, // parameter 0:filename + {PACK_HELP, new String[] { + "\u4F7F\u7528\u65B9\u6CD5: pack200 [-opt... | --option=value]... x.pack[.gz] y.jar", + "", + "\u5727\u7E2E\u30AA\u30D7\u30B7\u30E7\u30F3", + " -r\u3001--repack jar\u3092\u518D\u5727\u7E2E\u307E\u305F\u306F\u6B63\u898F\u5316\u3059\u308B\u30AA\u30D7\u30B7\u30E7\u30F3\u3067\u3001", + " jarsigner\u306B\u3088\u308B\u7F72\u540D\u306B\u9069\u3057\u307E\u3059", + " -g\u3001--no-gzip \u30D7\u30EC\u30FC\u30F3\u306Apack\u30D5\u30A1\u30A4\u30EB\u3092\u51FA\u529B\u3059\u308B\u30AA\u30D7\u30B7\u30E7\u30F3\u3067\u3001", + " \u30D5\u30A1\u30A4\u30EB\u5727\u7E2E\u30E6\u30FC\u30C6\u30A3\u30EA\u30C6\u30A3\u306B\u3088\u308B\u5727\u7E2E\u306B\u9069\u3057\u307E\u3059", + " --gzip (\u30C7\u30D5\u30A9\u30EB\u30C8) pack\u51FA\u529B\u3092\u5F8C\u51E6\u7406\u3067\u5727\u7E2E\u3057\u307E\u3059", + " (gzip\u3092\u4F7F\u7528)", + " -G\u3001--strip-debug \u5727\u7E2E\u4E2D\u306B\u30C7\u30D0\u30C3\u30B0\u5C5E\u6027(SourceFile\u3001", + " LineNumberTable\u3001LocalVariableTable", + " \u3001LocalVariableTypeTable)\u3092\u524A\u9664\u3057\u307E\u3059", + " -O\u3001--no-keep-file-order \u30D5\u30A1\u30A4\u30EB\u306E\u9806\u5E8F\u4ED8\u3051\u60C5\u5831\u3092\u8EE2\u9001\u3057\u307E\u305B\u3093", + " --keep-file-order (\u30C7\u30D5\u30A9\u30EB\u30C8)\u5165\u529B\u30D5\u30A1\u30A4\u30EB\u306E\u9806\u5E8F\u4ED8\u3051\u3092\u4FDD\u6301\u3057\u307E\u3059", + " -S{N}\u3001--segment-limit={N} \u30BB\u30B0\u30E1\u30F3\u30C8\u30FB\u30B5\u30A4\u30BA\u3092\u5236\u9650\u3057\u307E\u3059(\u30C7\u30D5\u30A9\u30EB\u30C8\u306F\u7121\u5236\u9650)", + " -E{N}\u3001--effort={N} \u5727\u7E2E\u306E\u8A66\u884C(\u30C7\u30D5\u30A9\u30EB\u30C8N=5)", + " -H{h}\u3001--deflate-hint={h} \u30C7\u30D5\u30EC\u30FC\u30C8\u30FB\u30D2\u30F3\u30C8\u3092\u8EE2\u9001\u3057\u307E\u3059: true\u3001false", + " \u307E\u305F\u306Fkeep(\u30C7\u30D5\u30A9\u30EB\u30C8)", + " -m{V}\u3001--modification-time={V} \u5909\u66F4\u6642\u9593\u3092\u8EE2\u9001\u3057\u307E\u3059: latest\u307E\u305F\u306Fkeep(\u30C7\u30D5\u30A9\u30EB\u30C8)", + " -P{F}\u3001--pass-file={F} \u6307\u5B9A\u3055\u308C\u305F\u5165\u529B\u8981\u7D20\u3092\u305D\u306E\u307E\u307E\u8EE2\u9001\u3057\u307E\u3059", + " -U{a}\u3001--unknown-attribute={a} \u4E0D\u660E\u306E\u5C5E\u6027\u30A2\u30AF\u30B7\u30E7\u30F3: error\u3001strip", + " \u307E\u305F\u306Fpass(\u30C7\u30D5\u30A9\u30EB\u30C8)", + " -C{N}={L}\u3001--class-attribute={N}={L} (\u30E6\u30FC\u30B6\u30FC\u5B9A\u7FA9\u5C5E\u6027)", + " -F{N}={L}\u3001--field-attribute={N}={L} (\u30E6\u30FC\u30B6\u30FC\u5B9A\u7FA9\u5C5E\u6027)", + " -M{N}={L}\u3001--method-attribute={N}={L} (\u30E6\u30FC\u30B6\u30FC\u5B9A\u7FA9\u5C5E\u6027)", + " -D{N}={L}\u3001--code-attribute={N}={L} (\u30E6\u30FC\u30B6\u30FC\u5B9A\u7FA9\u5C5E\u6027)", + " -f{F}\u3001--config-file={F} Pack200.Packer\u30D7\u30ED\u30D1\u30C6\u30A3\u306B\u30D5\u30A1\u30A4\u30EBF\u3092\u8AAD\u307F\u8FBC\u307F\u307E\u3059", + " -v\u3001--verbose \u30D7\u30ED\u30B0\u30E9\u30E0\u306E\u5197\u9577\u6027\u3092\u9AD8\u3081\u307E\u3059", + " -q\u3001--quiet \u5197\u9577\u6027\u3092\u6700\u4F4E\u30EC\u30D9\u30EB\u306B\u8A2D\u5B9A\u3057\u307E\u3059", + " -l{F}\u3001--log-file={F} \u6307\u5B9A\u306E\u30ED\u30B0\u30FB\u30D5\u30A1\u30A4\u30EB\u307E\u305F\u306FSystem.out ", + " ('-'\u306E\u5834\u5408)\u306B\u51FA\u529B\u3057\u307E\u3059", + " -?\u3001-h\u3001--help \u3053\u306E\u30D8\u30EB\u30D7\u30FB\u30E1\u30C3\u30BB\u30FC\u30B8\u3092\u51FA\u529B\u3057\u307E\u3059", + " -V\u3001--version \u30D7\u30ED\u30B0\u30E9\u30E0\u306E\u30D0\u30FC\u30B8\u30E7\u30F3\u3092\u51FA\u529B\u3057\u307E\u3059", + " -J{X} \u30AA\u30D7\u30B7\u30E7\u30F3X\u3092\u57FA\u790E\u3068\u306A\u308BJava VM\u306B\u6E21\u3057\u307E\u3059", + "", + "\u6CE8:", + " -P\u3001-C\u3001-F\u3001-M\u304A\u3088\u3073-D\u30AA\u30D7\u30B7\u30E7\u30F3\u306F\u7D2F\u7A4D\u3055\u308C\u307E\u3059\u3002", + " \u5C5E\u6027\u5B9A\u7FA9\u306E\u4F8B: -C SourceFile=RUH .", + " Config.\u30D5\u30A1\u30A4\u30EB\u30FB\u30D7\u30ED\u30D1\u30C6\u30A3\u306F\u3001Pack200 API\u306B\u3088\u3063\u3066\u5B9A\u7FA9\u3055\u308C\u307E\u3059\u3002", + " -S\u3001-E\u3001-H\u3001-m\u3001-U\u306E\u5024\u306E\u610F\u5473\u306F\u3001Pack200 API\u3092\u53C2\u7167\u3057\u3066\u304F\u3060\u3055\u3044\u3002", + " \u30EC\u30A4\u30A2\u30A6\u30C8\u5B9A\u7FA9(RUH\u306A\u3069)\u306FJSR 200\u306B\u3088\u3063\u3066\u5B9A\u7FA9\u3055\u308C\u307E\u3059\u3002", + "", + "\u518D\u5727\u7E2E\u30E2\u30FC\u30C9\u3067\u306F\u3001JAR\u30D5\u30A1\u30A4\u30EB\u304C\u5727\u7E2E/\u89E3\u51CD\u30B5\u30A4\u30AF\u30EB\u3067\u66F4\u65B0\u3055\u308C\u307E\u3059:", + " pack200 [-r|--repack] [-opt | --option=value]... [repackedy.jar] y.jar\n", + "", + "\u7D42\u4E86\u30B9\u30C6\u30FC\u30BF\u30B9:", + " 0 (\u6210\u529F\u3057\u305F\u5834\u5408)\u3001>0 (\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u305F\u5834\u5408)" + } + }, + {UNPACK_HELP, new String[] { + "\u4F7F\u7528\u65B9\u6CD5: unpack200 [-opt... | --option=value]... x.pack[.gz] y.jar\n", + "", + "\u89E3\u51CD\u30AA\u30D7\u30B7\u30E7\u30F3", + " -H{h}\u3001--deflate-hint={h} \u8EE2\u9001\u3055\u308C\u305F\u30C7\u30D5\u30EC\u30FC\u30C8\u30FB\u30D2\u30F3\u30C8\u3092\u30AA\u30FC\u30D0\u30FC\u30E9\u30A4\u30C9\u3057\u307E\u3059:", + " true\u3001false\u307E\u305F\u306Fkeep(\u30C7\u30D5\u30A9\u30EB\u30C8)", + " -r\u3001--remove-pack-file \u89E3\u51CD\u5F8C\u306B\u5165\u529B\u30D5\u30A1\u30A4\u30EB\u3092\u524A\u9664\u3057\u307E\u3059", + " -v\u3001--verbose \u30D7\u30ED\u30B0\u30E9\u30E0\u306E\u5197\u9577\u6027\u3092\u9AD8\u3081\u307E\u3059", + " -q\u3001--quiet \u5197\u9577\u6027\u3092\u6700\u4F4E\u30EC\u30D9\u30EB\u306B\u8A2D\u5B9A\u3057\u307E\u3059", + " -l{F}\u3001--log-file={F} \u6307\u5B9A\u306E\u30ED\u30B0\u30FB\u30D5\u30A1\u30A4\u30EB\u307E\u305F\u306F", + " System.out ('-'\u306E\u5834\u5408)\u306B\u51FA\u529B\u3057\u307E\u3059", + " -?\u3001-h\u3001--help \u3053\u306E\u30D8\u30EB\u30D7\u30FB\u30E1\u30C3\u30BB\u30FC\u30B8\u3092\u51FA\u529B\u3057\u307E\u3059", + " -V\u3001--version \u30D7\u30ED\u30B0\u30E9\u30E0\u306E\u30D0\u30FC\u30B8\u30E7\u30F3\u3092\u51FA\u529B\u3057\u307E\u3059", + " -J{X} \u30AA\u30D7\u30B7\u30E7\u30F3X\u3092\u57FA\u790E\u3068\u306A\u308BJava VM\u306B\u6E21\u3057\u307E\u3059" + } + }, + {MORE_INFO, "(\u8A73\u7D30\u306F\u3001{0} --help\u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002)"}, // parameter 0:command name + {DUPLICATE_OPTION, "\u91CD\u8907\u30AA\u30D7\u30B7\u30E7\u30F3: {0}"}, // parameter 0:option + {BAD_SPEC, "{0}\u306E\u7121\u52B9\u306A\u4ED5\u69D8: {1}"}, // parameter 0:option;parameter 1:specifier + {DEPRECATED, "\n\u8B66\u544A: {0}\u30C4\u30FC\u30EB\u306F\u975E\u63A8\u5968\u3067\u3042\u308A\u3001\u4ECA\u5F8C\u306EJDK\u30EA\u30EA\u30FC\u30B9\u3067\u524A\u9664\u3055\u308C\u308B\u4E88\u5B9A\u3067\u3059\u3002\n"} // parameter 0:command name + }; + + protected Object[][] getContents() { + return resource; + } +} diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/DriverResource_zh_CN.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/DriverResource_zh_CN.java new file mode 100644 index 000000000..96b71846d --- /dev/null +++ b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/DriverResource_zh_CN.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2005, 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package net.fabricmc.shade.com.sun.java.util.jar.pack; + +import java.util.ListResourceBundle; + +public class DriverResource_zh_CN extends ListResourceBundle { + + public static final String VERSION = "VERSION"; + public static final String BAD_ARGUMENT = "BAD_ARGUMENT"; + public static final String BAD_OPTION = "BAD_OPTION"; + public static final String BAD_REPACK_OUTPUT = "BAD_REPACK_OUTPUT"; + public static final String DETECTED_ZIP_COMMENT = "DETECTED_ZIP_COMMENT"; + public static final String SKIP_FOR_REPACKED = "SKIP_FOR_REPACKED"; + public static final String WRITE_PACK_FILE = "WRITE_PACK_FILE"; + public static final String WRITE_PACKGZ_FILE = "WRITE_PACKGZ_FILE"; + public static final String SKIP_FOR_MOVE_FAILED = "SKIP_FOR_MOVE_FAILED"; + public static final String PACK_HELP = "PACK_HELP"; + public static final String UNPACK_HELP = "UNPACK_HELP"; + public static final String MORE_INFO = "MORE_INFO"; + public static final String DUPLICATE_OPTION = "DUPLICATE_OPTION"; + public static final String BAD_SPEC = "BAD_SPEC"; + public static final String DEPRECATED = "DEPRECATED"; + + /* + * The following are the output of 'pack200' and 'unpack200' commands. + * Do not translate command arguments and words with a prefix of '-' or '--'. + */ + private static final Object[][] resource = { + {VERSION, "{0}\u7248\u672C {1}"}, // parameter 0:class name;parameter 1: version value + {BAD_ARGUMENT, "\u9519\u8BEF\u53C2\u6570: {0}"}, + {BAD_OPTION, "\u9519\u8BEF\u9009\u9879: {0}={1}"}, // parameter 0:option name;parameter 1:option value + {BAD_REPACK_OUTPUT, "--repack \u8F93\u51FA\u9519\u8BEF: {0}"}, // parameter 0:filename + {DETECTED_ZIP_COMMENT, "\u68C0\u6D4B\u5230 ZIP \u6CE8\u91CA: {0}"}, // parameter 0:comment + {SKIP_FOR_REPACKED, "\u7531\u4E8E\u5DF2\u91CD\u65B0\u6253\u5305\u800C\u8DF3\u8FC7: {0}"}, // parameter 0:filename + {WRITE_PACK_FILE, "\u8981\u5199\u5165 *.pack \u6587\u4EF6, \u8BF7\u6307\u5B9A --no-gzip: {0}"}, // parameter 0:filename + {WRITE_PACKGZ_FILE, "\u8981\u5199\u5165 *.pack.gz \u6587\u4EF6, \u8BF7\u6307\u5B9A --gzip: {0}"}, // parameter 0:filename + {SKIP_FOR_MOVE_FAILED, "\u7531\u4E8E\u79FB\u52A8\u5931\u8D25\u800C\u8DF3\u8FC7\u89E3\u5305: {0}"}, // parameter 0:filename + {PACK_HELP, new String[] { + "\u7528\u6CD5: pack200 [-opt... | --option=value]... x.pack[.gz] y.jar", + "", + "\u6253\u5305\u9009\u9879", + " -r, --repack \u518D\u6B21\u6253\u5305\u6216\u89C4\u8303\u5316 jar, \u9002\u5408\u4E8E ", + " \u4F7F\u7528 jarsigner \u8FDB\u884C\u7B7E\u540D", + " -g, --no-gzip \u8F93\u51FA\u65E0\u683C\u5F0F\u7684\u5305\u6587\u4EF6, \u9002\u5408\u4E8E", + " \u4F7F\u7528\u6587\u4EF6\u538B\u7F29\u5B9E\u7528\u7A0B\u5E8F\u8FDB\u884C\u538B\u7F29", + " --gzip (\u9ED8\u8BA4\u503C) \u4F7F\u7528 gzip \u5BF9\u5305\u8F93\u51FA\u8FDB\u884C", + " \u538B\u7F29\u540E\u5904\u7406", + " -G, --strip-debug \u6253\u5305\u65F6\u5220\u9664\u8C03\u8BD5\u5C5E\u6027 (SourceFile,", + " LineNumberTable, LocalVariableTable", + " \u548C LocalVariableTypeTable)", + " -O, --no-keep-file-order \u4E0D\u4F20\u8F93\u6587\u4EF6\u6392\u5E8F\u4FE1\u606F", + " --keep-file-order (\u9ED8\u8BA4\u503C) \u4FDD\u7559\u8F93\u5165\u6587\u4EF6\u6392\u5E8F", + " -S{N}, --segment-limit={N} \u9650\u5236\u6BB5\u5927\u5C0F (\u9ED8\u8BA4\u4E3A\u65E0\u9650\u5236)", + " -E{N}, --effort={N} \u6253\u5305\u6548\u679C (\u9ED8\u8BA4\u503C N=5)", + " -H{h}, --deflate-hint={h} \u4F20\u8F93\u538B\u7F29\u63D0\u793A: true, false", + " \u6216 keep (\u9ED8\u8BA4\u503C)", + " -m{V}, --modification-time={V} \u4F20\u8F93 modtimes: latest \u6216 keep (\u9ED8\u8BA4\u503C)", + " -P{F}, --pass-file={F} \u4F20\u8F93\u672A\u66F4\u6539\u7684\u7ED9\u5B9A\u8F93\u5165\u5143\u7D20", + " -U{a}, --unknown-attribute={a} \u672A\u77E5\u5C5E\u6027\u64CD\u4F5C: error, strip", + " \u6216 pass (\u9ED8\u8BA4\u503C)", + " -C{N}={L}, --class-attribute={N}={L} (\u7528\u6237\u5B9A\u4E49\u7684\u5C5E\u6027)", + " -F{N}={L}, --field-attribute={N}={L} (\u7528\u6237\u5B9A\u4E49\u7684\u5C5E\u6027)", + " -M{N}={L}, --method-attribute={N}={L} (\u7528\u6237\u5B9A\u4E49\u7684\u5C5E\u6027)", + " -D{N}={L}, --code-attribute={N}={L} (\u7528\u6237\u5B9A\u4E49\u7684\u5C5E\u6027)", + " -f{F}, --config-file={F} \u8BFB\u53D6\u6587\u4EF6 F \u7684 Pack200.Packer \u5C5E\u6027", + " -v, --verbose \u63D0\u9AD8\u7A0B\u5E8F\u8BE6\u7EC6\u7A0B\u5EA6", + " -q, --quiet \u5C06\u8BE6\u7EC6\u7A0B\u5EA6\u8BBE\u7F6E\u4E3A\u6700\u4F4E\u7EA7\u522B", + " -l{F}, --log-file={F} \u8F93\u51FA\u5230\u7ED9\u5B9A\u65E5\u5FD7\u6587\u4EF6, ", + " \u6216\u5BF9\u4E8E System.out \u6307\u5B9A '-'", + " -?, -h, --help \u8F93\u51FA\u6B64\u5E2E\u52A9\u6D88\u606F", + " -V, --version \u8F93\u51FA\u7A0B\u5E8F\u7248\u672C", + " -J{X} \u5C06\u9009\u9879 X \u4F20\u9012\u7ED9\u57FA\u7840 Java VM", + "", + "\u6CE8:", + " -P, -C, -F, -M \u548C -D \u9009\u9879\u7D2F\u8BA1\u3002", + " \u793A\u4F8B\u5C5E\u6027\u5B9A\u4E49: -C SourceFile=RUH\u3002", + " Config. \u6587\u4EF6\u5C5E\u6027\u7531 Pack200 API \u5B9A\u4E49\u3002", + " \u6709\u5173 -S, -E, -H-, -m, -U \u503C\u7684\u542B\u4E49, \u8BF7\u53C2\u9605 Pack200 API\u3002", + " \u5E03\u5C40\u5B9A\u4E49 (\u4F8B\u5982 RUH) \u7531 JSR 200 \u5B9A\u4E49\u3002", + "", + "\u91CD\u65B0\u6253\u5305\u6A21\u5F0F\u901A\u8FC7\u6253\u5305/\u89E3\u5305\u5468\u671F\u66F4\u65B0 JAR \u6587\u4EF6:", + " pack200 [-r|--repack] [-opt | --option=value]... [repackedy.jar] y.jar\n", + "", + "\u9000\u51FA\u72B6\u6001:", + " \u5982\u679C\u6210\u529F\u5219\u4E3A 0; \u5982\u679C\u51FA\u73B0\u9519\u8BEF, \u5219\u4E3A\u5927\u4E8E 0 \u7684\u503C" + } + }, + {UNPACK_HELP, new String[] { + "\u7528\u6CD5: unpack200 [-opt... | --option=value]... x.pack[.gz] y.jar\n", + "", + "\u89E3\u5305\u9009\u9879", + " -H{h}, --deflate-hint={h} \u8986\u76D6\u5DF2\u4F20\u8F93\u7684\u538B\u7F29\u63D0\u793A:", + " true, false \u6216 keep (\u9ED8\u8BA4\u503C)", + " -r, --remove-pack-file \u89E3\u5305\u4E4B\u540E\u5220\u9664\u8F93\u5165\u6587\u4EF6", + " -v, --verbose \u63D0\u9AD8\u7A0B\u5E8F\u8BE6\u7EC6\u7A0B\u5EA6", + " -q, --quiet \u5C06\u8BE6\u7EC6\u7A0B\u5EA6\u8BBE\u7F6E\u4E3A\u6700\u4F4E\u7EA7\u522B", + " -l{F}, --log-file={F} \u8F93\u51FA\u5230\u7ED9\u5B9A\u65E5\u5FD7\u6587\u4EF6, \u6216", + " \u5BF9\u4E8E System.out \u6307\u5B9A '-'", + " -?, -h, --help \u8F93\u51FA\u6B64\u5E2E\u52A9\u6D88\u606F", + " -V, --version \u8F93\u51FA\u7A0B\u5E8F\u7248\u672C", + " -J{X} \u5C06\u9009\u9879 X \u4F20\u9012\u7ED9\u57FA\u7840 Java VM" + } + }, + {MORE_INFO, "(\u6709\u5173\u8BE6\u7EC6\u4FE1\u606F, \u8BF7\u8FD0\u884C {0} --help\u3002)"}, // parameter 0:command name + {DUPLICATE_OPTION, "\u91CD\u590D\u7684\u9009\u9879: {0}"}, // parameter 0:option + {BAD_SPEC, "{0}\u7684\u89C4\u8303\u9519\u8BEF: {1}"}, // parameter 0:option;parameter 1:specifier + {DEPRECATED, "\n\u8B66\u544A\uFF1A{0} \u5DE5\u5177\u5DF2\u8FC7\u65F6\uFF0C\u8BA1\u5212\u5728\u672A\u6765\u7684 JDK \u53D1\u884C\u7248\u4E2D\u5220\u9664\u3002\n"} // parameter 0:command name + }; + + protected Object[][] getContents() { + return resource; + } +} diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/FixedList.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/FixedList.java new file mode 100644 index 000000000..3e4b8e12f --- /dev/null +++ b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/FixedList.java @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package net.fabricmc.shade.com.sun.java.util.jar.pack; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; + +/* + * @author ksrini + */ + +/* + * This class provides an ArrayList implementation which has a fixed size, + * thus all the operations which modifies the size have been rendered + * inoperative. This essentially allows us to use generified array + * lists in lieu of arrays. + */ +final class FixedList implements List { + + private final ArrayList flist; + + protected FixedList(int capacity) { + flist = new ArrayList<>(capacity); + // initialize the list to null + for (int i = 0 ; i < capacity ; i++) { + flist.add(null); + } + } + @Override + public int size() { + return flist.size(); + } + + @Override + public boolean isEmpty() { + return flist.isEmpty(); + } + + @Override + public boolean contains(Object o) { + return flist.contains(o); + } + + @Override + public Iterator iterator() { + return flist.iterator(); + } + + @Override + public Object[] toArray() { + return flist.toArray(); + } + + @Override + public T[] toArray(T[] a) { + return flist.toArray(a); + } + + @Override + public boolean add(E e) throws UnsupportedOperationException { + throw new UnsupportedOperationException("operation not permitted"); + } + + @Override + public boolean remove(Object o) throws UnsupportedOperationException { + throw new UnsupportedOperationException("operation not permitted"); + } + + @Override + public boolean containsAll(Collection c) { + return flist.containsAll(c); + } + + @Override + public boolean addAll(Collection c) throws UnsupportedOperationException { + throw new UnsupportedOperationException("operation not permitted"); + } + + @Override + public boolean addAll(int index, Collection c) throws UnsupportedOperationException { + throw new UnsupportedOperationException("operation not permitted"); + } + + @Override + public boolean removeAll(Collection c) throws UnsupportedOperationException { + throw new UnsupportedOperationException("operation not permitted"); + } + + @Override + public boolean retainAll(Collection c) throws UnsupportedOperationException { + throw new UnsupportedOperationException("operation not permitted"); + } + + @Override + public void clear() throws UnsupportedOperationException { + throw new UnsupportedOperationException("operation not permitted"); + } + + @Override + public E get(int index) { + return flist.get(index); + } + + @Override + public E set(int index, E element) { + return flist.set(index, element); + } + + @Override + public void add(int index, E element) throws UnsupportedOperationException { + throw new UnsupportedOperationException("operation not permitted"); + } + + @Override + public E remove(int index) throws UnsupportedOperationException { + throw new UnsupportedOperationException("operation not permitted"); + } + + @Override + public int indexOf(Object o) { + return flist.indexOf(o); + } + + @Override + public int lastIndexOf(Object o) { + return flist.lastIndexOf(o); + } + + @Override + public ListIterator listIterator() { + return flist.listIterator(); + } + + @Override + public ListIterator listIterator(int index) { + return flist.listIterator(index); + } + + @Override + public List subList(int fromIndex, int toIndex) { + return flist.subList(fromIndex, toIndex); + } + + @Override + public String toString() { + return "FixedList{" + "plist=" + flist + '}'; + } +} + diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Fixups.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Fixups.java new file mode 100644 index 000000000..726c94d9a --- /dev/null +++ b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Fixups.java @@ -0,0 +1,570 @@ +/* + * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package net.fabricmc.shade.com.sun.java.util.jar.pack; + +import net.fabricmc.shade.com.sun.java.util.jar.pack.ConstantPool.Entry; +import java.util.AbstractCollection; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.Objects; + +/** + * Collection of relocatable constant pool references. + * It operates with respect to a particular byte array, + * and stores some of its state in the bytes themselves. + *

+ * As a Collection, it can be iterated over, but it is not a List, + * since it does not natively support indexed access. + *

+ * + * @author John Rose + */ +final class Fixups extends AbstractCollection { + byte[] bytes; // the subject of the relocations + int head; // desc locating first reloc + int tail; // desc locating last reloc + int size; // number of relocations + Entry[] entries; // [0..size-1] relocations + int[] bigDescs; // descs which cannot be stored in the bytes + + // A "desc" (descriptor) is a bit-encoded pair of a location + // and format. Every fixup occurs at a "desc". Until final + // patching, bytes addressed by descs may also be used to + // link this data structure together. If the bytes are missing, + // or if the "desc" is too large to encode in the bytes, + // it is kept in the bigDescs array. + + Fixups(byte[] bytes) { + this.bytes = bytes; + entries = new Entry[3]; + bigDescs = noBigDescs; + } + Fixups() { + // If there are no bytes, all descs are kept in bigDescs. + this((byte[])null); + } + Fixups(byte[] bytes, Collection fixups) { + this(bytes); + addAll(fixups); + } + Fixups(Collection fixups) { + this((byte[])null); + addAll(fixups); + } + + private static final int MINBIGSIZE = 1; + // cleverly share empty bigDescs: + private static final int[] noBigDescs = {MINBIGSIZE}; + + @Override + public int size() { + return size; + } + + public void trimToSize() { + if (size != entries.length) { + Entry[] oldEntries = entries; + entries = new Entry[size]; + System.arraycopy(oldEntries, 0, entries, 0, size); + } + int bigSize = bigDescs[BIGSIZE]; + if (bigSize == MINBIGSIZE) { + bigDescs = noBigDescs; + } else if (bigSize != bigDescs.length) { + int[] oldBigDescs = bigDescs; + bigDescs = new int[bigSize]; + System.arraycopy(oldBigDescs, 0, bigDescs, 0, bigSize); + } + } + + public void visitRefs(Collection refs) { + for (int i = 0; i < size; i++) { + refs.add(entries[i]); + } + } + + @Override + public void clear() { + if (bytes != null) { + // Clean the bytes: + for (Fixup fx : this) { + //System.out.println("clean "+fx); + storeIndex(fx.location(), fx.format(), 0); + } + } + size = 0; + if (bigDescs != noBigDescs) + bigDescs[BIGSIZE] = MINBIGSIZE; + // do not trim to size, however + } + + public byte[] getBytes() { + return bytes; + } + + public void setBytes(byte[] newBytes) { + if (bytes == newBytes) return; + ArrayList old = null; + assert((old = new ArrayList<>(this)) != null); + if (bytes == null || newBytes == null) { + // One or the other representations is deficient. + // Construct a checkpoint. + ArrayList save = new ArrayList<>(this); + clear(); + bytes = newBytes; + addAll(save); + } else { + // assume newBytes is some sort of bitwise copy of the old bytes + bytes = newBytes; + } + assert(old.equals(new ArrayList<>(this))); + } + + private static final int LOC_SHIFT = 1; + private static final int FMT_MASK = 0x1; + private static final byte UNUSED_BYTE = 0; + private static final byte OVERFLOW_BYTE = -1; + // fill pointer of bigDescs array is in element [0] + private static final int BIGSIZE = 0; + + // Format values: + private static final int U2_FORMAT = 0; + private static final int U1_FORMAT = 1; + + // Special values for the static methods. + private static final int SPECIAL_LOC = 0; + private static final int SPECIAL_FMT = U2_FORMAT; + + static int fmtLen(int fmt) { return 1+(fmt-U1_FORMAT)/(U2_FORMAT-U1_FORMAT); } + static int descLoc(int desc) { return desc >>> LOC_SHIFT; } + static int descFmt(int desc) { return desc & FMT_MASK; } + static int descEnd(int desc) { return descLoc(desc) + fmtLen(descFmt(desc)); } + static int makeDesc(int loc, int fmt) { + int desc = (loc << LOC_SHIFT) | fmt; + assert(descLoc(desc) == loc); + assert(descFmt(desc) == fmt); + return desc; + } + int fetchDesc(int loc, int fmt) { + byte b1 = bytes[loc]; + assert(b1 != OVERFLOW_BYTE); + int value; + if (fmt == U2_FORMAT) { + byte b2 = bytes[loc+1]; + value = ((b1 & 0xFF) << 8) + (b2 & 0xFF); + } else { + value = (b1 & 0xFF); + } + // Stored loc field is difference between its own loc and next loc. + return value + (loc << LOC_SHIFT); + } + boolean storeDesc(int loc, int fmt, int desc) { + if (bytes == null) + return false; + int value = desc - (loc << LOC_SHIFT); + byte b1, b2; + switch (fmt) { + case U2_FORMAT: + assert(bytes[loc+0] == UNUSED_BYTE); + assert(bytes[loc+1] == UNUSED_BYTE); + b1 = (byte)(value >> 8); + b2 = (byte)(value >> 0); + if (value == (value & 0xFFFF) && b1 != OVERFLOW_BYTE) { + bytes[loc+0] = b1; + bytes[loc+1] = b2; + assert(fetchDesc(loc, fmt) == desc); + return true; + } + break; + case U1_FORMAT: + assert(bytes[loc] == UNUSED_BYTE); + b1 = (byte)value; + if (value == (value & 0xFF) && b1 != OVERFLOW_BYTE) { + bytes[loc] = b1; + assert(fetchDesc(loc, fmt) == desc); + return true; + } + break; + default: assert(false); + } + // Failure. Caller must allocate a bigDesc. + bytes[loc] = OVERFLOW_BYTE; + assert(fmt==U1_FORMAT || (bytes[loc+1]=(byte)bigDescs[BIGSIZE])!=999); + return false; + } + void storeIndex(int loc, int fmt, int value) { + storeIndex(bytes, loc, fmt, value); + } + static + void storeIndex(byte[] bytes, int loc, int fmt, int value) { + switch (fmt) { + case U2_FORMAT: + assert(value == (value & 0xFFFF)) : (value); + bytes[loc+0] = (byte)(value >> 8); + bytes[loc+1] = (byte)(value >> 0); + break; + case U1_FORMAT: + assert(value == (value & 0xFF)) : (value); + bytes[loc] = (byte)value; + break; + default: assert(false); + } + } + + void addU1(int pc, Entry ref) { + add(pc, U1_FORMAT, ref); + } + + void addU2(int pc, Entry ref) { + add(pc, U2_FORMAT, ref); + } + + /** Simple and necessary tuple to present each fixup. */ + public static + class Fixup implements Comparable { + int desc; // location and format of reloc + Entry entry; // which entry to plug into the bytes + Fixup(int desc, Entry entry) { + this.desc = desc; + this.entry = entry; + } + public Fixup(int loc, int fmt, Entry entry) { + this.desc = makeDesc(loc, fmt); + this.entry = entry; + } + public int location() { return descLoc(desc); } + public int format() { return descFmt(desc); } + public Entry entry() { return entry; } + @Override + public int compareTo(Fixup that) { + // Ordering depends only on location. + return this.location() - that.location(); + } + @Override + public boolean equals(Object x) { + if (!(x instanceof Fixup)) return false; + Fixup that = (Fixup) x; + return this.desc == that.desc && this.entry == that.entry; + } + @Override + public int hashCode() { + int hash = 7; + hash = 59 * hash + this.desc; + hash = 59 * hash + Objects.hashCode(this.entry); + return hash; + } + @Override + public String toString() { + return "@"+location()+(format()==U1_FORMAT?".1":"")+"="+entry; + } + } + + private + class Itr implements Iterator { + int index = 0; // index into entries + int bigIndex = BIGSIZE+1; // index into bigDescs + int next = head; // desc pointing to next fixup + @Override + public boolean hasNext() { return index < size; } + @Override + public void remove() { throw new UnsupportedOperationException(); } + @Override + public Fixup next() { + int thisIndex = index; + return new Fixup(nextDesc(), entries[thisIndex]); + } + int nextDesc() { + index++; + int thisDesc = next; + if (index < size) { + // Fetch next desc eagerly, in case this fixup gets finalized. + int loc = descLoc(thisDesc); + int fmt = descFmt(thisDesc); + if (bytes != null && bytes[loc] != OVERFLOW_BYTE) { + next = fetchDesc(loc, fmt); + } else { + // The unused extra byte is "asserted" to be equal to BI. + // This helps keep the overflow descs in sync. + assert(fmt==U1_FORMAT || bytes == null || bytes[loc+1]==(byte)bigIndex); + next = bigDescs[bigIndex++]; + } + } + return thisDesc; + } + } + + @Override + public Iterator iterator() { + return new Itr(); + } + public void add(int location, int format, Entry entry) { + addDesc(makeDesc(location, format), entry); + } + @Override + public boolean add(Fixup f) { + addDesc(f.desc, f.entry); + return true; + } + + @Override + public boolean addAll(Collection c) { + if (c instanceof Fixups) { + // Use knowledge of Itr structure to avoid building little structs. + Fixups that = (Fixups) c; + if (that.size == 0) return false; + if (this.size == 0 && entries.length < that.size) + growEntries(that.size); // presize exactly + Entry[] thatEntries = that.entries; + for (Itr i = that.new Itr(); i.hasNext(); ) { + int ni = i.index; + addDesc(i.nextDesc(), thatEntries[ni]); + } + return true; + } else { + return super.addAll(c); + } + } + // Here is how things get added: + private void addDesc(int thisDesc, Entry entry) { + if (entries.length == size) + growEntries(size * 2); + entries[size] = entry; + if (size == 0) { + head = tail = thisDesc; + } else { + int prevDesc = tail; + // Store new desc in previous tail. + int prevLoc = descLoc(prevDesc); + int prevFmt = descFmt(prevDesc); + int prevLen = fmtLen(prevFmt); + int thisLoc = descLoc(thisDesc); + // The collection must go in ascending order, and not overlap. + if (thisLoc < prevLoc + prevLen) + badOverlap(thisLoc); + tail = thisDesc; + if (!storeDesc(prevLoc, prevFmt, thisDesc)) { + // overflow + int bigSize = bigDescs[BIGSIZE]; + if (bigDescs.length == bigSize) + growBigDescs(); + //System.out.println("bigDescs["+bigSize+"] = "+thisDesc); + bigDescs[bigSize++] = thisDesc; + bigDescs[BIGSIZE] = bigSize; + } + } + size += 1; + } + private void badOverlap(int thisLoc) { + throw new IllegalArgumentException("locs must be ascending and must not overlap: "+thisLoc+" >> "+this); + } + + private void growEntries(int newSize) { + Entry[] oldEntries = entries; + entries = new Entry[Math.max(3, newSize)]; + System.arraycopy(oldEntries, 0, entries, 0, oldEntries.length); + } + private void growBigDescs() { + int[] oldBigDescs = bigDescs; + bigDescs = new int[oldBigDescs.length * 2]; + System.arraycopy(oldBigDescs, 0, bigDescs, 0, oldBigDescs.length); + } + + /// Static methods that optimize the use of this class. + static Object addRefWithBytes(Object f, byte[] bytes, Entry e) { + return add(f, bytes, 0, U2_FORMAT, e); + } + static Object addRefWithLoc(Object f, int loc, Entry entry) { + return add(f, null, loc, U2_FORMAT, entry); + } + private static + Object add(Object prevFixups, + byte[] bytes, int loc, int fmt, + Entry e) { + Fixups f; + if (prevFixups == null) { + if (loc == SPECIAL_LOC && fmt == SPECIAL_FMT) { + // Special convention: If the attribute has a + // U2 relocation at position zero, store the Entry + // rather than building a Fixups structure. + return e; + } + f = new Fixups(bytes); + } else if (!(prevFixups instanceof Fixups)) { + // Recognize the special convention: + Entry firstEntry = (Entry) prevFixups; + f = new Fixups(bytes); + f.add(SPECIAL_LOC, SPECIAL_FMT, firstEntry); + } else { + f = (Fixups) prevFixups; + assert(f.bytes == bytes); + } + f.add(loc, fmt, e); + return f; + } + + public static + void setBytes(Object fixups, byte[] bytes) { + if (fixups instanceof Fixups) { + Fixups f = (Fixups) fixups; + f.setBytes(bytes); + } + } + + public static + Object trimToSize(Object fixups) { + if (fixups instanceof Fixups) { + Fixups f = (Fixups) fixups; + f.trimToSize(); + if (f.size() == 0) + fixups = null; + } + return fixups; + } + + // Iterate over all the references in this set of fixups. + public static + void visitRefs(Object fixups, Collection refs) { + if (fixups == null) { + } else if (!(fixups instanceof Fixups)) { + // Special convention; see above. + refs.add((Entry) fixups); + } else { + Fixups f = (Fixups) fixups; + f.visitRefs(refs); + } + } + + // Clear out this set of fixups by replacing each reference + // by a hardcoded coding of its reference, drawn from ix. + public static + void finishRefs(Object fixups, byte[] bytes, ConstantPool.Index ix) { + if (fixups == null) + return; + if (!(fixups instanceof Fixups)) { + // Special convention; see above. + int index = ix.indexOf((Entry) fixups); + storeIndex(bytes, SPECIAL_LOC, SPECIAL_FMT, index); + return; + } + Fixups f = (Fixups) fixups; + assert(f.bytes == bytes); + f.finishRefs(ix); + } + + void finishRefs(ConstantPool.Index ix) { + if (isEmpty()) + return; + for (Fixup fx : this) { + int index = ix.indexOf(fx.entry); + //System.out.println("finish "+fx+" = "+index); + // Note that the iterator has already fetched the + // bytes we are about to overwrite. + storeIndex(fx.location(), fx.format(), index); + } + // Further iterations should do nothing: + bytes = null; // do not clean them + clear(); + } + +/* + /// Testing. + public static void main(String[] av) { + byte[] bytes = new byte[1 << 20]; + ConstantPool cp = new ConstantPool(); + Fixups f = new Fixups(bytes); + boolean isU1 = false; + int span = 3; + int nextLoc = 0; + int[] locs = new int[100]; + final int[] indexes = new int[100]; + int iptr = 1; + for (int loc = 0; loc < bytes.length; loc++) { + if (loc == nextLoc && loc+1 < bytes.length) { + int fmt = (isU1 ? U1_FORMAT : U2_FORMAT); + Entry e = ConstantPool.getUtf8Entry("L"+loc); + f.add(loc, fmt, e); + isU1 ^= true; + if (iptr < 10) { + // Make it close in. + nextLoc += fmtLen(fmt) + (iptr < 5 ? 0 : 1); + } else { + nextLoc += span; + span = (int)(span * 1.77); + } + // Here are the bytes that would have gone here: + locs[iptr] = loc; + if (fmt == U1_FORMAT) { + indexes[iptr++] = (loc & 0xFF); + } else { + indexes[iptr++] = ((loc & 0xFF) << 8) | ((loc+1) & 0xFF); + ++loc; // skip a byte + } + continue; + } + bytes[loc] = (byte)loc; + } + System.out.println("size="+f.size() + +" overflow="+(f.bigDescs[BIGSIZE]-1)); + System.out.println("Fixups: "+f); + // Test collection contents. + assert(iptr == 1+f.size()); + List l = new ArrayList(f); + Collections.sort(l); // should not change the order + if (!l.equals(new ArrayList(f))) System.out.println("** disordered"); + f.setBytes(null); + if (!l.equals(new ArrayList(f))) System.out.println("** bad set 1"); + f.setBytes(bytes); + if (!l.equals(new ArrayList(f))) System.out.println("** bad set 2"); + Fixups f3 = new Fixups(f); + if (!l.equals(new ArrayList(f3))) System.out.println("** bad set 3"); + Iterator fi = f.iterator(); + for (int i = 1; i < iptr; i++) { + Fixup fx = (Fixup) fi.next(); + if (fx.location() != locs[i]) { + System.out.println("** "+fx+" != "+locs[i]); + } + if (fx.format() == U1_FORMAT) + System.out.println(fx+" -> "+bytes[locs[i]]); + else + System.out.println(fx+" -> "+bytes[locs[i]]+" "+bytes[locs[i]+1]); + } + assert(!fi.hasNext()); + indexes[0] = 1; // like iptr + Index ix = new Index("ix") { + public int indexOf(Entry e) { + return indexes[indexes[0]++]; + } + }; + f.finishRefs(ix); + for (int loc = 0; loc < bytes.length; loc++) { + if (bytes[loc] != (byte)loc) { + System.out.println("** ["+loc+"] = "+bytes[loc]+" != "+(byte)loc); + } + } + } +//*/ +} diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Histogram.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Histogram.java new file mode 100644 index 000000000..f66433d73 --- /dev/null +++ b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Histogram.java @@ -0,0 +1,818 @@ +/* + * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package net.fabricmc.shade.com.sun.java.util.jar.pack; + +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.util.Arrays; + +/** + * Histogram derived from an integer array of events (int[]). + * @author John Rose + */ +final class Histogram { + // Compact histogram representation: 4 bytes per distinct value, + // plus 5 words per distinct count. + protected final int[][] matrix; // multi-row matrix {{counti,valueij...}} + protected final int totalWeight; // sum of all counts + + // These are created eagerly also, since that saves work. + // They cost another 8 bytes per distinct value. + protected final int[] values; // unique values, sorted by value + protected final int[] counts; // counts, same order as values + + private static final long LOW32 = (long)-1 >>> 32; + + /** Build a histogram given a sequence of values. + * To save work, the input should be sorted, but need not be. + */ + public + Histogram(int[] valueSequence) { + long[] hist2col = computeHistogram2Col(maybeSort(valueSequence)); + int[][] table = makeTable(hist2col); + values = table[0]; + counts = table[1]; + this.matrix = makeMatrix(hist2col); + this.totalWeight = valueSequence.length; + assert(assertWellFormed(valueSequence)); + } + public + Histogram(int[] valueSequence, int start, int end) { + this(sortedSlice(valueSequence, start, end)); + } + + /** Build a histogram given a compact matrix of counts and values. */ + public + Histogram(int[][] matrix) { + // sort the rows + matrix = normalizeMatrix(matrix); // clone and sort + this.matrix = matrix; + int length = 0; + int weight = 0; + for (int i = 0; i < matrix.length; i++) { + int rowLength = matrix[i].length-1; + length += rowLength; + weight += matrix[i][0] * rowLength; + } + this.totalWeight = weight; + long[] hist2col = new long[length]; + int fillp = 0; + for (int i = 0; i < matrix.length; i++) { + for (int j = 1; j < matrix[i].length; j++) { + // sort key is value, so put it in the high 32! + hist2col[fillp++] = ((long) matrix[i][j] << 32) + | (LOW32 & matrix[i][0]); + } + } + assert(fillp == hist2col.length); + Arrays.sort(hist2col); + int[][] table = makeTable(hist2col); + values = table[1]; //backwards + counts = table[0]; //backwards + assert(assertWellFormed(null)); + } + + /** Histogram of int values, reported compactly as a ragged matrix, + * indexed by descending frequency rank. + *

+ * Format of matrix: + * Each row in the matrix begins with an occurrence count, + * and continues with all int values that occur at that frequency. + *

+     *  int[][] matrix = {
+     *    { count1, value11, value12, value13, ...  },
+     *    { count2, value21, value22, ... },
+     *    ...
+     *  }
+     *  
+ * The first column of the matrix { count1, count2, ... } + * is sorted in descending order, and contains no duplicates. + * Each row of the matrix (apart from its first element) + * is sorted in ascending order, and contains no duplicates. + * That is, each sequence { valuei1, valuei2, ... } is sorted. + */ + public + int[][] getMatrix() { return matrix; } + + public + int getRowCount() { return matrix.length; } + + public + int getRowFrequency(int rn) { return matrix[rn][0]; } + + public + int getRowLength(int rn) { return matrix[rn].length-1; } + + public + int getRowValue(int rn, int vn) { return matrix[rn][vn+1]; } + + public + int getRowWeight(int rn) { + return getRowFrequency(rn) * getRowLength(rn); + } + + public + int getTotalWeight() { + return totalWeight; + } + + public + int getTotalLength() { + return values.length; + } + + /** Returns an array of all values, sorted. */ + public + int[] getAllValues() { + + return values; + } + + /** Returns an array parallel with {@link #getValues}, + * with a frequency for each value. + */ + public + int[] getAllFrequencies() { + return counts; + } + + private static double log2 = Math.log(2); + + public + int getFrequency(int value) { + int pos = Arrays.binarySearch(values, value); + if (pos < 0) return 0; + assert(values[pos] == value); + return counts[pos]; + } + + public + double getBitLength(int value) { + double prob = (double) getFrequency(value) / getTotalWeight(); + return - Math.log(prob) / log2; + } + + public + double getRowBitLength(int rn) { + double prob = (double) getRowFrequency(rn) / getTotalWeight(); + return - Math.log(prob) / log2; + } + + public + interface BitMetric { + public double getBitLength(int value); + } + private final BitMetric bitMetric = new BitMetric() { + public double getBitLength(int value) { + return Histogram.this.getBitLength(value); + } + }; + public BitMetric getBitMetric() { + return bitMetric; + } + + /** bit-length is negative entropy: -H(matrix). */ + public + double getBitLength() { + double sum = 0; + for (int i = 0; i < matrix.length; i++) { + sum += getRowBitLength(i) * getRowWeight(i); + } + assert(0.1 > Math.abs(sum - getBitLength(bitMetric))); + return sum; + } + + /** bit-length in to another coding (cross-entropy) */ + public + double getBitLength(BitMetric len) { + double sum = 0; + for (int i = 0; i < matrix.length; i++) { + for (int j = 1; j < matrix[i].length; j++) { + sum += matrix[i][0] * len.getBitLength(matrix[i][j]); + } + } + return sum; + } + + private static + double round(double x, double scale) { + return Math.round(x * scale) / scale; + } + + /** Sort rows and columns. + * Merge adjacent rows with the same key element [0]. + * Make a fresh copy of all of it. + */ + public int[][] normalizeMatrix(int[][] matrix) { + long[] rowMap = new long[matrix.length]; + for (int i = 0; i < matrix.length; i++) { + if (matrix[i].length <= 1) continue; + int count = matrix[i][0]; + if (count <= 0) continue; + rowMap[i] = (long) count << 32 | i; + } + Arrays.sort(rowMap); + int[][] newMatrix = new int[matrix.length][]; + int prevCount = -1; + int fillp1 = 0; + int fillp2 = 0; + for (int i = 0; ; i++) { + int[] row; + if (i < matrix.length) { + long rowMapEntry = rowMap[rowMap.length-i-1]; + if (rowMapEntry == 0) continue; + row = matrix[(int)rowMapEntry]; + assert(rowMapEntry>>>32 == row[0]); + } else { + row = new int[]{ -1 }; // close it off + } + if (row[0] != prevCount && fillp2 > fillp1) { + // Close off previous run. + int length = 0; + for (int p = fillp1; p < fillp2; p++) { + int[] row0 = newMatrix[p]; // previously visited row + assert(row0[0] == prevCount); + length += row0.length-1; + } + int[] row1 = new int[1+length]; // cloned & consolidated row + row1[0] = prevCount; + int rfillp = 1; + for (int p = fillp1; p < fillp2; p++) { + int[] row0 = newMatrix[p]; // previously visited row + assert(row0[0] == prevCount); + System.arraycopy(row0, 1, row1, rfillp, row0.length-1); + rfillp += row0.length-1; + } + if (!isSorted(row1, 1, true)) { + Arrays.sort(row1, 1, row1.length); + int jfillp = 2; + // Detect and squeeze out duplicates. + for (int j = 2; j < row1.length; j++) { + if (row1[j] != row1[j-1]) + row1[jfillp++] = row1[j]; + } + if (jfillp < row1.length) { + // Reallocate because of lost duplicates. + int[] newRow1 = new int[jfillp]; + System.arraycopy(row1, 0, newRow1, 0, jfillp); + row1 = newRow1; + } + } + newMatrix[fillp1++] = row1; + fillp2 = fillp1; + } + if (i == matrix.length) + break; + prevCount = row[0]; + newMatrix[fillp2++] = row; + } + assert(fillp1 == fillp2); // no unfinished business + // Now drop missing rows. + matrix = newMatrix; + if (fillp1 < matrix.length) { + newMatrix = new int[fillp1][]; + System.arraycopy(matrix, 0, newMatrix, 0, fillp1); + matrix = newMatrix; + } + return matrix; + } + + public + String[] getRowTitles(String name) { + int totalUnique = getTotalLength(); + int ltotalWeight = getTotalWeight(); + String[] histTitles = new String[matrix.length]; + int cumWeight = 0; + int cumUnique = 0; + for (int i = 0; i < matrix.length; i++) { + int count = getRowFrequency(i); + int unique = getRowLength(i); + int weight = getRowWeight(i); + cumWeight += weight; + cumUnique += unique; + long wpct = ((long)cumWeight * 100 + ltotalWeight/2) / ltotalWeight; + long upct = ((long)cumUnique * 100 + totalUnique/2) / totalUnique; + double len = getRowBitLength(i); + assert(0.1 > Math.abs(len - getBitLength(matrix[i][1]))); + histTitles[i] = name+"["+i+"]" + +" len="+round(len,10) + +" ("+count+"*["+unique+"])" + +" ("+cumWeight+":"+wpct+"%)" + +" ["+cumUnique+":"+upct+"%]"; + } + return histTitles; + } + + /** Print a report of this histogram. + */ + public + void print(PrintStream out) { + print("hist", out); + } + + /** Print a report of this histogram. + */ + public + void print(String name, PrintStream out) { + print(name, getRowTitles(name), out); + } + + /** Print a report of this histogram. + */ + public + void print(String name, String[] histTitles, PrintStream out) { + int totalUnique = getTotalLength(); + int ltotalWeight = getTotalWeight(); + double tlen = getBitLength(); + double avgLen = tlen / ltotalWeight; + double avg = (double) ltotalWeight / totalUnique; + String title = (name + +" len="+round(tlen,10) + +" avgLen="+round(avgLen,10) + +" weight("+ltotalWeight+")" + +" unique["+totalUnique+"]" + +" avgWeight("+round(avg,100)+")"); + if (histTitles == null) { + out.println(title); + } else { + out.println(title+" {"); + StringBuffer buf = new StringBuffer(); + for (int i = 0; i < matrix.length; i++) { + buf.setLength(0); + buf.append(" ").append(histTitles[i]).append(" {"); + for (int j = 1; j < matrix[i].length; j++) { + buf.append(" ").append(matrix[i][j]); + } + buf.append(" }"); + out.println(buf); + } + out.println("}"); + } + } + +/* + public static + int[][] makeHistogramMatrix(int[] values) { + // Make sure they are sorted. + values = maybeSort(values); + long[] hist2col = computeHistogram2Col(values); + int[][] matrix = makeMatrix(hist2col); + return matrix; + } +*/ + + private static + int[][] makeMatrix(long[] hist2col) { + // Sort by increasing count, then by increasing value. + Arrays.sort(hist2col); + int[] counts = new int[hist2col.length]; + for (int i = 0; i < counts.length; i++) { + counts[i] = (int)( hist2col[i] >>> 32 ); + } + long[] countHist = computeHistogram2Col(counts); + int[][] matrix = new int[countHist.length][]; + int histp = 0; // cursor into hist2col (increasing count, value) + int countp = 0; // cursor into countHist (increasing count) + // Do a join between hist2col (resorted) and countHist. + for (int i = matrix.length; --i >= 0; ) { + long countAndRep = countHist[countp++]; + int count = (int) (countAndRep); // what is the value count? + int repeat = (int) (countAndRep >>> 32); // # times repeated? + int[] row = new int[1+repeat]; + row[0] = count; + for (int j = 0; j < repeat; j++) { + long countAndValue = hist2col[histp++]; + assert(countAndValue >>> 32 == count); + row[1+j] = (int) countAndValue; + } + matrix[i] = row; + } + assert(histp == hist2col.length); + return matrix; + } + + private static + int[][] makeTable(long[] hist2col) { + int[][] table = new int[2][hist2col.length]; + // Break apart the entries in hist2col. + // table[0] gets values, table[1] gets entries. + for (int i = 0; i < hist2col.length; i++) { + table[0][i] = (int)( hist2col[i] ); + table[1][i] = (int)( hist2col[i] >>> 32 ); + } + return table; + } + + /** Simple two-column histogram. Contains repeated counts. + * Assumes input is sorted. Does not sort output columns. + *

+ * Format of result: + *

+     *  long[] hist = {
+     *    (count1 << 32) | (value1),
+     *    (count2 << 32) | (value2),
+     *    ...
+     *  }
+     *  
+ * In addition, the sequence {valuei...} is guaranteed to be sorted. + * Note that resorting this using Arrays.sort() will reorder the + * entries by increasing count. + */ + private static + long[] computeHistogram2Col(int[] sortedValues) { + switch (sortedValues.length) { + case 0: + return new long[]{ }; + case 1: + return new long[]{ ((long)1 << 32) | (LOW32 & sortedValues[0]) }; + } + long[] hist = null; + for (boolean sizeOnly = true; ; sizeOnly = false) { + int prevIndex = -1; + int prevValue = sortedValues[0] ^ -1; // force a difference + int prevCount = 0; + for (int i = 0; i <= sortedValues.length; i++) { + int thisValue; + if (i < sortedValues.length) + thisValue = sortedValues[i]; + else + thisValue = prevValue ^ -1; // force a difference at end + if (thisValue == prevValue) { + prevCount += 1; + } else { + // Found a new value. + if (!sizeOnly && prevCount != 0) { + // Save away previous value. + hist[prevIndex] = ((long)prevCount << 32) + | (LOW32 & prevValue); + } + prevValue = thisValue; + prevCount = 1; + prevIndex += 1; + } + } + if (sizeOnly) { + // Finished the sizing pass. Allocate the histogram. + hist = new long[prevIndex]; + } else { + break; // done + } + } + return hist; + } + + /** Regroup the histogram, so that it becomes an approximate histogram + * whose rows are of the given lengths. + * If matrix rows must be split, the latter parts (larger values) + * are placed earlier in the new matrix. + * If matrix rows are joined, they are resorted into ascending order. + * In the new histogram, the counts are averaged over row entries. + */ + private static + int[][] regroupHistogram(int[][] matrix, int[] groups) { + long oldEntries = 0; + for (int i = 0; i < matrix.length; i++) { + oldEntries += matrix[i].length-1; + } + long newEntries = 0; + for (int ni = 0; ni < groups.length; ni++) { + newEntries += groups[ni]; + } + if (newEntries > oldEntries) { + int newlen = groups.length; + long ok = oldEntries; + for (int ni = 0; ni < groups.length; ni++) { + if (ok < groups[ni]) { + int[] newGroups = new int[ni+1]; + System.arraycopy(groups, 0, newGroups, 0, ni+1); + groups = newGroups; + groups[ni] = (int) ok; + ok = 0; + break; + } + ok -= groups[ni]; + } + } else { + long excess = oldEntries - newEntries; + int[] newGroups = new int[groups.length+1]; + System.arraycopy(groups, 0, newGroups, 0, groups.length); + newGroups[groups.length] = (int) excess; + groups = newGroups; + } + int[][] newMatrix = new int[groups.length][]; + // Fill pointers. + int i = 0; // into matrix + int jMin = 1; + int jMax = matrix[i].length; + for (int ni = 0; ni < groups.length; ni++) { + int groupLength = groups[ni]; + int[] group = new int[1+groupLength]; + long groupWeight = 0; // count of all in new group + newMatrix[ni] = group; + int njFill = 1; + while (njFill < group.length) { + int len = group.length - njFill; + while (jMin == jMax) { + jMin = 1; + jMax = matrix[++i].length; + } + if (len > jMax - jMin) len = jMax - jMin; + groupWeight += (long) matrix[i][0] * len; + System.arraycopy(matrix[i], jMax - len, group, njFill, len); + jMax -= len; + njFill += len; + } + Arrays.sort(group, 1, group.length); + // compute average count of new group: + group[0] = (int) ((groupWeight + groupLength/2) / groupLength); + } + assert(jMin == jMax); + assert(i == matrix.length-1); + return newMatrix; + } + + public static + Histogram makeByteHistogram(InputStream bytes) throws IOException { + byte[] buf = new byte[1<<12]; + int[] tally = new int[1<<8]; + for (int nr; (nr = bytes.read(buf)) > 0; ) { + for (int i = 0; i < nr; i++) { + tally[buf[i] & 0xFF] += 1; + } + } + // Build a matrix. + int[][] matrix = new int[1<<8][2]; + for (int i = 0; i < tally.length; i++) { + matrix[i][0] = tally[i]; + matrix[i][1] = i; + } + return new Histogram(matrix); + } + + /** Slice and sort the given input array. */ + private static + int[] sortedSlice(int[] valueSequence, int start, int end) { + if (start == 0 && end == valueSequence.length && + isSorted(valueSequence, 0, false)) { + return valueSequence; + } else { + int[] slice = new int[end-start]; + System.arraycopy(valueSequence, start, slice, 0, slice.length); + Arrays.sort(slice); + return slice; + } + } + + /** Tell if an array is sorted. */ + private static + boolean isSorted(int[] values, int from, boolean strict) { + for (int i = from+1; i < values.length; i++) { + if (strict ? !(values[i-1] < values[i]) + : !(values[i-1] <= values[i])) { + return false; // found witness to disorder + } + } + return true; // no witness => sorted + } + + /** Clone and sort the array, if not already sorted. */ + private static + int[] maybeSort(int[] values) { + if (!isSorted(values, 0, false)) { + values = values.clone(); + Arrays.sort(values); + } + return values; + } + + + /// Debug stuff follows. + + private boolean assertWellFormed(int[] valueSequence) { +/* + // Sanity check. + int weight = 0; + int vlength = 0; + for (int i = 0; i < matrix.length; i++) { + int vlengthi = (matrix[i].length-1); + int count = matrix[i][0]; + assert(vlengthi > 0); // no empty rows + assert(count > 0); // no impossible rows + vlength += vlengthi; + weight += count * vlengthi; + } + assert(isSorted(values, 0, true)); + // make sure the counts all add up + assert(totalWeight == weight); + assert(vlength == values.length); + assert(vlength == counts.length); + int weight2 = 0; + for (int i = 0; i < counts.length; i++) { + weight2 += counts[i]; + } + assert(weight2 == weight); + int[] revcol1 = new int[matrix.length]; //1st matrix colunm + for (int i = 0; i < matrix.length; i++) { + // spot checking: try a random query on each matrix row + assert(matrix[i].length > 1); + revcol1[matrix.length-i-1] = matrix[i][0]; + assert(isSorted(matrix[i], 1, true)); + int rand = (matrix[i].length+1) / 2; + int val = matrix[i][rand]; + int count = matrix[i][0]; + int pos = Arrays.binarySearch(values, val); + assert(values[pos] == val); + assert(counts[pos] == matrix[i][0]); + if (valueSequence != null) { + int count2 = 0; + for (int j = 0; j < valueSequence.length; j++) { + if (valueSequence[j] == val) count2++; + } + assert(count2 == count); + } + } + assert(isSorted(revcol1, 0, true)); +//*/ + return true; + } + +/* + public static + int[] readValuesFrom(InputStream instr) { + return readValuesFrom(new InputStreamReader(instr)); + } + public static + int[] readValuesFrom(Reader inrdr) { + inrdr = new BufferedReader(inrdr); + final StreamTokenizer in = new StreamTokenizer(inrdr); + final int TT_NOTHING = -99; + in.commentChar('#'); + return readValuesFrom(new Iterator() { + int token = TT_NOTHING; + private int getToken() { + if (token == TT_NOTHING) { + try { + token = in.nextToken(); + assert(token != TT_NOTHING); + } catch (IOException ee) { + throw new RuntimeException(ee); + } + } + return token; + } + public boolean hasNext() { + return getToken() != StreamTokenizer.TT_EOF; + } + public Object next() { + int ntok = getToken(); + token = TT_NOTHING; + switch (ntok) { + case StreamTokenizer.TT_EOF: + throw new NoSuchElementException(); + case StreamTokenizer.TT_NUMBER: + return new Integer((int) in.nval); + default: + assert(false); + return null; + } + } + public void remove() { + throw new UnsupportedOperationException(); + } + }); + } + public static + int[] readValuesFrom(Iterator iter) { + return readValuesFrom(iter, 0); + } + public static + int[] readValuesFrom(Iterator iter, int initSize) { + int[] na = new int[Math.max(10, initSize)]; + int np = 0; + while (iter.hasNext()) { + Integer val = (Integer) iter.next(); + if (np == na.length) { + int[] na2 = new int[np*2]; + System.arraycopy(na, 0, na2, 0, np); + na = na2; + } + na[np++] = val.intValue(); + } + if (np != na.length) { + int[] na2 = new int[np]; + System.arraycopy(na, 0, na2, 0, np); + na = na2; + } + return na; + } + + public static + Histogram makeByteHistogram(byte[] bytes) { + try { + return makeByteHistogram(new ByteArrayInputStream(bytes)); + } catch (IOException ee) { + throw new RuntimeException(ee); + } + } + + public static + void main(String[] av) throws IOException { + if (av.length > 0 && av[0].equals("-r")) { + int[] values = new int[Integer.parseInt(av[1])]; + int limit = values.length; + if (av.length >= 3) { + limit = (int)( limit * Double.parseDouble(av[2]) ); + } + Random rnd = new Random(); + for (int i = 0; i < values.length; i++) { + values[i] = rnd.nextInt(limit);; + } + Histogram rh = new Histogram(values); + rh.print("random", System.out); + return; + } + if (av.length > 0 && av[0].equals("-s")) { + int[] values = readValuesFrom(System.in); + Random rnd = new Random(); + for (int i = values.length; --i > 0; ) { + int j = rnd.nextInt(i+1); + if (j < i) { + int tem = values[i]; + values[i] = values[j]; + values[j] = tem; + } + } + for (int i = 0; i < values.length; i++) + System.out.println(values[i]); + return; + } + if (av.length > 0 && av[0].equals("-e")) { + // edge cases + new Histogram(new int[][] { + {1, 11, 111}, + {0, 123, 456}, + {1, 111, 1111}, + {0, 456, 123}, + {3}, + {}, + {3}, + {2, 22}, + {4} + }).print(System.out); + return; + } + if (av.length > 0 && av[0].equals("-b")) { + // edge cases + Histogram bh = makeByteHistogram(System.in); + bh.print("bytes", System.out); + return; + } + boolean regroup = false; + if (av.length > 0 && av[0].equals("-g")) { + regroup = true; + } + + int[] values = readValuesFrom(System.in); + Histogram h = new Histogram(values); + if (!regroup) + h.print(System.out); + if (regroup) { + int[] groups = new int[12]; + for (int i = 0; i < groups.length; i++) { + groups[i] = 1< 0); + assert(pc+length <= bytes.length); + // Speed hack: Instruction.next reuses self if possible. + if (reuse != null && !reuse.special) { + reuse.reset(bytes, pc, bc, w, length); + return reuse; + } + return new Instruction(bytes, pc, bc, w, length); + } + + // Return the constant pool reference type, or 0 if none. + public byte getCPTag() { + return BC_TAG[w][bc]; + } + + // Return the constant pool index, or -1 if none. + public int getCPIndex() { + int indexLoc = BC_INDEX[w][bc]; + if (indexLoc == 0) return -1; + assert(w == 0); + if (length == 2) + return getByte(bytes, pc+indexLoc); // _ldc opcode only + else + return getShort(bytes, pc+indexLoc); + } + + public void setCPIndex(int cpi) { + int indexLoc = BC_INDEX[w][bc]; + assert(indexLoc != 0); + if (length == 2) + setByte(bytes, pc+indexLoc, cpi); // _ldc opcode only + else + setShort(bytes, pc+indexLoc, cpi); + assert(getCPIndex() == cpi); + } + + public ConstantPool.Entry getCPRef(ConstantPool.Entry[] cpMap) { + int index = getCPIndex(); + return (index < 0) ? null : cpMap[index]; + } + + // Return the slot of the affected local, or -1 if none. + public int getLocalSlot() { + int slotLoc = BC_SLOT[w][bc]; + if (slotLoc == 0) return -1; + if (w == 0) + return getByte(bytes, pc+slotLoc); + else + return getShort(bytes, pc+slotLoc); + } + + // Return the target of the branch, or -1 if none. + public int getBranchLabel() { + int branchLoc = BC_BRANCH[w][bc]; + if (branchLoc == 0) return -1; + assert(w == 0); + assert(length == 3 || length == 5); + int offset; + if (length == 3) + offset = (short)getShort(bytes, pc+branchLoc); + else + offset = getInt(bytes, pc+branchLoc); + assert(offset+pc >= 0); + assert(offset+pc <= bytes.length); + return offset+pc; + } + + public void setBranchLabel(int targetPC) { + int branchLoc = BC_BRANCH[w][bc]; + assert(branchLoc != 0); + if (length == 3) + setShort(bytes, pc+branchLoc, targetPC-pc); + else + setInt(bytes, pc+branchLoc, targetPC-pc); + assert(targetPC == getBranchLabel()); + } + + // Return the trailing constant in the instruction (as a signed value). + // Return 0 if there is none. + public int getConstant() { + int conLoc = BC_CON[w][bc]; + if (conLoc == 0) return 0; + switch (length - conLoc) { + case 1: return (byte) getByte(bytes, pc+conLoc); + case 2: return (short) getShort(bytes, pc+conLoc); + } + assert(false); + return 0; + } + + public void setConstant(int con) { + int conLoc = BC_CON[w][bc]; + assert(conLoc != 0); + switch (length - conLoc) { + case 1: setByte(bytes, pc+conLoc, con); break; + case 2: setShort(bytes, pc+conLoc, con); break; + } + assert(con == getConstant()); + } + + public abstract static class Switch extends Instruction { + // Each case is a (value, label) pair, indexed 0 <= n < caseCount + public abstract int getCaseCount(); + public abstract int getCaseValue(int n); + public abstract int getCaseLabel(int n); + public abstract void setCaseCount(int caseCount); + public abstract void setCaseValue(int n, int value); + public abstract void setCaseLabel(int n, int targetPC); + protected abstract int getLength(int caseCount); + + public int getDefaultLabel() { return intAt(0)+pc; } + public void setDefaultLabel(int targetPC) { setIntAt(0, targetPC-pc); } + + protected int apc; // aligned pc (table base) + protected int intAt(int n) { return getInt(bytes, apc + n*4); } + protected void setIntAt(int n, int x) { setInt(bytes, apc + n*4, x); } + protected Switch(byte[] bytes, int pc, int bc) { + super(bytes, pc, bc, /*w*/0, /*length*/0); + this.apc = alignPC(pc+1); + this.special = true; + length = getLength(getCaseCount()); + } + public int getAlignedPC() { return apc; } + public String toString() { + String s = super.toString(); + s += " Default:"+labstr(getDefaultLabel()); + int caseCount = getCaseCount(); + for (int i = 0; i < caseCount; i++) { + s += "\n\tCase "+getCaseValue(i)+":"+labstr(getCaseLabel(i)); + } + return s; + } + public static int alignPC(int apc) { + while (apc % 4 != 0) ++apc; + return apc; + } + } + + public static class TableSwitch extends Switch { + // apc: (df, lo, hi, (hi-lo+1)*(label)) + public int getLowCase() { return intAt(1); } + public int getHighCase() { return intAt(2); } + public int getCaseCount() { return intAt(2)-intAt(1)+1; } + public int getCaseValue(int n) { return getLowCase()+n; } + public int getCaseLabel(int n) { return intAt(3+n)+pc; } + + public void setLowCase(int val) { setIntAt(1, val); } + public void setHighCase(int val) { setIntAt(2, val); } + public void setCaseLabel(int n, int tpc) { setIntAt(3+n, tpc-pc); } + public void setCaseCount(int caseCount) { + setHighCase(getLowCase() + caseCount - 1); + length = getLength(caseCount); + } + public void setCaseValue(int n, int val) { + if (n != 0) throw new UnsupportedOperationException(); + int caseCount = getCaseCount(); + setLowCase(val); + setCaseCount(caseCount); // keep invariant + } + + TableSwitch(byte[] bytes, int pc) { + super(bytes, pc, Constants._tableswitch); + } + protected int getLength(int caseCount) { + return (apc-pc) + (3 + caseCount) * 4; + } + } + + public static class LookupSwitch extends Switch { + // apc: (df, nc, nc*(case, label)) + public int getCaseCount() { return intAt(1); } + public int getCaseValue(int n) { return intAt(2+n*2+0); } + public int getCaseLabel(int n) { return intAt(2+n*2+1)+pc; } + + public void setCaseCount(int caseCount) { + setIntAt(1, caseCount); + length = getLength(caseCount); + } + public void setCaseValue(int n, int val) { setIntAt(2+n*2+0, val); } + public void setCaseLabel(int n, int tpc) { setIntAt(2+n*2+1, tpc-pc); } + + LookupSwitch(byte[] bytes, int pc) { + super(bytes, pc, Constants._lookupswitch); + } + protected int getLength(int caseCount) { + return (apc-pc) + (2 + caseCount*2) * 4; + } + } + + /** Two instructions are equal if they have the same bytes. */ + public boolean equals(Object o) { + return (o != null) && (o.getClass() == Instruction.class) + && equals((Instruction) o); + } + + public int hashCode() { + int hash = 3; + hash = 11 * hash + Arrays.hashCode(this.bytes); + hash = 11 * hash + this.pc; + hash = 11 * hash + this.bc; + hash = 11 * hash + this.w; + hash = 11 * hash + this.length; + return hash; + } + + public boolean equals(Instruction that) { + if (this.pc != that.pc) return false; + if (this.bc != that.bc) return false; + if (this.w != that.w) return false; + if (this.length != that.length) return false; + for (int i = 1; i < length; i++) { + if (this.bytes[this.pc+i] != that.bytes[that.pc+i]) + return false; + } + return true; + } + + static String labstr(int pc) { + if (pc >= 0 && pc < 100000) + return ((100000+pc)+"").substring(1); + return pc+""; + } + public String toString() { + return toString(null); + } + public String toString(ConstantPool.Entry[] cpMap) { + String s = labstr(pc) + ": "; + if (bc >= Constants._bytecode_limit) { + s += Integer.toHexString(bc); + return s; + } + if (w == 1) s += "wide "; + String bcname = (bc < BC_NAME.length)? BC_NAME[bc]: null; + if (bcname == null) { + return s+"opcode#"+bc; + } + s += bcname; + int tag = getCPTag(); + if (tag != 0) s += " "+ConstantPool.tagName(tag)+":"; + int idx = getCPIndex(); + if (idx >= 0) s += (cpMap == null) ? ""+idx : "="+cpMap[idx].stringValue(); + int slt = getLocalSlot(); + if (slt >= 0) s += " Local:"+slt; + int lab = getBranchLabel(); + if (lab >= 0) s += " To:"+labstr(lab); + int con = getConstant(); + if (con != 0) s += " Con:"+con; + return s; + } + + + //public static byte constantPoolTagFor(int bc) { return BC_TAG[0][bc]; } + + /// Fetching values from byte arrays: + + public int getIntAt(int off) { + return getInt(bytes, pc+off); + } + public int getShortAt(int off) { + return getShort(bytes, pc+off); + } + public int getByteAt(int off) { + return getByte(bytes, pc+off); + } + + + public static int getInt(byte[] bytes, int pc) { + return (getShort(bytes, pc+0) << 16) + (getShort(bytes, pc+2) << 0); + } + public static int getShort(byte[] bytes, int pc) { + return (getByte(bytes, pc+0) << 8) + (getByte(bytes, pc+1) << 0); + } + public static int getByte(byte[] bytes, int pc) { + return bytes[pc] & 0xFF; + } + + + public static void setInt(byte[] bytes, int pc, int x) { + setShort(bytes, pc+0, x >> 16); + setShort(bytes, pc+2, x >> 0); + } + public static void setShort(byte[] bytes, int pc, int x) { + setByte(bytes, pc+0, x >> 8); + setByte(bytes, pc+1, x >> 0); + } + public static void setByte(byte[] bytes, int pc, int x) { + bytes[pc] = (byte)x; + } + + // some bytecode classifiers + + + public static boolean isNonstandard(int bc) { + return BC_LENGTH[0][bc] < 0; + } + + public static int opLength(int bc) { + int l = BC_LENGTH[0][bc]; + assert(l > 0); + return l; + } + public static int opWideLength(int bc) { + int l = BC_LENGTH[1][bc]; + assert(l > 0); + return l; + } + + public static boolean isLocalSlotOp(int bc) { + return (bc < BC_SLOT[0].length && BC_SLOT[0][bc] > 0); + } + + public static boolean isBranchOp(int bc) { + return (bc < BC_BRANCH[0].length && BC_BRANCH[0][bc] > 0); + } + + public static boolean isCPRefOp(int bc) { + if (bc < BC_INDEX[0].length && BC_INDEX[0][bc] > 0) return true; + if (bc >= Constants._xldc_op && bc < Constants._xldc_limit) return true; + if (bc == Constants._invokespecial_int || bc == Constants._invokestatic_int) return true; + return false; + } + + public static byte getCPRefOpTag(int bc) { + if (bc < BC_INDEX[0].length && BC_INDEX[0][bc] > 0) return BC_TAG[0][bc]; + if (bc >= Constants._xldc_op && bc < Constants._xldc_limit) return Constants.CONSTANT_LoadableValue; + if (bc == Constants._invokestatic_int || bc == Constants._invokespecial_int) return Constants.CONSTANT_InterfaceMethodref; + return Constants.CONSTANT_None; + } + + public static boolean isFieldOp(int bc) { + return (bc >= Constants._getstatic && bc <= Constants._putfield); + } + + public static boolean isInvokeInitOp(int bc) { + return (bc >= Constants._invokeinit_op && bc < Constants._invokeinit_limit); + } + + public static boolean isSelfLinkerOp(int bc) { + return (bc >= Constants._self_linker_op && bc < Constants._self_linker_limit); + } + + /// Format definitions. + + private static final byte[][] BC_LENGTH = new byte[2][0x100]; + private static final byte[][] BC_INDEX = new byte[2][0x100]; + private static final byte[][] BC_TAG = new byte[2][0x100]; + private static final byte[][] BC_BRANCH = new byte[2][0x100]; + private static final byte[][] BC_SLOT = new byte[2][0x100]; + private static final byte[][] BC_CON = new byte[2][0x100]; + private static final String[] BC_NAME = new String[0x100]; // debug only + private static final String[][] BC_FORMAT = new String[2][Constants._bytecode_limit]; // debug only + static { + for (int i = 0; i < Constants._bytecode_limit; i++) { + BC_LENGTH[0][i] = -1; + BC_LENGTH[1][i] = -1; + } + def("b", Constants._nop, Constants._dconst_1); + def("bx", Constants._bipush); + def("bxx", Constants._sipush); + def("bk", Constants._ldc); // do not pack + def("bkk", Constants._ldc_w, Constants._ldc2_w); // do not pack + def("blwbll", Constants._iload, Constants._aload); + def("b", Constants._iload_0, Constants._saload); + def("blwbll", Constants._istore, Constants._astore); + def("b", Constants._istore_0, Constants._lxor); + def("blxwbllxx", Constants._iinc); + def("b", Constants._i2l, Constants._dcmpg); + def("boo", Constants._ifeq, Constants._jsr); // pack oo + def("blwbll", Constants._ret); + def("", Constants._tableswitch, Constants._lookupswitch); // pack all ints, omit padding + def("b", Constants._ireturn, Constants._return); + def("bkf", Constants._getstatic, Constants._putfield); // pack kf (base=Field) + def("bkm", Constants._invokevirtual, Constants._invokestatic); // pack kn (base=Method) + def("bkixx", Constants._invokeinterface); // pack ki (base=IMethod), omit xx + def("bkyxx", Constants._invokedynamic); // pack ky (base=Any), omit xx + def("bkc", Constants._new); // pack kc + def("bx", Constants._newarray); + def("bkc", Constants._anewarray); // pack kc + def("b", Constants._arraylength, Constants._athrow); + def("bkc", Constants._checkcast, Constants._instanceof); // pack kc + def("b", Constants._monitorenter, Constants._monitorexit); + def("", Constants._wide); + def("bkcx", Constants._multianewarray); // pack kc + def("boo", Constants._ifnull, Constants._ifnonnull); // pack oo + def("boooo", Constants._goto_w, Constants._jsr_w); // pack oooo + for (int i = 0; i < Constants._bytecode_limit; i++) { + //System.out.println(i+": l="+BC_LENGTH[0][i]+" i="+BC_INDEX[0][i]); + //assert(BC_LENGTH[0][i] != -1); + if (BC_LENGTH[0][i] == -1) { + continue; // unknown opcode + } + + // Have a complete mapping, to support spurious _wide prefixes. + if (BC_LENGTH[1][i] == -1) + BC_LENGTH[1][i] = (byte)(1+BC_LENGTH[0][i]); + } + + String names = + "nop aconst_null iconst_m1 iconst_0 iconst_1 iconst_2 iconst_3 iconst_4 "+ + "iconst_5 lconst_0 lconst_1 fconst_0 fconst_1 fconst_2 dconst_0 dconst_1 "+ + "bipush sipush ldc ldc_w ldc2_w iload lload fload dload aload iload_0 "+ + "iload_1 iload_2 iload_3 lload_0 lload_1 lload_2 lload_3 fload_0 fload_1 "+ + "fload_2 fload_3 dload_0 dload_1 dload_2 dload_3 aload_0 aload_1 aload_2 "+ + "aload_3 iaload laload faload daload aaload baload caload saload istore "+ + "lstore fstore dstore astore istore_0 istore_1 istore_2 istore_3 lstore_0 "+ + "lstore_1 lstore_2 lstore_3 fstore_0 fstore_1 fstore_2 fstore_3 dstore_0 "+ + "dstore_1 dstore_2 dstore_3 astore_0 astore_1 astore_2 astore_3 iastore "+ + "lastore fastore dastore aastore bastore castore sastore pop pop2 dup "+ + "dup_x1 dup_x2 dup2 dup2_x1 dup2_x2 swap iadd ladd fadd dadd isub lsub "+ + "fsub dsub imul lmul fmul dmul idiv ldiv fdiv ddiv irem lrem frem drem "+ + "ineg lneg fneg dneg ishl lshl ishr lshr iushr lushr iand land ior lor "+ + "ixor lxor iinc i2l i2f i2d l2i l2f l2d f2i f2l f2d d2i d2l d2f i2b i2c "+ + "i2s lcmp fcmpl fcmpg dcmpl dcmpg ifeq ifne iflt ifge ifgt ifle if_icmpeq "+ + "if_icmpne if_icmplt if_icmpge if_icmpgt if_icmple if_acmpeq if_acmpne "+ + "goto jsr ret tableswitch lookupswitch ireturn lreturn freturn dreturn "+ + "areturn return getstatic putstatic getfield putfield invokevirtual "+ + "invokespecial invokestatic invokeinterface invokedynamic new newarray "+ + "anewarray arraylength athrow checkcast instanceof monitorenter "+ + "monitorexit wide multianewarray ifnull ifnonnull goto_w jsr_w "; + for (int bc = 0; names.length() > 0; bc++) { + int sp = names.indexOf(' '); + BC_NAME[bc] = names.substring(0, sp); + names = names.substring(sp+1); + } + } + public static String byteName(int bc) { + String iname; + if (bc < BC_NAME.length && BC_NAME[bc] != null) { + iname = BC_NAME[bc]; + } else if (isSelfLinkerOp(bc)) { + int idx = (bc - Constants._self_linker_op); + boolean isSuper = (idx >= Constants._self_linker_super_flag); + if (isSuper) idx -= Constants._self_linker_super_flag; + boolean isAload = (idx >= Constants._self_linker_aload_flag); + if (isAload) idx -= Constants._self_linker_aload_flag; + int origBC = Constants._first_linker_op + idx; + assert(origBC >= Constants._first_linker_op && origBC <= Constants._last_linker_op); + iname = BC_NAME[origBC]; + iname += (isSuper ? "_super" : "_this"); + if (isAload) iname = "aload_0&" + iname; + iname = "*"+iname; + } else if (isInvokeInitOp(bc)) { + int idx = (bc - Constants._invokeinit_op); + switch (idx) { + case Constants._invokeinit_self_option: + iname = "*invokespecial_init_this"; break; + case Constants._invokeinit_super_option: + iname = "*invokespecial_init_super"; break; + default: + assert(idx == Constants._invokeinit_new_option); + iname = "*invokespecial_init_new"; break; + } + } else { + switch (bc) { + case Constants._ildc: iname = "*ildc"; break; + case Constants._fldc: iname = "*fldc"; break; + case Constants._ildc_w: iname = "*ildc_w"; break; + case Constants._fldc_w: iname = "*fldc_w"; break; + case Constants._dldc2_w: iname = "*dldc2_w"; break; + case Constants._cldc: iname = "*cldc"; break; + case Constants._cldc_w: iname = "*cldc_w"; break; + case Constants._qldc: iname = "*qldc"; break; + case Constants._qldc_w: iname = "*qldc_w"; break; + case Constants._byte_escape: iname = "*byte_escape"; break; + case Constants._ref_escape: iname = "*ref_escape"; break; + case Constants._end_marker: iname = "*end"; break; + default: iname = "*bc#"+bc; break; + } + } + return iname; + } + private static int BW = 4; // width of classification field + private static void def(String fmt, int bc) { + def(fmt, bc, bc); + } + private static void def(String fmt, int from_bc, int to_bc) { + String[] fmts = { fmt, null }; + if (fmt.indexOf('w') > 0) { + fmts[1] = fmt.substring(fmt.indexOf('w')); + fmts[0] = fmt.substring(0, fmt.indexOf('w')); + } + for (int w = 0; w <= 1; w++) { + fmt = fmts[w]; + if (fmt == null) continue; + int length = fmt.length(); + int index = Math.max(0, fmt.indexOf('k')); + int tag = Constants.CONSTANT_None; + int branch = Math.max(0, fmt.indexOf('o')); + int slot = Math.max(0, fmt.indexOf('l')); + int con = Math.max(0, fmt.indexOf('x')); + if (index > 0 && index+1 < length) { + switch (fmt.charAt(index+1)) { + case 'c': tag = Constants.CONSTANT_Class; break; + case 'k': tag = Constants.CONSTANT_LoadableValue; break; + case 'f': tag = Constants.CONSTANT_Fieldref; break; + case 'm': tag = Constants.CONSTANT_Methodref; break; + case 'i': tag = Constants.CONSTANT_InterfaceMethodref; break; + case 'y': tag = Constants.CONSTANT_InvokeDynamic; break; + } + assert(tag != Constants.CONSTANT_None); + } else if (index > 0 && length == 2) { + assert(from_bc == Constants._ldc); + tag = Constants.CONSTANT_LoadableValue; // _ldc opcode only + } + for (int bc = from_bc; bc <= to_bc; bc++) { + BC_FORMAT[w][bc] = fmt; + assert(BC_LENGTH[w][bc] == -1); + BC_LENGTH[w][bc] = (byte) length; + BC_INDEX[w][bc] = (byte) index; + BC_TAG[w][bc] = (byte) tag; + assert(!(index == 0 && tag != Constants.CONSTANT_None)); + BC_BRANCH[w][bc] = (byte) branch; + BC_SLOT[w][bc] = (byte) slot; + assert(branch == 0 || slot == 0); // not both branch & local + assert(branch == 0 || index == 0); // not both branch & cp + assert(slot == 0 || index == 0); // not both local & cp + BC_CON[w][bc] = (byte) con; + } + } + } + + public static void opcodeChecker(byte[] code, ConstantPool.Entry[] cpMap, + Package.Version clsVersion) throws FormatException { + Instruction i = at(code, 0); + while (i != null) { + int opcode = i.getBC(); + if (opcode < Constants._nop || opcode > Constants._jsr_w) { + String message = "illegal opcode: " + opcode + " " + i; + throw new FormatException(message); + } + ConstantPool.Entry e = i.getCPRef(cpMap); + if (e != null) { + byte tag = i.getCPTag(); + boolean match = e.tagMatches(tag); + if (!match && + (i.bc == Constants._invokespecial || i.bc == Constants._invokestatic) && + e.tagMatches(Constants.CONSTANT_InterfaceMethodref) && + clsVersion.greaterThan(Constants.JAVA7_MAX_CLASS_VERSION)) { + match = true; + } + if (!match) { + String message = "illegal reference, expected type=" + + ConstantPool.tagName(tag) + ": " + + i.toString(cpMap); + throw new FormatException(message); + } + } + i = i.next(); + } + } + static class FormatException extends IOException { + private static final long serialVersionUID = 3175572275651367015L; + + FormatException(String message) { + super(message); + } + } +} diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/NativeUnpack.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/NativeUnpack.java new file mode 100644 index 000000000..dfff91a22 --- /dev/null +++ b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/NativeUnpack.java @@ -0,0 +1,331 @@ +/* + * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + +package net.fabricmc.shade.com.sun.java.util.jar.pack; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.security.PrivilegedAction; +import java.util.jar.JarOutputStream; +import net.fabricmc.shade.java.util.jar.Pack200; +import java.util.zip.CRC32; +import java.util.zip.Deflater; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +@SuppressWarnings({"removal"}) +class NativeUnpack { + // Pointer to the native unpacker obj + private long unpackerPtr; + + // Input stream. + private BufferedInputStream in; + + private static synchronized native void initIDs(); + + // Starts processing at the indicated position in the buffer. + // If the buffer is null, the readInputFn callback is used to get bytes. + // Returns (s<<32|f), the number of following segments and files. + private synchronized native long start(ByteBuffer buf, long offset); + + // Returns true if there's another, and fills in the parts. + private synchronized native boolean getNextFile(Object[] parts); + + private synchronized native ByteBuffer getUnusedInput(); + + // Resets the engine and frees all resources. + // Returns total number of bytes consumed by the engine. + private synchronized native long finish(); + + // Setting state in the unpacker. + protected synchronized native boolean setOption(String opt, String value); + protected synchronized native String getOption(String opt); + + private int _verbose; + + // State for progress bar: + private long _byteCount; // bytes read in current segment + private int _segCount; // number of segs scanned + private int _fileCount; // number of files written + private long _estByteLimit; // estimate of eventual total + private int _estSegLimit; // ditto + private int _estFileLimit; // ditto + private int _prevPercent = -1; // for monotonicity + + private final CRC32 _crc32 = new CRC32(); + private byte[] _buf = new byte[1<<14]; + + private UnpackerImpl _p200; + private PropMap _props; + + static { + // If loading from stand alone build uncomment this. + // System.loadLibrary("unpack"); + java.security.AccessController.doPrivileged( + (PrivilegedAction) () -> { + System.loadLibrary("unpack"); + return null; + }); + initIDs(); + } + + NativeUnpack(UnpackerImpl p200) { + super(); + _p200 = p200; + _props = p200.props; + p200._nunp = this; + } + + // for JNI callbacks + private static Object currentInstance() { + UnpackerImpl p200 = (UnpackerImpl) Utils.getTLGlobals(); + return (p200 == null)? null: p200._nunp; + } + + private synchronized long getUnpackerPtr() { + return unpackerPtr; + } + + // Callback from the unpacker engine to get more data. + private long readInputFn(ByteBuffer pbuf, long minlen) throws IOException { + if (in == null) return 0; // nothing is readable + long maxlen = pbuf.capacity() - pbuf.position(); + assert(minlen <= maxlen); // don't talk nonsense + long numread = 0; + int steps = 0; + while (numread < minlen) { + steps++; + // read available input, up to buf.length or maxlen + int readlen = _buf.length; + if (readlen > (maxlen - numread)) + readlen = (int)(maxlen - numread); + int nr = in.read(_buf, 0, readlen); + if (nr <= 0) break; + numread += nr; + assert(numread <= maxlen); + // %%% get rid of this extra copy by using nio? + pbuf.put(_buf, 0, nr); + } + if (_verbose > 1) + Utils.log.fine("readInputFn("+minlen+","+maxlen+") => "+numread+" steps="+steps); + if (maxlen > 100) { + _estByteLimit = _byteCount + maxlen; + } else { + _estByteLimit = (_byteCount + numread) * 20; + } + _byteCount += numread; + updateProgress(); + return numread; + } + + private void updateProgress() { + // Progress is a combination of segment reading and file writing. + final double READ_WT = 0.33; + final double WRITE_WT = 0.67; + double readProgress = _segCount; + if (_estByteLimit > 0 && _byteCount > 0) + readProgress += (double)_byteCount / _estByteLimit; + double writeProgress = _fileCount; + double scaledProgress + = READ_WT * readProgress / Math.max(_estSegLimit,1) + + WRITE_WT * writeProgress / Math.max(_estFileLimit,1); + int percent = (int) Math.round(100*scaledProgress); + if (percent > 100) percent = 100; + if (percent > _prevPercent) { + _prevPercent = percent; + _props.setInteger(Pack200.Unpacker.PROGRESS, percent); + if (_verbose > 0) + Utils.log.info("progress = "+percent); + } + } + + private void copyInOption(String opt) { + String val = _props.getProperty(opt); + if (_verbose > 0) + Utils.log.info("set "+opt+"="+val); + if (val != null) { + boolean set = setOption(opt, val); + if (!set) + Utils.log.warning("Invalid option "+opt+"="+val); + } + } + + void run(InputStream inRaw, JarOutputStream jstream, + ByteBuffer presetInput) throws IOException { + BufferedInputStream in0 = new BufferedInputStream(inRaw); + this.in = in0; // for readInputFn to see + _verbose = _props.getInteger(Utils.DEBUG_VERBOSE); + // Fix for BugId: 4902477, -unpack.modification.time = 1059010598000 + // TODO eliminate and fix in unpack.cpp + + final int modtime = Pack200.Packer.KEEP.equals(_props.getProperty(Utils.UNPACK_MODIFICATION_TIME, "0")) ? + Constants.NO_MODTIME : _props.getTime(Utils.UNPACK_MODIFICATION_TIME); + + copyInOption(Utils.DEBUG_VERBOSE); + copyInOption(Pack200.Unpacker.DEFLATE_HINT); + if (modtime == Constants.NO_MODTIME) // Don't pass KEEP && NOW + copyInOption(Utils.UNPACK_MODIFICATION_TIME); + updateProgress(); // reset progress bar + for (;;) { + // Read the packed bits. + long counts = start(presetInput, 0); + _byteCount = _estByteLimit = 0; // reset partial scan counts + ++_segCount; // just finished scanning a whole segment... + int nextSeg = (int)( counts >>> 32 ); + int nextFile = (int)( counts >>> 0 ); + + // Estimate eventual total number of segments and files. + _estSegLimit = _segCount + nextSeg; + double filesAfterThisSeg = _fileCount + nextFile; + _estFileLimit = (int)( (filesAfterThisSeg * + _estSegLimit) / _segCount ); + + // Write the files. + int[] intParts = { 0,0, 0, 0 }; + // intParts = {size.hi/lo, mod, defl} + Object[] parts = { intParts, null, null, null }; + // parts = { {intParts}, name, data0/1 } + while (getNextFile(parts)) { + //BandStructure.printArrayTo(System.out, intParts, 0, parts.length); + String name = (String) parts[1]; + long size = ( (long)intParts[0] << 32) + + (((long)intParts[1] << 32) >>> 32); + + long mtime = (modtime != Constants.NO_MODTIME ) ? + modtime : intParts[2] ; + boolean deflateHint = (intParts[3] != 0); + ByteBuffer data0 = (ByteBuffer) parts[2]; + ByteBuffer data1 = (ByteBuffer) parts[3]; + writeEntry(jstream, name, mtime, size, deflateHint, + data0, data1); + ++_fileCount; + updateProgress(); + } + presetInput = getUnusedInput(); + long consumed = finish(); + if (_verbose > 0) + Utils.log.info("bytes consumed = "+consumed); + if (presetInput == null && + !Utils.isPackMagic(Utils.readMagic(in0))) { + break; + } + if (_verbose > 0 ) { + if (presetInput != null) + Utils.log.info("unused input = "+presetInput); + } + } + } + + void run(InputStream in, JarOutputStream jstream) throws IOException { + run(in, jstream, null); + } + + void run(File inFile, JarOutputStream jstream) throws IOException { + // %%% maybe memory-map the file, and pass it straight into unpacker + ByteBuffer mappedFile = null; + try (FileInputStream fis = new FileInputStream(inFile)) { + run(fis, jstream, mappedFile); + } + // Note: caller is responsible to finish with jstream. + } + + private void writeEntry(JarOutputStream j, String name, + long mtime, long lsize, boolean deflateHint, + ByteBuffer data0, ByteBuffer data1) throws IOException { + int size = (int)lsize; + if (size != lsize) + throw new IOException("file too large: "+lsize); + + CRC32 crc32 = _crc32; + + if (_verbose > 1) + Utils.log.fine("Writing entry: "+name+" size="+size + +(deflateHint?" deflated":"")); + + if (_buf.length < size) { + int newSize = size; + while (newSize < _buf.length) { + newSize <<= 1; + if (newSize <= 0) { + newSize = size; + break; + } + } + _buf = new byte[newSize]; + } + assert(_buf.length >= size); + + int fillp = 0; + if (data0 != null) { + int size0 = data0.capacity(); + data0.get(_buf, fillp, size0); + fillp += size0; + } + if (data1 != null) { + int size1 = data1.capacity(); + data1.get(_buf, fillp, size1); + fillp += size1; + } + while (fillp < size) { + // Fill in rest of data from the stream itself. + int nr = in.read(_buf, fillp, size - fillp); + if (nr <= 0) throw new IOException("EOF at end of archive"); + fillp += nr; + } + + ZipEntry z = new ZipEntry(name); + z.setTime(mtime * 1000); + + if (size == 0) { + z.setMethod(ZipOutputStream.STORED); + z.setSize(0); + z.setCrc(0); + z.setCompressedSize(0); + } else if (!deflateHint) { + z.setMethod(ZipOutputStream.STORED); + z.setSize(size); + z.setCompressedSize(size); + crc32.reset(); + crc32.update(_buf, 0, size); + z.setCrc(crc32.getValue()); + } else { + z.setMethod(Deflater.DEFLATED); + z.setSize(size); + } + + j.putNextEntry(z); + + if (size > 0) + j.write(_buf, 0, size); + + j.closeEntry(); + if (_verbose > 0) Utils.log.info("Writing " + Utils.zeString(z)); + } +} diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Package.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Package.java new file mode 100644 index 000000000..6e783ac5f --- /dev/null +++ b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Package.java @@ -0,0 +1,1375 @@ +/* + * Copyright (c) 2001, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package net.fabricmc.shade.com.sun.java.util.jar.pack; + +import net.fabricmc.shade.com.sun.java.util.jar.pack.ConstantPool.ClassEntry; +import net.fabricmc.shade.com.sun.java.util.jar.pack.ConstantPool.DescriptorEntry; +import net.fabricmc.shade.com.sun.java.util.jar.pack.ConstantPool.BootstrapMethodEntry; +import net.fabricmc.shade.com.sun.java.util.jar.pack.ConstantPool.Index; +import net.fabricmc.shade.com.sun.java.util.jar.pack.ConstantPool.LiteralEntry; +import net.fabricmc.shade.com.sun.java.util.jar.pack.ConstantPool.Utf8Entry; +import net.fabricmc.shade.com.sun.java.util.jar.pack.ConstantPool.Entry; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.SequenceInputStream; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Set; +import java.util.jar.JarFile; + +/** + * Define the main data structure transmitted by pack/unpack. + * @author John Rose + */ +class Package { + int verbose; + { + PropMap pmap = Utils.currentPropMap(); + if (pmap != null) + verbose = pmap.getInteger(Utils.DEBUG_VERBOSE); + } + + final int magic = Constants.JAVA_PACKAGE_MAGIC; + + int default_modtime = Constants.NO_MODTIME; + int default_options = 0; // FO_DEFLATE_HINT + + Version defaultClassVersion = null; + + // These fields can be adjusted by driver properties. + final Version minClassVersion; + final Version maxClassVersion; + // null, indicates that consensus rules during package write + final Version packageVersion; + + Version observedHighestClassVersion = null; + + + // What constants are used in this unit? + ConstantPool.IndexGroup cp = new ConstantPool.IndexGroup(); + + /* + * typically used by the PackageReader to set the defaults, in which + * case we take the defaults. + */ + public Package() { + minClassVersion = Constants.JAVA_MIN_CLASS_VERSION; + maxClassVersion = Constants.JAVA_MAX_CLASS_VERSION; + packageVersion = null; + } + + + /* + * Typically used by the PackerImpl during before packing, the defaults are + * overridden by the users preferences. + */ + public Package(Version minClassVersion, Version maxClassVersion, Version packageVersion) { + // Fill in permitted range of major/minor version numbers. + this.minClassVersion = minClassVersion == null + ? Constants.JAVA_MIN_CLASS_VERSION + : minClassVersion; + this.maxClassVersion = maxClassVersion == null + ? Constants.JAVA_MAX_CLASS_VERSION + : maxClassVersion; + this.packageVersion = packageVersion; + } + + + public void reset() { + cp = new ConstantPool.IndexGroup(); + classes.clear(); + files.clear(); + BandStructure.nextSeqForDebug = 0; + observedHighestClassVersion = null; + } + + // Special empty versions of Code and InnerClasses, used for markers. + public static final Attribute.Layout attrCodeEmpty; + public static final Attribute.Layout attrBootstrapMethodsEmpty; + public static final Attribute.Layout attrInnerClassesEmpty; + public static final Attribute.Layout attrSourceFileSpecial; + public static final Map attrDefs; + static { + Map ad = new HashMap<>(3); + attrCodeEmpty = Attribute.define(ad, Constants.ATTR_CONTEXT_METHOD, + "Code", "").layout(); + attrBootstrapMethodsEmpty = Attribute.define(ad, Constants.ATTR_CONTEXT_CLASS, + "BootstrapMethods", "").layout(); + attrInnerClassesEmpty = Attribute.define(ad, Constants.ATTR_CONTEXT_CLASS, + "InnerClasses", "").layout(); + attrSourceFileSpecial = Attribute.define(ad, Constants.ATTR_CONTEXT_CLASS, + "SourceFile", "RUNH").layout(); + attrDefs = Collections.unmodifiableMap(ad); + } + + Version getDefaultClassVersion() { + return defaultClassVersion; + } + + /** Return the highest version number of all classes, + * or 0 if there are no classes. + */ + private void setHighestClassVersion() { + if (observedHighestClassVersion != null) + return; + Version res = Constants.JAVA_MIN_CLASS_VERSION; // initial low value + for (Class cls : classes) { + Version ver = cls.getVersion(); + if (res.lessThan(ver)) res = ver; + } + observedHighestClassVersion = res; + } + + Version getHighestClassVersion() { + setHighestClassVersion(); + return observedHighestClassVersion; + } + + // What Java classes are in this unit? + + ArrayList classes = new ArrayList<>(); + + public List getClasses() { + return classes; + } + + public final + class Class extends Attribute.Holder implements Comparable { + public Package getPackage() { return Package.this; } + + // Optional file characteristics and data source (a "class stub") + File file; + + // File header + int magic; + Version version; + + // Local constant pool (one-way mapping of index => package cp). + Entry[] cpMap; + + // Class header + //int flags; // in Attribute.Holder.this.flags + ClassEntry thisClass; + ClassEntry superClass; + ClassEntry[] interfaces; + + // Class parts + ArrayList fields; + ArrayList methods; + //ArrayList attributes; // in Attribute.Holder.this.attributes + // Note that InnerClasses may be collected at the package level. + ArrayList innerClasses; + ArrayList bootstrapMethods; + + Class(int flags, ClassEntry thisClass, ClassEntry superClass, ClassEntry[] interfaces) { + this.magic = Constants.JAVA_MAGIC; + this.version = defaultClassVersion; + this.flags = flags; + this.thisClass = thisClass; + this.superClass = superClass; + this.interfaces = interfaces; + + boolean added = classes.add(this); + assert(added); + } + + Class(String classFile) { + // A blank class; must be read with a ClassReader, etc. + initFile(newStub(classFile)); + } + + List getFields() { return fields == null ? noFields : fields; } + List getMethods() { return methods == null ? noMethods : methods; } + + public String getName() { + return thisClass.stringValue(); + } + + Version getVersion() { + return this.version; + } + + // Note: equals and hashCode are identity-based. + public int compareTo(Class that) { + String n0 = this.getName(); + String n1 = that.getName(); + return n0.compareTo(n1); + } + + String getObviousSourceFile() { + return Package.getObviousSourceFile(getName()); + } + + private void transformSourceFile(boolean minimize) { + // Replace "obvious" SourceFile by null. + Attribute olda = getAttribute(attrSourceFileSpecial); + if (olda == null) + return; // no SourceFile attr. + String obvious = getObviousSourceFile(); + List ref = new ArrayList<>(1); + olda.visitRefs(this, Constants.VRM_PACKAGE, ref); + Utf8Entry sfName = (Utf8Entry) ref.get(0); + Attribute a = olda; + if (sfName == null) { + if (minimize) { + // A pair of zero bytes. Cannot use predef. layout. + a = Attribute.find(Constants.ATTR_CONTEXT_CLASS, "SourceFile", "H"); + a = a.addContent(new byte[2]); + } else { + // Expand null attribute to the obvious string. + byte[] bytes = new byte[2]; + sfName = getRefString(obvious); + Object f = null; + f = Fixups.addRefWithBytes(f, bytes, sfName); + a = attrSourceFileSpecial.addContent(bytes, f); + } + } else if (obvious.equals(sfName.stringValue())) { + if (minimize) { + // Replace by an all-zero attribute. + a = attrSourceFileSpecial.addContent(new byte[2]); + } else { + assert(false); + } + } + if (a != olda) { + if (verbose > 2) + Utils.log.fine("recoding obvious SourceFile="+obvious); + List newAttrs = new ArrayList<>(getAttributes()); + int where = newAttrs.indexOf(olda); + newAttrs.set(where, a); + setAttributes(newAttrs); + } + } + + void minimizeSourceFile() { + transformSourceFile(true); + } + void expandSourceFile() { + transformSourceFile(false); + } + + protected Entry[] getCPMap() { + return cpMap; + } + + protected void setCPMap(Entry[] cpMap) { + this.cpMap = cpMap; + } + + boolean hasBootstrapMethods() { + return bootstrapMethods != null && !bootstrapMethods.isEmpty(); + } + + List getBootstrapMethods() { + return bootstrapMethods; + } + + BootstrapMethodEntry[] getBootstrapMethodMap() { + return (hasBootstrapMethods()) + ? bootstrapMethods.toArray(new BootstrapMethodEntry[bootstrapMethods.size()]) + : null; + } + + void setBootstrapMethods(Collection bsms) { + assert(bootstrapMethods == null); // do not do this twice + bootstrapMethods = new ArrayList<>(bsms); + } + + boolean hasInnerClasses() { + return innerClasses != null; + } + List getInnerClasses() { + return innerClasses; + } + + public void setInnerClasses(Collection ics) { + innerClasses = (ics == null) ? null : new ArrayList<>(ics); + // Edit the attribute list, if necessary. + Attribute a = getAttribute(attrInnerClassesEmpty); + if (innerClasses != null && a == null) + addAttribute(attrInnerClassesEmpty.canonicalInstance()); + else if (innerClasses == null && a != null) + removeAttribute(a); + } + + /** Given a global map of ICs (keyed by thisClass), + * compute the subset of its Map.values which are + * required to be present in the local InnerClasses + * attribute. Perform this calculation without + * reference to any actual InnerClasses attribute. + *

+ * The order of the resulting list is consistent + * with that of Package.this.allInnerClasses. + */ + public List computeGloballyImpliedICs() { + Set cpRefs = new HashSet<>(); + { // This block temporarily displaces this.innerClasses. + ArrayList innerClassesSaved = innerClasses; + innerClasses = null; // ignore for the moment + visitRefs(Constants.VRM_CLASSIC, cpRefs); + innerClasses = innerClassesSaved; + } + ConstantPool.completeReferencesIn(cpRefs, true); + + Set icRefs = new HashSet<>(); + for (Entry e : cpRefs) { + // Restrict cpRefs to InnerClasses entries only. + if (!(e instanceof ClassEntry)) continue; + // For every IC reference, add its outers also. + while (e != null) { + InnerClass ic = getGlobalInnerClass(e); + if (ic == null) break; + if (!icRefs.add(e)) break; + e = ic.outerClass; + // If we add A$B$C to the mix, we must also add A$B. + } + } + // This loop is structured this way so as to accumulate + // entries into impliedICs in an order which reflects + // the order of allInnerClasses. + ArrayList impliedICs = new ArrayList<>(); + for (InnerClass ic : allInnerClasses) { + // This one is locally relevant if it describes + // a member of the current class, or if the current + // class uses it somehow. In the particular case + // where thisClass is an inner class, it will already + // be a member of icRefs. + if (icRefs.contains(ic.thisClass) + || ic.outerClass == this.thisClass) { + // Add every relevant class to the IC attribute: + if (verbose > 1) + Utils.log.fine("Relevant IC: "+ic); + impliedICs.add(ic); + } + } + return impliedICs; + } + + // Helper for both minimizing and expanding. + // Computes a symmetric difference. + private List computeICdiff() { + List impliedICs = computeGloballyImpliedICs(); + List actualICs = getInnerClasses(); + if (actualICs == null) + actualICs = Collections.emptyList(); + + // Symmetric difference is calculated from I, A like this: + // diff = (I+A) - (I*A) + // Note that the center C is unordered, but the result + // preserves the original ordering of I and A. + // + // Class file rules require that outers precede inners. + // So, add I before A, in case A$B$Z is local, but A$B + // is implicit. The reverse is never the case. + if (actualICs.isEmpty()) { + return impliedICs; + // Diff is I since A is empty. + } + if (impliedICs.isEmpty()) { + return actualICs; + // Diff is A since I is empty. + } + // (I*A) is non-trivial + Set center = new HashSet<>(actualICs); + center.retainAll(new HashSet<>(impliedICs)); + impliedICs.addAll(actualICs); + impliedICs.removeAll(center); + // Diff is now I^A = (I+A)-(I*A). + return impliedICs; + } + + /** When packing, anticipate the effect of expandLocalICs. + * Replace the local ICs by their symmetric difference + * with the globally implied ICs for this class; if this + * difference is empty, remove the local ICs altogether. + *

+ * An empty local IC attribute is reserved to signal + * the unpacker to delete the attribute altogether, + * so a missing local IC attribute signals the unpacker + * to use the globally implied ICs changed. + */ + void minimizeLocalICs() { + List diff = computeICdiff(); + List actualICs = innerClasses; + List localICs; // will be the diff, modulo edge cases + if (diff.isEmpty()) { + // No diff, so transmit no attribute. + localICs = null; + if (actualICs != null && actualICs.isEmpty()) { + // Odd case: No implied ICs, and a zero length attr. + // Do not support it directly. + if (verbose > 0) + Utils.log.info("Warning: Dropping empty InnerClasses attribute from "+this); + } + } else if (actualICs == null) { + // No local IC attribute, even though some are implied. + // Signal with trivial attribute. + localICs = Collections.emptyList(); + } else { + // Transmit a non-empty diff, which will create + // a local ICs attribute. + localICs = diff; + } + // Reduce the set to the symmetric difference. + setInnerClasses(localICs); + if (verbose > 1 && localICs != null) + Utils.log.fine("keeping local ICs in "+this+": "+localICs); + } + + /** When unpacking, undo the effect of minimizeLocalICs. + * Must return negative if any IC tuples may have been deleted. + * Otherwise, return positive if any IC tuples were added. + */ + int expandLocalICs() { + List localICs = innerClasses; + List actualICs; + int changed; + if (localICs == null) { + // Diff was empty. (Common case.) + List impliedICs = computeGloballyImpliedICs(); + if (impliedICs.isEmpty()) { + actualICs = null; + changed = 0; + } else { + actualICs = impliedICs; + changed = 1; // added more tuples + } + } else if (localICs.isEmpty()) { + // It was a non-empty diff, but the local ICs were absent. + actualICs = null; + // [] => null, no tuple change, but attribute deletion. + changed = -1; + } else { + // Non-trivial diff was transmitted. + actualICs = computeICdiff(); + // If we only added more ICs, return +1. + changed = actualICs.containsAll(localICs)? +1: -1; + } + setInnerClasses(actualICs); + return changed; + } + + public abstract + class Member extends Attribute.Holder implements Comparable { + DescriptorEntry descriptor; + + protected Member(int flags, DescriptorEntry descriptor) { + this.flags = flags; + this.descriptor = descriptor; + } + + public Class thisClass() { return Class.this; } + + public DescriptorEntry getDescriptor() { + return descriptor; + } + public String getName() { + return descriptor.nameRef.stringValue(); + } + public String getType() { + return descriptor.typeRef.stringValue(); + } + + protected Entry[] getCPMap() { + return cpMap; + } + protected void visitRefs(int mode, Collection refs) { + if (verbose > 2) Utils.log.fine("visitRefs "+this); + // Careful: The descriptor is used by the package, + // but the classfile breaks it into component refs. + if (mode == Constants.VRM_CLASSIC) { + refs.add(descriptor.nameRef); + refs.add(descriptor.typeRef); + } else { + refs.add(descriptor); + } + // Handle attribute list: + super.visitRefs(mode, refs); + } + + public String toString() { + return Class.this + "." + descriptor.prettyString(); + } + } + + public + class Field extends Member { + // Order is significant for fields: It is visible to reflection. + int order; + + public Field(int flags, DescriptorEntry descriptor) { + super(flags, descriptor); + assert(!descriptor.isMethod()); + if (fields == null) + fields = new ArrayList<>(); + boolean added = fields.add(this); + assert(added); + order = fields.size(); + } + + public byte getLiteralTag() { + return descriptor.getLiteralTag(); + } + + public int compareTo(Member o) { + Field that = (Field)o; + return this.order - that.order; + } + } + + public + class Method extends Member { + // Code attribute is specially hardwired. + Code code; + + public Method(int flags, DescriptorEntry descriptor) { + super(flags, descriptor); + assert(descriptor.isMethod()); + if (methods == null) + methods = new ArrayList<>(); + boolean added = methods.add(this); + assert(added); + } + + public void trimToSize() { + super.trimToSize(); + if (code != null) + code.trimToSize(); + } + + public int getArgumentSize() { + int argSize = descriptor.typeRef.computeSize(true); + int thisSize = Modifier.isStatic(flags) ? 0 : 1; + return thisSize + argSize; + } + + // Sort methods in a canonical order (by type, then by name). + public int compareTo(Member o) { + Method that = (Method)o; + return this.getDescriptor().compareTo(that.getDescriptor()); + } + + public void strip(String attrName) { + if ("Code".equals(attrName)) + code = null; + if (code != null) + code.strip(attrName); + super.strip(attrName); + } + protected void visitRefs(int mode, Collection refs) { + super.visitRefs(mode, refs); + if (code != null) { + if (mode == Constants.VRM_CLASSIC) { + refs.add(getRefString("Code")); + } + code.visitRefs(mode, refs); + } + } + } + + public void trimToSize() { + super.trimToSize(); + for (int isM = 0; isM <= 1; isM++) { + ArrayList members = (isM == 0) ? fields : methods; + if (members == null) continue; + members.trimToSize(); + for (Member m : members) { + m.trimToSize(); + } + } + if (innerClasses != null) { + innerClasses.trimToSize(); + } + } + + public void strip(String attrName) { + if ("InnerClass".equals(attrName)) + innerClasses = null; + for (int isM = 0; isM <= 1; isM++) { + ArrayList members = (isM == 0) ? fields : methods; + if (members == null) continue; + for (Member m : members) { + m.strip(attrName); + } + } + super.strip(attrName); + } + + protected void visitRefs(int mode, Collection refs) { + if (verbose > 2) Utils.log.fine("visitRefs "+this); + refs.add(thisClass); + refs.add(superClass); + refs.addAll(Arrays.asList(interfaces)); + for (int isM = 0; isM <= 1; isM++) { + ArrayList members = (isM == 0) ? fields : methods; + if (members == null) continue; + for (Member m : members) { + boolean ok = false; + try { + m.visitRefs(mode, refs); + ok = true; + } finally { + if (!ok) + Utils.log.warning("Error scanning "+m); + } + } + } + visitInnerClassRefs(mode, refs); + // Handle attribute list: + super.visitRefs(mode, refs); + } + + protected void visitInnerClassRefs(int mode, Collection refs) { + Package.visitInnerClassRefs(innerClasses, mode, refs); + } + + // Hook called by ClassReader when it's done. + void finishReading() { + trimToSize(); + maybeChooseFileName(); + } + + public void initFile(File file) { + assert(this.file == null); // set-once + if (file == null) { + // Build a trivial stub. + file = newStub(canonicalFileName()); + } + this.file = file; + assert(file.isClassStub()); + file.stubClass = this; + maybeChooseFileName(); + } + + public void maybeChooseFileName() { + if (thisClass == null) { + return; // do not choose yet + } + String canonName = canonicalFileName(); + if (file.nameString.equals("")) { + file.nameString = canonName; + } + if (file.nameString.equals(canonName)) { + // The file name is predictable. Transmit "". + file.name = getRefString(""); + return; + } + // If name has not yet been looked up, find it now. + if (file.name == null) { + file.name = getRefString(file.nameString); + } + } + + public String canonicalFileName() { + if (thisClass == null) return null; + return thisClass.stringValue() + ".class"; + } + + public java.io.File getFileName(java.io.File parent) { + String name = file.name.stringValue(); + if (name.equals("")) + name = canonicalFileName(); + String fname = name.replace('/', java.io.File.separatorChar); + return new java.io.File(parent, fname); + } + public java.io.File getFileName() { + return getFileName(null); + } + + public String toString() { + return thisClass.stringValue(); + } + } + + void addClass(Class c) { + assert(c.getPackage() == this); + boolean added = classes.add(c); + assert(added); + // Make sure the class is represented in the total file order: + if (c.file == null) c.initFile(null); + addFile(c.file); + } + + // What non-class files are in this unit? + ArrayList files = new ArrayList<>(); + + public List getFiles() { + return files; + } + + public List getClassStubs() { + List classStubs = new ArrayList<>(classes.size()); + for (Class cls : classes) { + assert(cls.file.isClassStub()); + classStubs.add(cls.file); + } + return classStubs; + } + + public final class File implements Comparable { + String nameString; // true name of this file + Utf8Entry name; + int modtime = Constants.NO_MODTIME; + int options = 0; // random flag bits, such as deflate_hint + Class stubClass; // if this is a stub, here's the class + ArrayList prepend = new ArrayList<>(); // list of byte[] + ByteArrayOutputStream append = new ByteArrayOutputStream(); + + File(Utf8Entry name) { + this.name = name; + this.nameString = name.stringValue(); + // caller must fill in contents + } + File(String nameString) { + nameString = fixupFileName(nameString); + this.name = getRefString(nameString); + this.nameString = name.stringValue(); + } + + public boolean isDirectory() { + // JAR directory. Useless. + return nameString.endsWith("/"); + } + public boolean isClassStub() { + return (options & Constants.FO_IS_CLASS_STUB) != 0; + } + public Class getStubClass() { + assert(isClassStub()); + assert(stubClass != null); + return stubClass; + } + public boolean isTrivialClassStub() { + return isClassStub() + && name.stringValue().equals("") + && (modtime == Constants.NO_MODTIME || modtime == default_modtime) + && (options &~ Constants.FO_IS_CLASS_STUB) == 0; + } + + // The nameString is the key. Ignore other things. + // (Note: The name might be "", in the case of a trivial class stub.) + public boolean equals(Object o) { + if (o == null || (o.getClass() != File.class)) + return false; + File that = (File)o; + return that.nameString.equals(this.nameString); + } + public int hashCode() { + return nameString.hashCode(); + } + // Simple alphabetic sort. PackageWriter uses a better comparator. + public int compareTo(File that) { + return this.nameString.compareTo(that.nameString); + } + public String toString() { + return nameString+"{" + +(isClassStub()?"*":"") + +(BandStructure.testBit(options,Constants.FO_DEFLATE_HINT)?"@":"") + +(modtime==Constants.NO_MODTIME?"":"M"+modtime) + +(getFileLength()==0?"":"["+getFileLength()+"]") + +"}"; + } + + public java.io.File getFileName() { + return getFileName(null); + } + public java.io.File getFileName(java.io.File parent) { + String lname = this.nameString; + //if (name.startsWith("./")) name = name.substring(2); + String fname = lname.replace('/', java.io.File.separatorChar); + return new java.io.File(parent, fname); + } + + public void addBytes(byte[] bytes) { + addBytes(bytes, 0, bytes.length); + } + public void addBytes(byte[] bytes, int off, int len) { + if (((append.size() | len) << 2) < 0) { + prepend.add(append.toByteArray()); + append.reset(); + } + append.write(bytes, off, len); + } + public long getFileLength() { + long len = 0; + if (prepend == null || append == null) return 0; + for (byte[] block : prepend) { + len += block.length; + } + len += append.size(); + return len; + } + public void writeTo(OutputStream out) throws IOException { + if (prepend == null || append == null) return; + for (byte[] block : prepend) { + out.write(block); + } + append.writeTo(out); + } + public void readFrom(InputStream in) throws IOException { + byte[] buf = new byte[1 << 16]; + int nr; + while ((nr = in.read(buf)) > 0) { + addBytes(buf, 0, nr); + } + } + public InputStream getInputStream() { + InputStream in = new ByteArrayInputStream(append.toByteArray()); + if (prepend.isEmpty()) return in; + List isa = new ArrayList<>(prepend.size()+1); + for (byte[] bytes : prepend) { + isa.add(new ByteArrayInputStream(bytes)); + } + isa.add(in); + return new SequenceInputStream(Collections.enumeration(isa)); + } + + protected void visitRefs(int mode, Collection refs) { + assert(name != null); + refs.add(name); + } + } + + File newStub(String classFileNameString) { + File stub = new File(classFileNameString); + stub.options |= Constants.FO_IS_CLASS_STUB; + stub.prepend = null; + stub.append = null; // do not collect data + return stub; + } + + private static String fixupFileName(String name) { + String fname = name.replace(java.io.File.separatorChar, '/'); + if (fname.startsWith("/")) { + throw new IllegalArgumentException("absolute file name "+fname); + } + return fname; + } + + void addFile(File file) { + boolean added = files.add(file); + assert(added); + } + + // Is there a globally declared table of inner classes? + List allInnerClasses = new ArrayList<>(); + Map allInnerClassesByThis; + + public + List getAllInnerClasses() { + return allInnerClasses; + } + + public + void setAllInnerClasses(Collection ics) { + assert(ics != allInnerClasses); + allInnerClasses.clear(); + allInnerClasses.addAll(ics); + + // Make an index: + allInnerClassesByThis = new HashMap<>(allInnerClasses.size()); + for (InnerClass ic : allInnerClasses) { + Object pic = allInnerClassesByThis.put(ic.thisClass, ic); + assert(pic == null); // caller must ensure key uniqueness! + } + } + + /** Return a global inner class record for the given thisClass. */ + public + InnerClass getGlobalInnerClass(Entry thisClass) { + assert(thisClass instanceof ClassEntry); + return allInnerClassesByThis.get(thisClass); + } + + static + class InnerClass implements Comparable { + final ClassEntry thisClass; + final ClassEntry outerClass; + final Utf8Entry name; + final int flags; + + // Can name and outerClass be derived from thisClass? + final boolean predictable; + + // About 30% of inner classes are anonymous (in rt.jar). + // About 60% are class members; the rest are named locals. + // Nearly all have predictable outers and names. + + InnerClass(ClassEntry thisClass, ClassEntry outerClass, + Utf8Entry name, int flags) { + this.thisClass = thisClass; + this.outerClass = outerClass; + this.name = name; + this.flags = flags; + this.predictable = computePredictable(); + } + + private boolean computePredictable() { + //System.out.println("computePredictable "+outerClass+" "+this.name); + String[] parse = parseInnerClassName(thisClass.stringValue()); + if (parse == null) return false; + String pkgOuter = parse[0]; + //String number = parse[1]; + String lname = parse[2]; + String haveName = (this.name == null) ? null : this.name.stringValue(); + String haveOuter = (outerClass == null) ? null : outerClass.stringValue(); + boolean lpredictable = (lname == haveName && pkgOuter == haveOuter); + //System.out.println("computePredictable => "+predictable); + return lpredictable; + } + + public boolean equals(Object o) { + if (o == null || o.getClass() != InnerClass.class) + return false; + InnerClass that = (InnerClass)o; + return eq(this.thisClass, that.thisClass) + && eq(this.outerClass, that.outerClass) + && eq(this.name, that.name) + && this.flags == that.flags; + } + private static boolean eq(Object x, Object y) { + return (x == null)? y == null: x.equals(y); + } + public int hashCode() { + return thisClass.hashCode(); + } + public int compareTo(InnerClass that) { + return this.thisClass.compareTo(that.thisClass); + } + + protected void visitRefs(int mode, Collection refs) { + refs.add(thisClass); + if (mode == Constants.VRM_CLASSIC || !predictable) { + // If the name can be demangled, the package omits + // the products of demangling. Otherwise, include them. + refs.add(outerClass); + refs.add(name); + } + } + + public String toString() { + return thisClass.stringValue(); + } + } + + // Helper for building InnerClasses attributes. + private static + void visitInnerClassRefs(Collection innerClasses, int mode, Collection refs) { + if (innerClasses == null) { + return; // no attribute; nothing to do + } + if (mode == Constants.VRM_CLASSIC) { + refs.add(getRefString("InnerClasses")); + } + if (innerClasses.size() > 0) { + // Count the entries themselves: + for (InnerClass c : innerClasses) { + c.visitRefs(mode, refs); + } + } + } + + static String[] parseInnerClassName(String n) { + //System.out.println("parseInnerClassName "+n); + String pkgOuter, number, name; + int dollar1, dollar2; // pointers to $ in the pattern + // parse n = (/)*($)?($)? + int nlen = n.length(); + int pkglen = lastIndexOf(SLASH_MIN, SLASH_MAX, n, n.length()) + 1; + dollar2 = lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, n, n.length()); + if (dollar2 < pkglen) return null; + if (isDigitString(n, dollar2+1, nlen)) { + // n = (/)*$ + number = n.substring(dollar2+1, nlen); + name = null; + dollar1 = dollar2; + } else if ((dollar1 + = lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, n, dollar2-1)) + > pkglen + && isDigitString(n, dollar1+1, dollar2)) { + // n = (/)*$$ + number = n.substring(dollar1+1, dollar2); + name = n.substring(dollar2+1, nlen).intern(); + } else { + // n = (/)*$ + dollar1 = dollar2; + number = null; + name = n.substring(dollar2+1, nlen).intern(); + } + if (number == null) + pkgOuter = n.substring(0, dollar1).intern(); + else + pkgOuter = null; + //System.out.println("parseInnerClassName parses "+pkgOuter+" "+number+" "+name); + return new String[] { pkgOuter, number, name }; + } + + private static final int SLASH_MIN = '.'; + private static final int SLASH_MAX = '/'; + private static final int DOLLAR_MIN = 0; + private static final int DOLLAR_MAX = '-'; + static { + assert(lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, "x$$y$", 4) == 2); + assert(lastIndexOf(SLASH_MIN, SLASH_MAX, "x//y/", 4) == 2); + } + + private static int lastIndexOf(int chMin, int chMax, String str, int pos) { + for (int i = pos; --i >= 0; ) { + int ch = str.charAt(i); + if (ch >= chMin && ch <= chMax) { + return i; + } + } + return -1; + } + + private static boolean isDigitString(String x, int beg, int end) { + if (beg == end) return false; // null string + for (int i = beg; i < end; i++) { + char ch = x.charAt(i); + if (!(ch >= '0' && ch <= '9')) return false; + } + return true; + } + + static String getObviousSourceFile(String className) { + String n = className; + int pkglen = lastIndexOf(SLASH_MIN, SLASH_MAX, n, n.length()) + 1; + n = n.substring(pkglen); + int cutoff = n.length(); + for (;;) { + // Work backwards, finding all '$', '#', etc. + int dollar2 = lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, n, cutoff-1); + if (dollar2 < 0) + break; + cutoff = dollar2; + if (cutoff == 0) + break; + } + String obvious = n.substring(0, cutoff)+".java"; + return obvious; + } +/* + static { + assert(getObviousSourceFile("foo").equals("foo.java")); + assert(getObviousSourceFile("foo/bar").equals("bar.java")); + assert(getObviousSourceFile("foo/bar$baz").equals("bar.java")); + assert(getObviousSourceFile("foo/bar#baz#1").equals("bar.java")); + assert(getObviousSourceFile("foo.bar.baz#1").equals("baz.java")); + } +*/ + + static Utf8Entry getRefString(String s) { + return ConstantPool.getUtf8Entry(s); + } + + static LiteralEntry getRefLiteral(Comparable s) { + return ConstantPool.getLiteralEntry(s); + } + + void stripAttributeKind(String what) { + // what is one of { Debug, Compile, Constant, Exceptions, InnerClasses } + if (verbose > 0) + Utils.log.info("Stripping "+what.toLowerCase()+" data and attributes..."); + switch (what) { + case "Debug": + strip("SourceFile"); + strip("LineNumberTable"); + strip("LocalVariableTable"); + strip("LocalVariableTypeTable"); + break; + case "Compile": + // Keep the inner classes normally. + // Although they have no effect on execution, + // the Reflection API exposes them, and JCK checks them. + // NO: // strip("InnerClasses"); + strip("Deprecated"); + strip("Synthetic"); + break; + case "Exceptions": + // Keep the exceptions normally. + // Although they have no effect on execution, + // the Reflection API exposes them, and JCK checks them. + strip("Exceptions"); + break; + case "Constant": + stripConstantFields(); + break; + } + } + + public void trimToSize() { + classes.trimToSize(); + for (Class c : classes) { + c.trimToSize(); + } + files.trimToSize(); + } + + public void strip(String attrName) { + for (Class c : classes) { + c.strip(attrName); + } + } + + public void stripConstantFields() { + for (Class c : classes) { + for (Iterator j = c.fields.iterator(); j.hasNext(); ) { + Class.Field f = j.next(); + if (Modifier.isFinal(f.flags) + // do not strip non-static finals: + && Modifier.isStatic(f.flags) + && f.getAttribute("ConstantValue") != null + && !f.getName().startsWith("serial")) { + if (verbose > 2) { + Utils.log.fine(">> Strip "+this+" ConstantValue"); + j.remove(); + } + } + } + } + } + + protected void visitRefs(int mode, Collection refs) { + for ( Class c : classes) { + c.visitRefs(mode, refs); + } + if (mode != Constants.VRM_CLASSIC) { + for (File f : files) { + f.visitRefs(mode, refs); + } + visitInnerClassRefs(allInnerClasses, mode, refs); + } + } + + // Use this before writing the package file. + // It sorts files into a new order which seems likely to + // compress better. It also moves classes to the end of the + // file order. It also removes JAR directory entries, which + // are useless. + void reorderFiles(boolean keepClassOrder, boolean stripDirectories) { + // First reorder the classes, if that is allowed. + if (!keepClassOrder) { + // In one test with rt.jar, this trick gained 0.7% + Collections.sort(classes); + } + + // Remove stubs from resources; maybe we'll add them on at the end, + // if there are some non-trivial ones. The best case is that + // modtimes and options are not transmitted, and the stub files + // for class files do not need to be transmitted at all. + // Also + List stubs = getClassStubs(); + for (Iterator i = files.iterator(); i.hasNext(); ) { + File file = i.next(); + if (file.isClassStub() || + (stripDirectories && file.isDirectory())) { + i.remove(); + } + } + + // Sort the remaining non-class files. + // We sort them by file type. + // This keeps files of similar format near each other. + // Put class files at the end, keeping their fixed order. + // Be sure the JAR file's required manifest stays at the front. (4893051) + Collections.sort(files, new Comparator() { + public int compare(File r0, File r1) { + // Get the file name. + String f0 = r0.nameString; + String f1 = r1.nameString; + if (f0.equals(f1)) return 0; + if (JarFile.MANIFEST_NAME.equals(f0)) return 0-1; + if (JarFile.MANIFEST_NAME.equals(f1)) return 1-0; + // Extract file basename. + String n0 = f0.substring(1+f0.lastIndexOf('/')); + String n1 = f1.substring(1+f1.lastIndexOf('/')); + // Extract basename extension. + String x0 = n0.substring(1+n0.lastIndexOf('.')); + String x1 = n1.substring(1+n1.lastIndexOf('.')); + int r; + // Primary sort key is file extension. + r = x0.compareTo(x1); + if (r != 0) return r; + r = f0.compareTo(f1); + return r; + } + }); + + // Add back the class stubs after sorting, before trimStubs. + files.addAll(stubs); + } + + void trimStubs() { + // Restore enough non-trivial stubs to carry the needed class modtimes. + for (ListIterator i = files.listIterator(files.size()); i.hasPrevious(); ) { + File file = i.previous(); + if (!file.isTrivialClassStub()) { + if (verbose > 1) + Utils.log.fine("Keeping last non-trivial "+file); + break; + } + if (verbose > 2) + Utils.log.fine("Removing trivial "+file); + i.remove(); + } + + if (verbose > 0) { + Utils.log.info("Transmitting "+files.size()+" files, including per-file data for "+getClassStubs().size()+" classes out of "+classes.size()); + } + } + + // Use this before writing the package file. + void buildGlobalConstantPool(Set requiredEntries) { + if (verbose > 1) + Utils.log.fine("Checking for unused CP entries"); + requiredEntries.add(getRefString("")); // uconditionally present + visitRefs(Constants.VRM_PACKAGE, requiredEntries); + ConstantPool.completeReferencesIn(requiredEntries, false); + if (verbose > 1) + Utils.log.fine("Sorting CP entries"); + Index cpAllU = ConstantPool.makeIndex("unsorted", requiredEntries); + Index[] byTagU = ConstantPool.partitionByTag(cpAllU); + for (int i = 0; i < ConstantPool.TAGS_IN_ORDER.length; i++) { + byte tag = ConstantPool.TAGS_IN_ORDER[i]; + // Work on all entries of a given kind. + Index ix = byTagU[tag]; + if (ix == null) continue; + ConstantPool.sort(ix); + cp.initIndexByTag(tag, ix); + byTagU[tag] = null; // done with it + } + for (int i = 0; i < byTagU.length; i++) { + Index ix = byTagU[i]; + assert(ix == null); // all consumed + } + for (int i = 0; i < ConstantPool.TAGS_IN_ORDER.length; i++) { + byte tag = ConstantPool.TAGS_IN_ORDER[i]; + Index ix = cp.getIndexByTag(tag); + assert(ix.assertIsSorted()); + if (verbose > 2) Utils.log.fine(ix.dumpString()); + } + } + + // Use this before writing the class files. + void ensureAllClassFiles() { + Set fileSet = new HashSet<>(files); + for (Class cls : classes) { + // Add to the end of ths list: + if (!fileSet.contains(cls.file)) + files.add(cls.file); + } + } + + static final List noObjects = Arrays.asList(new Object[0]); + static final List noFields = Arrays.asList(new Class.Field[0]); + static final List noMethods = Arrays.asList(new Class.Method[0]); + static final List noInnerClasses = Arrays.asList(new InnerClass[0]); + + protected static final class Version { + + public final short major; + public final short minor; + + private Version(short major, short minor) { + this.major = major; + this.minor = minor; + } + + public String toString() { + return major + "." + minor; + } + + public boolean equals(Object that) { + return that instanceof Version + && major == ((Version)that).major + && minor == ((Version)that).minor; + } + + public int intValue() { + return (major << 16) + minor; + } + + public int hashCode() { + return (major << 16) + 7 + minor; + } + + public static Version of(int major, int minor) { + return new Version((short)major, (short)minor); + } + + public static Version of(byte[] bytes) { + int minor = ((bytes[0] & 0xFF) << 8) | (bytes[1] & 0xFF); + int major = ((bytes[2] & 0xFF) << 8) | (bytes[3] & 0xFF); + return new Version((short)major, (short)minor); + } + + public static Version of(int major_minor) { + short minor = (short)major_minor; + short major = (short)(major_minor >>> 16); + return new Version(major, minor); + } + + public static Version makeVersion(PropMap props, String partialKey) { + int min = props.getInteger(Utils.COM_PREFIX + + partialKey + ".minver", -1); + int maj = props.getInteger(Utils.COM_PREFIX + + partialKey + ".majver", -1); + return min >= 0 && maj >= 0 ? Version.of(maj, min) : null; + } + public byte[] asBytes() { + byte[] bytes = { + (byte) (minor >> 8), (byte) minor, + (byte) (major >> 8), (byte) major + }; + return bytes; + } + public int compareTo(Version that) { + return this.intValue() - that.intValue(); + } + + public boolean lessThan(Version that) { + return compareTo(that) < 0 ; + } + + public boolean greaterThan(Version that) { + return compareTo(that) > 0 ; + } + } +} diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/PackageReader.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/PackageReader.java new file mode 100644 index 000000000..ede5caedc --- /dev/null +++ b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/PackageReader.java @@ -0,0 +1,2371 @@ +/* + * Copyright (c) 2001, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package net.fabricmc.shade.com.sun.java.util.jar.pack; + +import java.io.ByteArrayOutputStream; +import java.io.EOFException; +import java.io.PrintStream; +import java.io.FilterInputStream; +import java.io.BufferedInputStream; +import java.io.InputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Map; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Set; + +/** + * Reader for a package file. + * + * @see PackageWriter + * @author John Rose + */ +class PackageReader extends BandStructure { + Package pkg; + byte[] bytes; + LimitedBuffer in; + Package.Version packageVersion; + + PackageReader(Package pkg, InputStream in) throws IOException { + this.pkg = pkg; + this.in = new LimitedBuffer(in); + } + + /** A buffered input stream which is careful not to + * read its underlying stream ahead of a given mark, + * called the 'readLimit'. This property declares + * the maximum number of characters that future reads + * can consume from the underlying stream. + */ + static + class LimitedBuffer extends BufferedInputStream { + long served; // total number of charburgers served + int servedPos; // ...as of this value of super.pos + long limit; // current declared limit + long buffered; + public boolean atLimit() { + boolean z = (getBytesServed() == limit); + assert(!z || limit == buffered); + return z; + } + public long getBytesServed() { + return served + (pos - servedPos); + } + public void setReadLimit(long newLimit) { + if (newLimit == -1) + limit = -1; + else + limit = getBytesServed() + newLimit; + } + public long getReadLimit() { + if (limit == -1) + return limit; + else + return limit - getBytesServed(); + } + public int read() throws IOException { + if (pos < count) { + // fast path + return buf[pos++] & 0xFF; + } + served += (pos - servedPos); + int ch = super.read(); + servedPos = pos; + if (ch >= 0) served += 1; + assert(served <= limit || limit == -1); + return ch; + } + public int read(byte b[], int off, int len) throws IOException { + served += (pos - servedPos); + int nr = super.read(b, off, len); + servedPos = pos; + if (nr >= 0) served += nr; + //assert(served <= limit || limit == -1); + return nr; + } + public long skip(long n) throws IOException { + throw new RuntimeException("no skipping"); + } + LimitedBuffer(InputStream originalIn) { + super(null, 1<<14); + servedPos = pos; + super.in = new FilterInputStream(originalIn) { + public int read() throws IOException { + if (buffered == limit) + return -1; + ++buffered; + return super.read(); + } + public int read(byte b[], int off, int len) throws IOException { + if (buffered == limit) + return -1; + if (limit != -1) { + long remaining = limit - buffered; + if (len > remaining) + len = (int)remaining; + } + int nr = super.read(b, off, len); + if (nr >= 0) buffered += nr; + return nr; + } + }; + } + } + + void read() throws IOException { + boolean ok = false; + try { + // pack200_archive: + // file_header + // *band_headers :BYTE1 + // cp_bands + // attr_definition_bands + // ic_bands + // class_bands + // bc_bands + // file_bands + readFileHeader(); + readBandHeaders(); + readConstantPool(); // cp_bands + readAttrDefs(); + readInnerClasses(); + Package.Class[] classes = readClasses(); + readByteCodes(); + readFiles(); // file_bands + assert(archiveSize1 == 0 || in.atLimit()); + assert(archiveSize1 == 0 || + in.getBytesServed() == archiveSize0+archiveSize1); + all_bands.doneDisbursing(); + + // As a post-pass, build constant pools and inner classes. + for (int i = 0; i < classes.length; i++) { + reconstructClass(classes[i]); + } + + ok = true; + } catch (Exception ee) { + Utils.log.warning("Error on input: "+ee, ee); + if (verbose > 0) + Utils.log.info("Stream offsets:"+ + " served="+in.getBytesServed()+ + " buffered="+in.buffered+ + " limit="+in.limit); + //if (verbose > 0) ee.printStackTrace(); + if (ee instanceof IOException) throw (IOException)ee; + if (ee instanceof RuntimeException) throw (RuntimeException)ee; + throw new Error("error unpacking", ee); + } + } + + // Temporary count values, until band decoding gets rolling. + int[] tagCount = new int[Constants.CONSTANT_Limit]; + int numFiles; + int numAttrDefs; + int numInnerClasses; + int numClasses; + + void readFileHeader() throws IOException { + // file_header: + // archive_magic archive_header + readArchiveMagic(); + readArchiveHeader(); + } + + // Local routine used to parse fixed-format scalars + // in the file_header: + private int getMagicInt32() throws IOException { + int res = 0; + for (int i = 0; i < 4; i++) { + res <<= 8; + res |= (archive_magic.getByte() & 0xFF); + } + return res; + } + + static final int MAGIC_BYTES = 4; + + void readArchiveMagic() throws IOException { + // Read a minimum of bytes in the first gulp. + in.setReadLimit(MAGIC_BYTES + AH_LENGTH_MIN); + + // archive_magic: + // #archive_magic_word :BYTE1[4] + archive_magic.expectLength(MAGIC_BYTES); + archive_magic.readFrom(in); + + // read and check magic numbers: + int magic = getMagicInt32(); + if (pkg.magic != magic) { + throw new IOException("Unexpected package magic number: got " + + magic + "; expected " + pkg.magic); + } + archive_magic.doneDisbursing(); + } + + // Fixed 6211177, converted to throw IOException + void checkArchiveVersion() throws IOException { + Package.Version versionFound = null; + for (Package.Version v : new Package.Version[] { + Constants.JAVA8_PACKAGE_VERSION, + Constants.JAVA7_PACKAGE_VERSION, + Constants.JAVA6_PACKAGE_VERSION, + Constants.JAVA5_PACKAGE_VERSION + }) { + if (packageVersion.equals(v)) { + versionFound = v; + break; + } + } + if (versionFound == null) { + String expVer = Constants.JAVA8_PACKAGE_VERSION.toString() + + "OR" + + Constants.JAVA7_PACKAGE_VERSION.toString() + + " OR " + + Constants.JAVA6_PACKAGE_VERSION.toString() + + " OR " + + Constants.JAVA5_PACKAGE_VERSION.toString(); + throw new IOException("Unexpected package minor version: got " + + packageVersion.toString() + "; expected " + expVer); + } + } + + void readArchiveHeader() throws IOException { + // archive_header: + // #archive_minver :UNSIGNED5[1] + // #archive_majver :UNSIGNED5[1] + // #archive_options :UNSIGNED5[1] + // (archive_file_counts) ** (#have_file_headers) + // (archive_special_counts) ** (#have_special_formats) + // cp_counts + // class_counts + // + // archive_file_counts: + // #archive_size_hi :UNSIGNED5[1] + // #archive_size_lo :UNSIGNED5[1] + // #archive_next_count :UNSIGNED5[1] + // #archive_modtime :UNSIGNED5[1] + // #file_count :UNSIGNED5[1] + // + // class_counts: + // #ic_count :UNSIGNED5[1] + // #default_class_minver :UNSIGNED5[1] + // #default_class_majver :UNSIGNED5[1] + // #class_count :UNSIGNED5[1] + // + // archive_special_counts: + // #band_headers_size :UNSIGNED5[1] + // #attr_definition_count :UNSIGNED5[1] + // + archive_header_0.expectLength(AH_LENGTH_0); + archive_header_0.readFrom(in); + + int minver = archive_header_0.getInt(); + int majver = archive_header_0.getInt(); + packageVersion = Package.Version.of(majver, minver); + checkArchiveVersion(); + this.initHighestClassVersion(Constants.JAVA7_MAX_CLASS_VERSION); + + archiveOptions = archive_header_0.getInt(); + archive_header_0.doneDisbursing(); + + // detect archive optional fields in archive header + boolean haveSpecial = testBit(archiveOptions, Constants.AO_HAVE_SPECIAL_FORMATS); + boolean haveFiles = testBit(archiveOptions, Constants.AO_HAVE_FILE_HEADERS); + boolean haveNumbers = testBit(archiveOptions, Constants.AO_HAVE_CP_NUMBERS); + boolean haveCPExtra = testBit(archiveOptions, Constants.AO_HAVE_CP_EXTRAS); + initAttrIndexLimit(); + + // now we are ready to use the data: + archive_header_S.expectLength(haveFiles? AH_LENGTH_S : 0); + archive_header_S.readFrom(in); + if (haveFiles) { + long sizeHi = archive_header_S.getInt(); + long sizeLo = archive_header_S.getInt(); + archiveSize1 = (sizeHi << 32) + ((sizeLo << 32) >>> 32); + // Set the limit, now, up to the file_bits. + in.setReadLimit(archiveSize1); // for debug only + } else { + archiveSize1 = 0; + in.setReadLimit(-1); // remove limitation + } + archive_header_S.doneDisbursing(); + archiveSize0 = in.getBytesServed(); + + int remainingHeaders = AH_LENGTH_MIN - AH_LENGTH_0 - AH_LENGTH_S; + if (haveFiles) remainingHeaders += AH_FILE_HEADER_LEN; + if (haveSpecial) remainingHeaders += AH_SPECIAL_FORMAT_LEN; + if (haveNumbers) remainingHeaders += AH_CP_NUMBER_LEN; + if (haveCPExtra) remainingHeaders += AH_CP_EXTRA_LEN; + archive_header_1.expectLength(remainingHeaders); + archive_header_1.readFrom(in); + + if (haveFiles) { + archiveNextCount = archive_header_1.getInt(); + pkg.default_modtime = archive_header_1.getInt(); + numFiles = archive_header_1.getInt(); + } else { + archiveNextCount = 0; + numFiles = 0; + } + + if (haveSpecial) { + band_headers.expectLength(archive_header_1.getInt()); + numAttrDefs = archive_header_1.getInt(); + } else { + band_headers.expectLength(0); + numAttrDefs = 0; + } + + readConstantPoolCounts(haveNumbers, haveCPExtra); + + numInnerClasses = archive_header_1.getInt(); + + minver = (short) archive_header_1.getInt(); + majver = (short) archive_header_1.getInt(); + pkg.defaultClassVersion = Package.Version.of(majver, minver); + numClasses = archive_header_1.getInt(); + + archive_header_1.doneDisbursing(); + + // set some derived archive bits + if (testBit(archiveOptions, Constants.AO_DEFLATE_HINT)) { + pkg.default_options |= Constants.FO_DEFLATE_HINT; + } + } + + void readBandHeaders() throws IOException { + band_headers.readFrom(in); + bandHeaderBytePos = 1; // Leave room to pushback the initial XB byte. + bandHeaderBytes = new byte[bandHeaderBytePos + band_headers.length()]; + for (int i = bandHeaderBytePos; i < bandHeaderBytes.length; i++) { + bandHeaderBytes[i] = (byte) band_headers.getByte(); + } + band_headers.doneDisbursing(); + } + + void readConstantPoolCounts(boolean haveNumbers, boolean haveCPExtra) throws IOException { + // size the constant pools: + for (int k = 0; k < ConstantPool.TAGS_IN_ORDER.length; k++) { + // cp_counts: + // #cp_Utf8_count :UNSIGNED5[1] + // (cp_number_counts) ** (#have_cp_numbers) + // #cp_String_count :UNSIGNED5[1] + // #cp_Class_count :UNSIGNED5[1] + // #cp_Signature_count :UNSIGNED5[1] + // #cp_Descr_count :UNSIGNED5[1] + // #cp_Field_count :UNSIGNED5[1] + // #cp_Method_count :UNSIGNED5[1] + // #cp_Imethod_count :UNSIGNED5[1] + // (cp_attr_counts) ** (#have_cp_attr_counts) + // + // cp_number_counts: + // #cp_Int_count :UNSIGNED5[1] + // #cp_Float_count :UNSIGNED5[1] + // #cp_Long_count :UNSIGNED5[1] + // #cp_Double_count :UNSIGNED5[1] + // + // cp_extra_counts: + // #cp_MethodHandle_count :UNSIGNED5[1] + // #cp_MethodType_count :UNSIGNED5[1] + // #cp_InvokeDynamic_count :UNSIGNED5[1] + // #cp_BootstrapMethod_count :UNSIGNED5[1] + // + byte tag = ConstantPool.TAGS_IN_ORDER[k]; + if (!haveNumbers) { + // These four counts are optional. + switch (tag) { + case Constants.CONSTANT_Integer: + case Constants.CONSTANT_Float: + case Constants.CONSTANT_Long: + case Constants.CONSTANT_Double: + continue; + } + } + if (!haveCPExtra) { + // These four counts are optional. + switch (tag) { + case Constants.CONSTANT_MethodHandle: + case Constants.CONSTANT_MethodType: + case Constants.CONSTANT_InvokeDynamic: + case Constants.CONSTANT_BootstrapMethod: + continue; + } + } + tagCount[tag] = archive_header_1.getInt(); + } + } + + protected ConstantPool.Index getCPIndex(byte tag) { + return pkg.cp.getIndexByTag(tag); + } + ConstantPool.Index initCPIndex(byte tag, ConstantPool.Entry[] cpMap) { + if (verbose > 3) { + for (int i = 0; i < cpMap.length; i++) { + Utils.log.fine("cp.add "+cpMap[i]); + } + } + ConstantPool.Index index = ConstantPool.makeIndex(ConstantPool.tagName(tag), cpMap); + if (verbose > 1) Utils.log.fine("Read "+index); + pkg.cp.initIndexByTag(tag, index); + return index; + } + + void checkLegacy(String bandname) { + if (packageVersion.lessThan(Constants.JAVA7_PACKAGE_VERSION)) { + throw new RuntimeException("unexpected band " + bandname); + } + } + void readConstantPool() throws IOException { + // cp_bands: + // cp_Utf8 + // *cp_Int :UDELTA5 + // *cp_Float :UDELTA5 + // cp_Long + // cp_Double + // *cp_String :UDELTA5 (cp_Utf8) + // *cp_Class :UDELTA5 (cp_Utf8) + // cp_Signature + // cp_Descr + // cp_Field + // cp_Method + // cp_Imethod + + if (verbose > 0) Utils.log.info("Reading CP"); + + for (int k = 0; k < ConstantPool.TAGS_IN_ORDER.length; k++) { + byte tag = ConstantPool.TAGS_IN_ORDER[k]; + int len = tagCount[tag]; + + ConstantPool.Entry[] cpMap = new ConstantPool.Entry[len]; + if (verbose > 0) + Utils.log.info("Reading "+cpMap.length+" "+ConstantPool.tagName(tag)+" entries..."); + + switch (tag) { + case Constants.CONSTANT_Utf8: + readUtf8Bands(cpMap); + break; + case Constants.CONSTANT_Integer: + cp_Int.expectLength(cpMap.length); + cp_Int.readFrom(in); + for (int i = 0; i < cpMap.length; i++) { + int x = cp_Int.getInt(); // coding handles signs OK + cpMap[i] = ConstantPool.getLiteralEntry(x); + } + cp_Int.doneDisbursing(); + break; + case Constants.CONSTANT_Float: + cp_Float.expectLength(cpMap.length); + cp_Float.readFrom(in); + for (int i = 0; i < cpMap.length; i++) { + int x = cp_Float.getInt(); + float fx = Float.intBitsToFloat(x); + cpMap[i] = ConstantPool.getLiteralEntry(fx); + } + cp_Float.doneDisbursing(); + break; + case Constants.CONSTANT_Long: + // cp_Long: + // *cp_Long_hi :UDELTA5 + // *cp_Long_lo :DELTA5 + cp_Long_hi.expectLength(cpMap.length); + cp_Long_hi.readFrom(in); + cp_Long_lo.expectLength(cpMap.length); + cp_Long_lo.readFrom(in); + for (int i = 0; i < cpMap.length; i++) { + long hi = cp_Long_hi.getInt(); + long lo = cp_Long_lo.getInt(); + long x = (hi << 32) + ((lo << 32) >>> 32); + cpMap[i] = ConstantPool.getLiteralEntry(x); + } + cp_Long_hi.doneDisbursing(); + cp_Long_lo.doneDisbursing(); + break; + case Constants.CONSTANT_Double: + // cp_Double: + // *cp_Double_hi :UDELTA5 + // *cp_Double_lo :DELTA5 + cp_Double_hi.expectLength(cpMap.length); + cp_Double_hi.readFrom(in); + cp_Double_lo.expectLength(cpMap.length); + cp_Double_lo.readFrom(in); + for (int i = 0; i < cpMap.length; i++) { + long hi = cp_Double_hi.getInt(); + long lo = cp_Double_lo.getInt(); + long x = (hi << 32) + ((lo << 32) >>> 32); + double dx = Double.longBitsToDouble(x); + cpMap[i] = ConstantPool.getLiteralEntry(dx); + } + cp_Double_hi.doneDisbursing(); + cp_Double_lo.doneDisbursing(); + break; + case Constants.CONSTANT_String: + cp_String.expectLength(cpMap.length); + cp_String.readFrom(in); + cp_String.setIndex(getCPIndex(Constants.CONSTANT_Utf8)); + for (int i = 0; i < cpMap.length; i++) { + cpMap[i] = ConstantPool.getLiteralEntry(cp_String.getRef().stringValue()); + } + cp_String.doneDisbursing(); + break; + case Constants.CONSTANT_Class: + cp_Class.expectLength(cpMap.length); + cp_Class.readFrom(in); + cp_Class.setIndex(getCPIndex(Constants.CONSTANT_Utf8)); + for (int i = 0; i < cpMap.length; i++) { + cpMap[i] = ConstantPool.getClassEntry(cp_Class.getRef().stringValue()); + } + cp_Class.doneDisbursing(); + break; + case Constants.CONSTANT_Signature: + readSignatureBands(cpMap); + break; + case Constants.CONSTANT_NameandType: + // cp_Descr: + // *cp_Descr_type :DELTA5 (cp_Signature) + // *cp_Descr_name :UDELTA5 (cp_Utf8) + cp_Descr_name.expectLength(cpMap.length); + cp_Descr_name.readFrom(in); + cp_Descr_name.setIndex(getCPIndex(Constants.CONSTANT_Utf8)); + cp_Descr_type.expectLength(cpMap.length); + cp_Descr_type.readFrom(in); + cp_Descr_type.setIndex(getCPIndex(Constants.CONSTANT_Signature)); + for (int i = 0; i < cpMap.length; i++) { + ConstantPool.Entry ref = cp_Descr_name.getRef(); + ConstantPool.Entry ref2 = cp_Descr_type.getRef(); + cpMap[i] = ConstantPool.getDescriptorEntry((ConstantPool.Utf8Entry)ref, + (ConstantPool.SignatureEntry)ref2); + } + cp_Descr_name.doneDisbursing(); + cp_Descr_type.doneDisbursing(); + break; + case Constants.CONSTANT_Fieldref: + readMemberRefs(tag, cpMap, cp_Field_class, cp_Field_desc); + break; + case Constants.CONSTANT_Methodref: + readMemberRefs(tag, cpMap, cp_Method_class, cp_Method_desc); + break; + case Constants.CONSTANT_InterfaceMethodref: + readMemberRefs(tag, cpMap, cp_Imethod_class, cp_Imethod_desc); + break; + case Constants.CONSTANT_MethodHandle: + if (cpMap.length > 0) { + checkLegacy(cp_MethodHandle_refkind.name()); + } + cp_MethodHandle_refkind.expectLength(cpMap.length); + cp_MethodHandle_refkind.readFrom(in); + cp_MethodHandle_member.expectLength(cpMap.length); + cp_MethodHandle_member.readFrom(in); + cp_MethodHandle_member.setIndex(getCPIndex(Constants.CONSTANT_AnyMember)); + for (int i = 0; i < cpMap.length; i++) { + byte refKind = (byte) cp_MethodHandle_refkind.getInt(); + ConstantPool.MemberEntry memRef = (ConstantPool.MemberEntry) cp_MethodHandle_member.getRef(); + cpMap[i] = ConstantPool.getMethodHandleEntry(refKind, memRef); + } + cp_MethodHandle_refkind.doneDisbursing(); + cp_MethodHandle_member.doneDisbursing(); + break; + case Constants.CONSTANT_MethodType: + if (cpMap.length > 0) { + checkLegacy(cp_MethodType.name()); + } + cp_MethodType.expectLength(cpMap.length); + cp_MethodType.readFrom(in); + cp_MethodType.setIndex(getCPIndex(Constants.CONSTANT_Signature)); + for (int i = 0; i < cpMap.length; i++) { + ConstantPool.SignatureEntry typeRef = (ConstantPool.SignatureEntry) cp_MethodType.getRef(); + cpMap[i] = ConstantPool.getMethodTypeEntry(typeRef); + } + cp_MethodType.doneDisbursing(); + break; + case Constants.CONSTANT_InvokeDynamic: + if (cpMap.length > 0) { + checkLegacy(cp_InvokeDynamic_spec.name()); + } + cp_InvokeDynamic_spec.expectLength(cpMap.length); + cp_InvokeDynamic_spec.readFrom(in); + cp_InvokeDynamic_spec.setIndex(getCPIndex(Constants.CONSTANT_BootstrapMethod)); + cp_InvokeDynamic_desc.expectLength(cpMap.length); + cp_InvokeDynamic_desc.readFrom(in); + cp_InvokeDynamic_desc.setIndex(getCPIndex(Constants.CONSTANT_NameandType)); + for (int i = 0; i < cpMap.length; i++) { + ConstantPool.BootstrapMethodEntry bss = (ConstantPool.BootstrapMethodEntry) cp_InvokeDynamic_spec.getRef(); + ConstantPool.DescriptorEntry descr = (ConstantPool.DescriptorEntry) cp_InvokeDynamic_desc.getRef(); + cpMap[i] = ConstantPool.getInvokeDynamicEntry(bss, descr); + } + cp_InvokeDynamic_spec.doneDisbursing(); + cp_InvokeDynamic_desc.doneDisbursing(); + break; + case Constants.CONSTANT_BootstrapMethod: + if (cpMap.length > 0) { + checkLegacy(cp_BootstrapMethod_ref.name()); + } + cp_BootstrapMethod_ref.expectLength(cpMap.length); + cp_BootstrapMethod_ref.readFrom(in); + cp_BootstrapMethod_ref.setIndex(getCPIndex(Constants.CONSTANT_MethodHandle)); + cp_BootstrapMethod_arg_count.expectLength(cpMap.length); + cp_BootstrapMethod_arg_count.readFrom(in); + int totalArgCount = cp_BootstrapMethod_arg_count.getIntTotal(); + cp_BootstrapMethod_arg.expectLength(totalArgCount); + cp_BootstrapMethod_arg.readFrom(in); + cp_BootstrapMethod_arg.setIndex(getCPIndex(Constants.CONSTANT_LoadableValue)); + for (int i = 0; i < cpMap.length; i++) { + ConstantPool.MethodHandleEntry bsm = (ConstantPool.MethodHandleEntry) cp_BootstrapMethod_ref.getRef(); + int argc = cp_BootstrapMethod_arg_count.getInt(); + ConstantPool.Entry[] argRefs = new ConstantPool.Entry[argc]; + for (int j = 0; j < argc; j++) { + argRefs[j] = cp_BootstrapMethod_arg.getRef(); + } + cpMap[i] = ConstantPool.getBootstrapMethodEntry(bsm, argRefs); + } + cp_BootstrapMethod_ref.doneDisbursing(); + cp_BootstrapMethod_arg_count.doneDisbursing(); + cp_BootstrapMethod_arg.doneDisbursing(); + break; + default: + throw new AssertionError("unexpected CP tag in package"); + } + + ConstantPool.Index index = initCPIndex(tag, cpMap); + + if (optDumpBands) { + try (PrintStream ps = new PrintStream(getDumpStream(index, ".idx"))) { + printArrayTo(ps, index.cpMap, 0, index.cpMap.length); + } + } + } + + cp_bands.doneDisbursing(); + + if (optDumpBands || verbose > 1) { + for (byte tag = Constants.CONSTANT_GroupFirst; tag < Constants.CONSTANT_GroupLimit; tag++) { + ConstantPool.Index index = pkg.cp.getIndexByTag(tag); + if (index == null || index.isEmpty()) continue; + ConstantPool.Entry[] cpMap = index.cpMap; + if (verbose > 1) + Utils.log.info("Index group "+ConstantPool.tagName(tag)+" contains "+cpMap.length+" entries."); + if (optDumpBands) { + try (PrintStream ps = new PrintStream(getDumpStream(index.debugName, tag, ".gidx", index))) { + printArrayTo(ps, cpMap, 0, cpMap.length, true); + } + } + } + } + + setBandIndexes(); + } + + void readUtf8Bands(ConstantPool.Entry[] cpMap) throws IOException { + // cp_Utf8: + // *cp_Utf8_prefix :DELTA5 + // *cp_Utf8_suffix :UNSIGNED5 + // *cp_Utf8_chars :CHAR3 + // *cp_Utf8_big_suffix :DELTA5 + // (*cp_Utf8_big_chars :DELTA5) + // ** length(cp_Utf8_big_suffix) + int len = cpMap.length; + if (len == 0) + return; // nothing to read + + // Bands have implicit leading zeroes, for the empty string: + final int SUFFIX_SKIP_1 = 1; + final int PREFIX_SKIP_2 = 2; + + // First band: Read lengths of shared prefixes. + cp_Utf8_prefix.expectLength(Math.max(0, len - PREFIX_SKIP_2)); + cp_Utf8_prefix.readFrom(in); + + // Second band: Read lengths of unshared suffixes: + cp_Utf8_suffix.expectLength(Math.max(0, len - SUFFIX_SKIP_1)); + cp_Utf8_suffix.readFrom(in); + + char[][] suffixChars = new char[len][]; + int bigSuffixCount = 0; + + // Third band: Read the char values in the unshared suffixes: + cp_Utf8_chars.expectLength(cp_Utf8_suffix.getIntTotal()); + cp_Utf8_chars.readFrom(in); + for (int i = 0; i < len; i++) { + int suffix = (i < SUFFIX_SKIP_1)? 0: cp_Utf8_suffix.getInt(); + if (suffix == 0 && i >= SUFFIX_SKIP_1) { + // chars are packed in cp_Utf8_big_chars + bigSuffixCount += 1; + continue; + } + suffixChars[i] = new char[suffix]; + for (int j = 0; j < suffix; j++) { + int ch = cp_Utf8_chars.getInt(); + assert(ch == (char)ch); + suffixChars[i][j] = (char)ch; + } + } + cp_Utf8_chars.doneDisbursing(); + + // Fourth band: Go back and size the specially packed strings. + int maxChars = 0; + cp_Utf8_big_suffix.expectLength(bigSuffixCount); + cp_Utf8_big_suffix.readFrom(in); + cp_Utf8_suffix.resetForSecondPass(); + for (int i = 0; i < len; i++) { + int suffix = (i < SUFFIX_SKIP_1)? 0: cp_Utf8_suffix.getInt(); + int prefix = (i < PREFIX_SKIP_2)? 0: cp_Utf8_prefix.getInt(); + if (suffix == 0 && i >= SUFFIX_SKIP_1) { + assert(suffixChars[i] == null); + suffix = cp_Utf8_big_suffix.getInt(); + } else { + assert(suffixChars[i] != null); + } + if (maxChars < prefix + suffix) + maxChars = prefix + suffix; + } + char[] buf = new char[maxChars]; + + // Fifth band(s): Get the specially packed characters. + cp_Utf8_suffix.resetForSecondPass(); + cp_Utf8_big_suffix.resetForSecondPass(); + for (int i = 0; i < len; i++) { + if (i < SUFFIX_SKIP_1) continue; + int suffix = cp_Utf8_suffix.getInt(); + if (suffix != 0) continue; // already input + suffix = cp_Utf8_big_suffix.getInt(); + suffixChars[i] = new char[suffix]; + if (suffix == 0) { + // Do not bother to add an empty "(Utf8_big_0)" band. + continue; + } + IntBand packed = cp_Utf8_big_chars.newIntBand("(Utf8_big_"+i+")"); + packed.expectLength(suffix); + packed.readFrom(in); + for (int j = 0; j < suffix; j++) { + int ch = packed.getInt(); + assert(ch == (char)ch); + suffixChars[i][j] = (char)ch; + } + packed.doneDisbursing(); + } + cp_Utf8_big_chars.doneDisbursing(); + + // Finally, sew together all the prefixes and suffixes. + cp_Utf8_prefix.resetForSecondPass(); + cp_Utf8_suffix.resetForSecondPass(); + cp_Utf8_big_suffix.resetForSecondPass(); + for (int i = 0; i < len; i++) { + int prefix = (i < PREFIX_SKIP_2)? 0: cp_Utf8_prefix.getInt(); + int suffix = (i < SUFFIX_SKIP_1)? 0: cp_Utf8_suffix.getInt(); + if (suffix == 0 && i >= SUFFIX_SKIP_1) + suffix = cp_Utf8_big_suffix.getInt(); + + // by induction, the buffer is already filled with the prefix + System.arraycopy(suffixChars[i], 0, buf, prefix, suffix); + + cpMap[i] = ConstantPool.getUtf8Entry(new String(buf, 0, prefix+suffix)); + } + + cp_Utf8_prefix.doneDisbursing(); + cp_Utf8_suffix.doneDisbursing(); + cp_Utf8_big_suffix.doneDisbursing(); + } + + Map utf8Signatures; + + void readSignatureBands(ConstantPool.Entry[] cpMap) throws IOException { + // cp_Signature: + // *cp_Signature_form :DELTA5 (cp_Utf8) + // *cp_Signature_classes :UDELTA5 (cp_Class) + cp_Signature_form.expectLength(cpMap.length); + cp_Signature_form.readFrom(in); + cp_Signature_form.setIndex(getCPIndex(Constants.CONSTANT_Utf8)); + int[] numSigClasses = new int[cpMap.length]; + for (int i = 0; i < cpMap.length; i++) { + ConstantPool.Utf8Entry formRef = (ConstantPool.Utf8Entry) cp_Signature_form.getRef(); + numSigClasses[i] = ConstantPool.countClassParts(formRef); + } + cp_Signature_form.resetForSecondPass(); + cp_Signature_classes.expectLength(getIntTotal(numSigClasses)); + cp_Signature_classes.readFrom(in); + cp_Signature_classes.setIndex(getCPIndex(Constants.CONSTANT_Class)); + utf8Signatures = new HashMap<>(); + for (int i = 0; i < cpMap.length; i++) { + ConstantPool.Utf8Entry formRef = (ConstantPool.Utf8Entry) cp_Signature_form.getRef(); + ConstantPool.ClassEntry[] classRefs = new ConstantPool.ClassEntry[numSigClasses[i]]; + for (int j = 0; j < classRefs.length; j++) { + classRefs[j] = (ConstantPool.ClassEntry) cp_Signature_classes.getRef(); + } + ConstantPool.SignatureEntry se = ConstantPool.getSignatureEntry(formRef, classRefs); + cpMap[i] = se; + utf8Signatures.put(se.asUtf8Entry(), se); + } + cp_Signature_form.doneDisbursing(); + cp_Signature_classes.doneDisbursing(); + } + + void readMemberRefs(byte tag, ConstantPool.Entry[] cpMap, CPRefBand cp_class, CPRefBand cp_desc) throws IOException { + // cp_Field: + // *cp_Field_class :DELTA5 (cp_Class) + // *cp_Field_desc :UDELTA5 (cp_Descr) + // cp_Method: + // *cp_Method_class :DELTA5 (cp_Class) + // *cp_Method_desc :UDELTA5 (cp_Descr) + // cp_Imethod: + // *cp_Imethod_class :DELTA5 (cp_Class) + // *cp_Imethod_desc :UDELTA5 (cp_Descr) + cp_class.expectLength(cpMap.length); + cp_class.readFrom(in); + cp_class.setIndex(getCPIndex(Constants.CONSTANT_Class)); + cp_desc.expectLength(cpMap.length); + cp_desc.readFrom(in); + cp_desc.setIndex(getCPIndex(Constants.CONSTANT_NameandType)); + for (int i = 0; i < cpMap.length; i++) { + ConstantPool.ClassEntry mclass = (ConstantPool.ClassEntry) cp_class.getRef(); + ConstantPool.DescriptorEntry mdescr = (ConstantPool.DescriptorEntry) cp_desc.getRef(); + cpMap[i] = ConstantPool.getMemberEntry(tag, mclass, mdescr); + } + cp_class.doneDisbursing(); + cp_desc.doneDisbursing(); + } + + void readFiles() throws IOException { + // file_bands: + // *file_name :UNSIGNED5 (cp_Utf8) + // *file_size_hi :UNSIGNED5 + // *file_size_lo :UNSIGNED5 + // *file_modtime :DELTA5 + // *file_options :UNSIGNED5 + // *file_bits :BYTE1 + if (verbose > 0) + Utils.log.info(" ...building "+numFiles+" files..."); + file_name.expectLength(numFiles); + file_size_lo.expectLength(numFiles); + int options = archiveOptions; + boolean haveSizeHi = testBit(options, Constants.AO_HAVE_FILE_SIZE_HI); + boolean haveModtime = testBit(options, Constants.AO_HAVE_FILE_MODTIME); + boolean haveOptions = testBit(options, Constants.AO_HAVE_FILE_OPTIONS); + if (haveSizeHi) + file_size_hi.expectLength(numFiles); + if (haveModtime) + file_modtime.expectLength(numFiles); + if (haveOptions) + file_options.expectLength(numFiles); + + file_name.readFrom(in); + file_size_hi.readFrom(in); + file_size_lo.readFrom(in); + file_modtime.readFrom(in); + file_options.readFrom(in); + file_bits.setInputStreamFrom(in); + + Iterator nextClass = pkg.getClasses().iterator(); + + // Compute file lengths before reading any file bits. + long totalFileLength = 0; + long[] fileLengths = new long[numFiles]; + for (int i = 0; i < numFiles; i++) { + long size = ((long)file_size_lo.getInt() << 32) >>> 32; + if (haveSizeHi) + size += (long)file_size_hi.getInt() << 32; + fileLengths[i] = size; + totalFileLength += size; + } + assert(in.getReadLimit() == -1 || in.getReadLimit() == totalFileLength); + + byte[] buf = new byte[1<<16]; + for (int i = 0; i < numFiles; i++) { + // %%% Use a big temp file for file bits? + ConstantPool.Utf8Entry name = (ConstantPool.Utf8Entry) file_name.getRef(); + long size = fileLengths[i]; + Package.File file = pkg.new File(name); + file.modtime = pkg.default_modtime; + file.options = pkg.default_options; + if (haveModtime) + file.modtime += file_modtime.getInt(); + if (haveOptions) + file.options |= file_options.getInt(); + if (verbose > 1) + Utils.log.fine("Reading "+size+" bytes of "+name.stringValue()); + long toRead = size; + while (toRead > 0) { + int nr = buf.length; + if (nr > toRead) nr = (int) toRead; + nr = file_bits.getInputStream().read(buf, 0, nr); + if (nr < 0) throw new EOFException(); + file.addBytes(buf, 0, nr); + toRead -= nr; + } + pkg.addFile(file); + if (file.isClassStub()) { + assert(file.getFileLength() == 0); + Package.Class cls = nextClass.next(); + cls.initFile(file); + } + } + + // Do the rest of the classes. + while (nextClass.hasNext()) { + Package.Class cls = nextClass.next(); + cls.initFile(null); // implicitly initialize to a trivial one + cls.file.modtime = pkg.default_modtime; + } + + file_name.doneDisbursing(); + file_size_hi.doneDisbursing(); + file_size_lo.doneDisbursing(); + file_modtime.doneDisbursing(); + file_options.doneDisbursing(); + file_bits.doneDisbursing(); + file_bands.doneDisbursing(); + + if (archiveSize1 != 0 && !in.atLimit()) { + throw new RuntimeException("Predicted archive_size "+ + archiveSize1+" != "+ + (in.getBytesServed()-archiveSize0)); + } + } + + void readAttrDefs() throws IOException { + // attr_definition_bands: + // *attr_definition_headers :BYTE1 + // *attr_definition_name :UNSIGNED5 (cp_Utf8) + // *attr_definition_layout :UNSIGNED5 (cp_Utf8) + attr_definition_headers.expectLength(numAttrDefs); + attr_definition_name.expectLength(numAttrDefs); + attr_definition_layout.expectLength(numAttrDefs); + attr_definition_headers.readFrom(in); + attr_definition_name.readFrom(in); + attr_definition_layout.readFrom(in); + try (PrintStream dump = !optDumpBands ? null + : new PrintStream(getDumpStream(attr_definition_headers, ".def"))) + { + for (int i = 0; i < numAttrDefs; i++) { + int header = attr_definition_headers.getByte(); + ConstantPool.Utf8Entry name = (ConstantPool.Utf8Entry) attr_definition_name.getRef(); + ConstantPool.Utf8Entry layout = (ConstantPool.Utf8Entry) attr_definition_layout.getRef(); + int ctype = (header & ADH_CONTEXT_MASK); + int index = (header >> ADH_BIT_SHIFT) - ADH_BIT_IS_LSB; + Attribute.Layout def = new Attribute.Layout(ctype, + name.stringValue(), + layout.stringValue()); + // Check layout string for Java 6 extensions. + String pvLayout = def.layoutForClassVersion(getHighestClassVersion()); + if (!pvLayout.equals(def.layout())) { + throw new IOException("Bad attribute layout in archive: "+def.layout()); + } + this.setAttributeLayoutIndex(def, index); + if (dump != null) dump.println(index+" "+def); + } + } + attr_definition_headers.doneDisbursing(); + attr_definition_name.doneDisbursing(); + attr_definition_layout.doneDisbursing(); + // Attribute layouts define bands, one per layout element. + // Create them now, all at once. + makeNewAttributeBands(); + attr_definition_bands.doneDisbursing(); + } + + void readInnerClasses() throws IOException { + // ic_bands: + // *ic_this_class :UDELTA5 (cp_Class) + // *ic_flags :UNSIGNED5 + // *ic_outer_class :DELTA5 (null or cp_Class) + // *ic_name :DELTA5 (null or cp_Utf8) + ic_this_class.expectLength(numInnerClasses); + ic_this_class.readFrom(in); + ic_flags.expectLength(numInnerClasses); + ic_flags.readFrom(in); + int longICCount = 0; + for (int i = 0; i < numInnerClasses; i++) { + int flags = ic_flags.getInt(); + boolean longForm = (flags & Constants.ACC_IC_LONG_FORM) != 0; + if (longForm) { + longICCount += 1; + } + } + ic_outer_class.expectLength(longICCount); + ic_outer_class.readFrom(in); + ic_name.expectLength(longICCount); + ic_name.readFrom(in); + ic_flags.resetForSecondPass(); + List icList = new ArrayList<>(numInnerClasses); + for (int i = 0; i < numInnerClasses; i++) { + int flags = ic_flags.getInt(); + boolean longForm = (flags & Constants.ACC_IC_LONG_FORM) != 0; + flags &= ~Constants.ACC_IC_LONG_FORM; + ConstantPool.ClassEntry thisClass = (ConstantPool.ClassEntry) ic_this_class.getRef(); + ConstantPool.ClassEntry outerClass; + ConstantPool.Utf8Entry thisName; + if (longForm) { + outerClass = (ConstantPool.ClassEntry) ic_outer_class.getRef(); + thisName = (ConstantPool.Utf8Entry) ic_name.getRef(); + } else { + String n = thisClass.stringValue(); + String[] parse = Package.parseInnerClassName(n); + assert(parse != null); + String pkgOuter = parse[0]; + //String number = parse[1]; + String name = parse[2]; + if (pkgOuter == null) + outerClass = null; + else + outerClass = ConstantPool.getClassEntry(pkgOuter); + if (name == null) + thisName = null; + else + thisName = ConstantPool.getUtf8Entry(name); + } + Package.InnerClass ic = + new Package.InnerClass(thisClass, outerClass, thisName, flags); + assert(longForm || ic.predictable); + icList.add(ic); + } + ic_flags.doneDisbursing(); + ic_this_class.doneDisbursing(); + ic_outer_class.doneDisbursing(); + ic_name.doneDisbursing(); + pkg.setAllInnerClasses(icList); + ic_bands.doneDisbursing(); + } + + void readLocalInnerClasses(Package.Class cls) throws IOException { + int nc = class_InnerClasses_N.getInt(); + List localICs = new ArrayList<>(nc); + for (int i = 0; i < nc; i++) { + ConstantPool.ClassEntry thisClass = (ConstantPool.ClassEntry) class_InnerClasses_RC.getRef(); + int flags = class_InnerClasses_F.getInt(); + if (flags == 0) { + // A zero flag means copy a global IC here. + Package.InnerClass ic = pkg.getGlobalInnerClass(thisClass); + assert(ic != null); // must be a valid global IC reference + localICs.add(ic); + } else { + if (flags == Constants.ACC_IC_LONG_FORM) + flags = 0; // clear the marker bit + ConstantPool.ClassEntry outer = (ConstantPool.ClassEntry) class_InnerClasses_outer_RCN.getRef(); + ConstantPool.Utf8Entry name = (ConstantPool.Utf8Entry) class_InnerClasses_name_RUN.getRef(); + localICs.add(new Package.InnerClass(thisClass, outer, name, flags)); + } + } + cls.setInnerClasses(localICs); + // cls.expandLocalICs may add more tuples to ics also, + // or may even delete tuples. + // We cannot do that now, because we do not know the + // full contents of the local constant pool yet. + } + + static final int NO_FLAGS_YET = 0; // placeholder for later flag read-in + + Package.Class[] readClasses() throws IOException { + // class_bands: + // *class_this :DELTA5 (cp_Class) + // *class_super :DELTA5 (cp_Class) + // *class_interface_count :DELTA5 + // *class_interface :DELTA5 (cp_Class) + // ...(member bands)... + // class_attr_bands + // code_bands + Package.Class[] classes = new Package.Class[numClasses]; + if (verbose > 0) + Utils.log.info(" ...building "+classes.length+" classes..."); + + class_this.expectLength(numClasses); + class_super.expectLength(numClasses); + class_interface_count.expectLength(numClasses); + + class_this.readFrom(in); + class_super.readFrom(in); + class_interface_count.readFrom(in); + class_interface.expectLength(class_interface_count.getIntTotal()); + class_interface.readFrom(in); + for (int i = 0; i < classes.length; i++) { + ConstantPool.ClassEntry thisClass = (ConstantPool.ClassEntry) class_this.getRef(); + ConstantPool.ClassEntry superClass = (ConstantPool.ClassEntry) class_super.getRef(); + ConstantPool.ClassEntry[] interfaces = new ConstantPool.ClassEntry[class_interface_count.getInt()]; + for (int j = 0; j < interfaces.length; j++) { + interfaces[j] = (ConstantPool.ClassEntry) class_interface.getRef(); + } + // Packer encoded rare case of null superClass as thisClass: + if (superClass == thisClass) superClass = null; + Package.Class cls = pkg.new Class(NO_FLAGS_YET, + thisClass, superClass, interfaces); + classes[i] = cls; + } + class_this.doneDisbursing(); + class_super.doneDisbursing(); + class_interface_count.doneDisbursing(); + class_interface.doneDisbursing(); + readMembers(classes); + countAndReadAttrs(Constants.ATTR_CONTEXT_CLASS, Arrays.asList(classes)); + pkg.trimToSize(); + readCodeHeaders(); + //code_bands.doneDisbursing(); // still need to read code attrs + //class_bands.doneDisbursing(); // still need to read code attrs + return classes; + } + + private int getOutputIndex(ConstantPool.Entry e) { + // Output CPs do not contain signatures. + assert(e.tag != Constants.CONSTANT_Signature); + int k = pkg.cp.untypedIndexOf(e); + // In the output ordering, input signatures can serve + // in place of Utf8s. + if (k >= 0) + return k; + if (e.tag == Constants.CONSTANT_Utf8) { + ConstantPool.Entry se = utf8Signatures.get(e); + return pkg.cp.untypedIndexOf(se); + } + return -1; + } + + Comparator entryOutputOrder = new Comparator() { + public int compare(ConstantPool.Entry e0, ConstantPool.Entry e1) { + int k0 = getOutputIndex(e0); + int k1 = getOutputIndex(e1); + if (k0 >= 0 && k1 >= 0) + // If both have keys, use the keys. + return k0 - k1; + if (k0 == k1) + // If neither have keys, use their native tags & spellings. + return e0.compareTo(e1); + // Otherwise, the guy with the key comes first. + return (k0 >= 0)? 0-1: 1-0; + } + }; + + void reconstructClass(Package.Class cls) { + if (verbose > 1) Utils.log.fine("reconstruct "+cls); + + // check for local .ClassFile.version + Attribute retroVersion = cls.getAttribute(attrClassFileVersion); + if (retroVersion != null) { + cls.removeAttribute(retroVersion); + cls.version = parseClassFileVersionAttr(retroVersion); + } else { + cls.version = pkg.defaultClassVersion; + } + + // Replace null SourceFile by "obvious" string. + cls.expandSourceFile(); + + // record the local cp: + cls.setCPMap(reconstructLocalCPMap(cls)); + } + + ConstantPool.Entry[] reconstructLocalCPMap(Package.Class cls) { + Set ldcRefs = ldcRefMap.get(cls); + Set cpRefs = new HashSet<>(); + + // look for constant pool entries: + cls.visitRefs(Constants.VRM_CLASSIC, cpRefs); + + ArrayList bsms = new ArrayList<>(); + // flesh out the local constant pool + ConstantPool.completeReferencesIn(cpRefs, true, bsms); + + // add the bsm and references as required + if (!bsms.isEmpty()) { + cls.addAttribute(Package.attrBootstrapMethodsEmpty.canonicalInstance()); + cpRefs.add(Package.getRefString("BootstrapMethods")); + Collections.sort(bsms); + cls.setBootstrapMethods(bsms); + } + + // Now that we know all our local class references, + // compute the InnerClasses attribute. + // An InnerClasses attribute usually gets added here, + // although it might already have been present. + int changed = cls.expandLocalICs(); + + if (changed != 0) { + if (changed > 0) { + // Just visit the expanded InnerClasses attr. + cls.visitInnerClassRefs(Constants.VRM_CLASSIC, cpRefs); + } else { + // Have to recompute from scratch, because of deletions. + cpRefs.clear(); + cls.visitRefs(Constants.VRM_CLASSIC, cpRefs); + } + + // flesh out the local constant pool, again + ConstantPool.completeReferencesIn(cpRefs, true, bsms); + } + + // construct a local constant pool + int numDoubles = 0; + for (ConstantPool.Entry e : cpRefs) { + if (e.isDoubleWord()) numDoubles++; + } + ConstantPool.Entry[] cpMap = new ConstantPool.Entry[1+numDoubles+cpRefs.size()]; + int fillp = 1; + + // Add all ldc operands first. + if (ldcRefs != null) { + assert(cpRefs.containsAll(ldcRefs)); + for (ConstantPool.Entry e : ldcRefs) { + cpMap[fillp++] = e; + } + assert(fillp == 1+ldcRefs.size()); + cpRefs.removeAll(ldcRefs); + ldcRefs = null; // done with it + } + + // Next add all the two-byte references. + Set wideRefs = cpRefs; + cpRefs = null; // do not use! + int narrowLimit = fillp; + for (ConstantPool.Entry e : wideRefs) { + cpMap[fillp++] = e; + } + assert(fillp == narrowLimit+wideRefs.size()); + Arrays.sort(cpMap, 1, narrowLimit, entryOutputOrder); + Arrays.sort(cpMap, narrowLimit, fillp, entryOutputOrder); + + if (verbose > 3) { + Utils.log.fine("CP of "+this+" {"); + for (int i = 0; i < fillp; i++) { + ConstantPool.Entry e = cpMap[i]; + Utils.log.fine(" "+((e==null)?-1:getOutputIndex(e)) + +" : "+e); + } + Utils.log.fine("}"); + } + + // Now repack backwards, introducing null elements. + int revp = cpMap.length; + for (int i = fillp; --i >= 1; ) { + ConstantPool.Entry e = cpMap[i]; + if (e.isDoubleWord()) + cpMap[--revp] = null; + cpMap[--revp] = e; + } + assert(revp == 1); // do not process the initial null + + return cpMap; + } + + void readMembers(Package.Class[] classes) throws IOException { + // class_bands: + // ... + // *class_field_count :DELTA5 + // *class_method_count :DELTA5 + // + // *field_descr :DELTA5 (cp_Descr) + // field_attr_bands + // + // *method_descr :MDELTA5 (cp_Descr) + // method_attr_bands + // ... + assert(classes.length == numClasses); + class_field_count.expectLength(numClasses); + class_method_count.expectLength(numClasses); + class_field_count.readFrom(in); + class_method_count.readFrom(in); + + // Make a pre-pass over field and method counts to size the descrs: + int totalNF = class_field_count.getIntTotal(); + int totalNM = class_method_count.getIntTotal(); + field_descr.expectLength(totalNF); + method_descr.expectLength(totalNM); + if (verbose > 1) Utils.log.fine("expecting #fields="+totalNF+ + " and #methods="+totalNM+" in #classes="+numClasses); + + List fields = new ArrayList<>(totalNF); + field_descr.readFrom(in); + for (int i = 0; i < classes.length; i++) { + Package.Class c = classes[i]; + int nf = class_field_count.getInt(); + for (int j = 0; j < nf; j++) { + Package.Class.Field f = c.new Field(NO_FLAGS_YET, (ConstantPool.DescriptorEntry) + field_descr.getRef()); + fields.add(f); + } + } + class_field_count.doneDisbursing(); + field_descr.doneDisbursing(); + countAndReadAttrs(Constants.ATTR_CONTEXT_FIELD, fields); + fields = null; // release to GC + + List methods = new ArrayList<>(totalNM); + method_descr.readFrom(in); + for (int i = 0; i < classes.length; i++) { + Package.Class c = classes[i]; + int nm = class_method_count.getInt(); + for (int j = 0; j < nm; j++) { + Package.Class.Method m = c.new Method(NO_FLAGS_YET, (ConstantPool.DescriptorEntry) + method_descr.getRef()); + methods.add(m); + } + } + class_method_count.doneDisbursing(); + method_descr.doneDisbursing(); + countAndReadAttrs(Constants.ATTR_CONTEXT_METHOD, methods); + + // Up to this point, Code attributes look like empty attributes. + // Now we start to special-case them. The empty canonical Code + // attributes stay in the method attribute lists, however. + allCodes = buildCodeAttrs(methods); + } + + Code[] allCodes; + List codesWithFlags; + Map> ldcRefMap = new HashMap<>(); + + Code[] buildCodeAttrs(List methods) { + List codes = new ArrayList<>(methods.size()); + for (Package.Class.Method m : methods) { + if (m.getAttribute(attrCodeEmpty) != null) { + m.code = new Code(m); + codes.add(m.code); + } + } + Code[] a = new Code[codes.size()]; + codes.toArray(a); + return a; + } + + void readCodeHeaders() throws IOException { + // code_bands: + // *code_headers :BYTE1 + // + // *code_max_stack :UNSIGNED5 + // *code_max_na_locals :UNSIGNED5 + // *code_handler_count :UNSIGNED5 + // ... + // code_attr_bands + boolean attrsOK = testBit(archiveOptions, Constants.AO_HAVE_ALL_CODE_FLAGS); + code_headers.expectLength(allCodes.length); + code_headers.readFrom(in); + List longCodes = new ArrayList<>(allCodes.length / 10); + for (int i = 0; i < allCodes.length; i++) { + Code c = allCodes[i]; + int sc = code_headers.getByte(); + assert(sc == (sc & 0xFF)); + if (verbose > 2) + Utils.log.fine("codeHeader "+c+" = "+sc); + if (sc == LONG_CODE_HEADER) { + // We will read ms/ml/nh/flags from bands shortly. + longCodes.add(c); + continue; + } + // Short code header is the usual case: + c.setMaxStack( shortCodeHeader_max_stack(sc) ); + c.setMaxNALocals( shortCodeHeader_max_na_locals(sc) ); + c.setHandlerCount( shortCodeHeader_handler_count(sc) ); + assert(shortCodeHeader(c) == sc); + } + code_headers.doneDisbursing(); + code_max_stack.expectLength(longCodes.size()); + code_max_na_locals.expectLength(longCodes.size()); + code_handler_count.expectLength(longCodes.size()); + + // Do the long headers now. + code_max_stack.readFrom(in); + code_max_na_locals.readFrom(in); + code_handler_count.readFrom(in); + for (Code c : longCodes) { + c.setMaxStack( code_max_stack.getInt() ); + c.setMaxNALocals( code_max_na_locals.getInt() ); + c.setHandlerCount( code_handler_count.getInt() ); + } + code_max_stack.doneDisbursing(); + code_max_na_locals.doneDisbursing(); + code_handler_count.doneDisbursing(); + + readCodeHandlers(); + + if (attrsOK) { + // Code attributes are common (debug info not stripped). + codesWithFlags = Arrays.asList(allCodes); + } else { + // Code attributes are very sparse (debug info is stripped). + codesWithFlags = longCodes; + } + countAttrs(Constants.ATTR_CONTEXT_CODE, codesWithFlags); + // do readAttrs later, after BCs are scanned + } + + void readCodeHandlers() throws IOException { + // code_bands: + // ... + // *code_handler_start_P :BCI5 + // *code_handler_end_PO :BRANCH5 + // *code_handler_catch_PO :BRANCH5 + // *code_handler_class_RCN :UNSIGNED5 (null or cp_Class) + // ... + int nh = 0; + for (int i = 0; i < allCodes.length; i++) { + Code c = allCodes[i]; + nh += c.getHandlerCount(); + } + + ValueBand[] code_handler_bands = { + code_handler_start_P, + code_handler_end_PO, + code_handler_catch_PO, + code_handler_class_RCN + }; + + for (int i = 0; i < code_handler_bands.length; i++) { + code_handler_bands[i].expectLength(nh); + code_handler_bands[i].readFrom(in); + } + + for (int i = 0; i < allCodes.length; i++) { + Code c = allCodes[i]; + for (int j = 0, jmax = c.getHandlerCount(); j < jmax; j++) { + c.handler_class[j] = code_handler_class_RCN.getRef(); + // For now, just record the raw BCI codes. + // We must wait until we have instruction boundaries. + c.handler_start[j] = code_handler_start_P.getInt(); + c.handler_end[j] = code_handler_end_PO.getInt(); + c.handler_catch[j] = code_handler_catch_PO.getInt(); + } + } + for (int i = 0; i < code_handler_bands.length; i++) { + code_handler_bands[i].doneDisbursing(); + } + } + + void fixupCodeHandlers() { + // Actually decode (renumber) the BCIs now. + for (int i = 0; i < allCodes.length; i++) { + Code c = allCodes[i]; + for (int j = 0, jmax = c.getHandlerCount(); j < jmax; j++) { + int sum = c.handler_start[j]; + c.handler_start[j] = c.decodeBCI(sum); + sum += c.handler_end[j]; + c.handler_end[j] = c.decodeBCI(sum); + sum += c.handler_catch[j]; + c.handler_catch[j] = c.decodeBCI(sum); + } + } + } + + // Generic routines for reading attributes of + // classes, fields, methods, and codes. + // The holders is a global list, already collected, + // of attribute "customers". + void countAndReadAttrs(int ctype, Collection holders) + throws IOException { + // class_attr_bands: + // *class_flags :UNSIGNED5 + // *class_attr_count :UNSIGNED5 + // *class_attr_indexes :UNSIGNED5 + // *class_attr_calls :UNSIGNED5 + // *class_Signature_RS :UNSIGNED5 (cp_Signature) + // class_metadata_bands + // *class_SourceFile_RU :UNSIGNED5 (cp_Utf8) + // *class_EnclosingMethod_RM :UNSIGNED5 (cp_Method) + // ic_local_bands + // *class_ClassFile_version_minor_H :UNSIGNED5 + // *class_ClassFile_version_major_H :UNSIGNED5 + // class_type_metadata_bands + // + // field_attr_bands: + // *field_flags :UNSIGNED5 + // *field_attr_count :UNSIGNED5 + // *field_attr_indexes :UNSIGNED5 + // *field_attr_calls :UNSIGNED5 + // *field_Signature_RS :UNSIGNED5 (cp_Signature) + // field_metadata_bands + // *field_ConstantValue_KQ :UNSIGNED5 (cp_Int, etc.; see note) + // field_type_metadata_bands + // + // method_attr_bands: + // *method_flags :UNSIGNED5 + // *method_attr_count :UNSIGNED5 + // *method_attr_indexes :UNSIGNED5 + // *method_attr_calls :UNSIGNED5 + // *method_Signature_RS :UNSIGNED5 (cp_Signature) + // method_metadata_bands + // *method_Exceptions_N :UNSIGNED5 + // *method_Exceptions_RC :UNSIGNED5 (cp_Class) + // *method_MethodParameters_NB: BYTE1 + // *method_MethodParameters_RUN: UNSIGNED5 (cp_Utf8) + // *method_MethodParameters_FH: UNSIGNED5 (flag) + // method_type_metadata_bands + // + // code_attr_bands: + // *code_flags :UNSIGNED5 + // *code_attr_count :UNSIGNED5 + // *code_attr_indexes :UNSIGNED5 + // *code_attr_calls :UNSIGNED5 + // *code_LineNumberTable_N :UNSIGNED5 + // *code_LineNumberTable_bci_P :BCI5 + // *code_LineNumberTable_line :UNSIGNED5 + // *code_LocalVariableTable_N :UNSIGNED5 + // *code_LocalVariableTable_bci_P :BCI5 + // *code_LocalVariableTable_span_O :BRANCH5 + // *code_LocalVariableTable_name_RU :UNSIGNED5 (cp_Utf8) + // *code_LocalVariableTable_type_RS :UNSIGNED5 (cp_Signature) + // *code_LocalVariableTable_slot :UNSIGNED5 + // code_type_metadata_bands + + countAttrs(ctype, holders); + readAttrs(ctype, holders); + } + + // Read flags and count the attributes that are to be placed + // on the given holders. + void countAttrs(int ctype, Collection holders) + throws IOException { + // Here, xxx stands for one of class, field, method, code. + MultiBand xxx_attr_bands = attrBands[ctype]; + long flagMask = attrFlagMask[ctype]; + if (verbose > 1) { + Utils.log.fine("scanning flags and attrs for "+ + Attribute.contextName(ctype)+"["+holders.size()+"]"); + } + + // Fetch the attribute layout definitions which govern the bands + // we are about to read. + List defList = attrDefs.get(ctype); + Attribute.Layout[] defs = new Attribute.Layout[defList.size()]; + defList.toArray(defs); + IntBand xxx_flags_hi = getAttrBand(xxx_attr_bands, AB_FLAGS_HI); + IntBand xxx_flags_lo = getAttrBand(xxx_attr_bands, AB_FLAGS_LO); + IntBand xxx_attr_count = getAttrBand(xxx_attr_bands, AB_ATTR_COUNT); + IntBand xxx_attr_indexes = getAttrBand(xxx_attr_bands, AB_ATTR_INDEXES); + IntBand xxx_attr_calls = getAttrBand(xxx_attr_bands, AB_ATTR_CALLS); + + // Count up the number of holders which have overflow attrs. + int overflowMask = attrOverflowMask[ctype]; + int overflowHolderCount = 0; + boolean haveLongFlags = haveFlagsHi(ctype); + xxx_flags_hi.expectLength(haveLongFlags? holders.size(): 0); + xxx_flags_hi.readFrom(in); + xxx_flags_lo.expectLength(holders.size()); + xxx_flags_lo.readFrom(in); + assert((flagMask & overflowMask) == overflowMask); + for (Attribute.Holder h : holders) { + int flags = xxx_flags_lo.getInt(); + h.flags = flags; + if ((flags & overflowMask) != 0) + overflowHolderCount += 1; + } + + // For each holder with overflow attrs, read a count. + xxx_attr_count.expectLength(overflowHolderCount); + xxx_attr_count.readFrom(in); + xxx_attr_indexes.expectLength(xxx_attr_count.getIntTotal()); + xxx_attr_indexes.readFrom(in); + + // Now it's time to check flag bits that indicate attributes. + // We accumulate (a) a list of attribute types for each holder + // (class/field/method/code), and also we accumulate (b) a total + // count for each attribute type. + int[] totalCounts = new int[defs.length]; + for (Attribute.Holder h : holders) { + assert(h.attributes == null); + // System.out.println("flags="+h.flags+" using fm="+flagMask); + long attrBits = ((h.flags & flagMask) << 32) >>> 32; + // Clean up the flags now. + h.flags -= (int)attrBits; // strip attr bits + assert(h.flags == (char)h.flags); // 16 bits only now + assert((ctype != Constants.ATTR_CONTEXT_CODE) || h.flags == 0); + if (haveLongFlags) + attrBits += (long)xxx_flags_hi.getInt() << 32; + if (attrBits == 0) continue; // no attrs on this guy + + int noa = 0; // number of overflow attrs + long overflowBit = (attrBits & overflowMask); + assert(overflowBit >= 0); + attrBits -= overflowBit; + if (overflowBit != 0) { + noa = xxx_attr_count.getInt(); + } + + int nfa = 0; // number of flag attrs + long bits = attrBits; + for (int ai = 0; bits != 0; ai++) { + if ((bits & (1L< ha = new ArrayList<>(nfa + noa); + h.attributes = ha; + bits = attrBits; // iterate again + for (int ai = 0; bits != 0; ai++) { + if ((bits & (1L< 0; noa--) { + int ai = xxx_attr_indexes.getInt(); + totalCounts[ai] += 1; + // This definition index is live in this holder. + if (defs[ai] == null) badAttrIndex(ai, ctype); + Attribute canonical = defs[ai].canonicalInstance(); + ha.add(canonical); + } + } + + xxx_flags_hi.doneDisbursing(); + xxx_flags_lo.doneDisbursing(); + xxx_attr_count.doneDisbursing(); + xxx_attr_indexes.doneDisbursing(); + + // Now each holder has a list of canonical attribute instances. + // For layouts with no elements, we are done. However, for + // layouts with bands, we must replace each canonical (empty) + // instance with a value-bearing one, initialized from the + // appropriate bands. + + // Make a small pass to detect and read backward call counts. + int callCounts = 0; + for (boolean predef = true; ; predef = false) { + for (int ai = 0; ai < defs.length; ai++) { + Attribute.Layout def = defs[ai]; + if (def == null) continue; // unused index + if (predef != isPredefinedAttr(ctype, ai)) + continue; // wrong pass + int totalCount = totalCounts[ai]; + if (totalCount == 0) + continue; // irrelevant + Attribute.Layout.Element[] cbles = def.getCallables(); + for (int j = 0; j < cbles.length; j++) { + assert(cbles[j].kind == Attribute.EK_CBLE); + if (cbles[j].flagTest(Attribute.EF_BACK)) + callCounts += 1; + } + } + if (!predef) break; + } + xxx_attr_calls.expectLength(callCounts); + xxx_attr_calls.readFrom(in); + + // Finally, size all the attribute bands. + for (boolean predef = true; ; predef = false) { + for (int ai = 0; ai < defs.length; ai++) { + Attribute.Layout def = defs[ai]; + if (def == null) continue; // unused index + if (predef != isPredefinedAttr(ctype, ai)) + continue; // wrong pass + int totalCount = totalCounts[ai]; + Band[] ab = attrBandTable.get(def); + if (def == attrInnerClassesEmpty) { + // Special case. + // Size the bands as if using the following layout: + // [RCH TI[ (0)[] ()[RCNH RUNH] ]]. + class_InnerClasses_N.expectLength(totalCount); + class_InnerClasses_N.readFrom(in); + int tupleCount = class_InnerClasses_N.getIntTotal(); + class_InnerClasses_RC.expectLength(tupleCount); + class_InnerClasses_RC.readFrom(in); + class_InnerClasses_F.expectLength(tupleCount); + class_InnerClasses_F.readFrom(in); + // Drop remaining columns wherever flags are zero: + tupleCount -= class_InnerClasses_F.getIntCount(0); + class_InnerClasses_outer_RCN.expectLength(tupleCount); + class_InnerClasses_outer_RCN.readFrom(in); + class_InnerClasses_name_RUN.expectLength(tupleCount); + class_InnerClasses_name_RUN.readFrom(in); + } else if (!optDebugBands && totalCount == 0) { + // Expect no elements at all. Skip quickly. however if we + // are debugging bands, read all bands regardless + for (int j = 0; j < ab.length; j++) { + ab[j].doneWithUnusedBand(); + } + } else { + // Read these bands in sequence. + boolean hasCallables = def.hasCallables(); + if (!hasCallables) { + readAttrBands(def.elems, totalCount, new int[0], ab); + } else { + Attribute.Layout.Element[] cbles = def.getCallables(); + // At first, record initial calls. + // Later, forward calls may also accumulate here: + int[] forwardCounts = new int[cbles.length]; + forwardCounts[0] = totalCount; + for (int j = 0; j < cbles.length; j++) { + assert(cbles[j].kind == Attribute.EK_CBLE); + int entryCount = forwardCounts[j]; + forwardCounts[j] = -1; // No more, please! + if (totalCount > 0 && cbles[j].flagTest(Attribute.EF_BACK)) + entryCount += xxx_attr_calls.getInt(); + readAttrBands(cbles[j].body, entryCount, forwardCounts, ab); + } + } + // mark them read, to satisfy asserts + if (optDebugBands && totalCount == 0) { + for (int j = 0; j < ab.length; j++) { + ab[j].doneDisbursing(); + } + } + } + } + if (!predef) break; + } + xxx_attr_calls.doneDisbursing(); + } + + void badAttrIndex(int ai, int ctype) throws IOException { + throw new IOException("Unknown attribute index "+ai+" for "+ + Constants.ATTR_CONTEXT_NAME[ctype]+" attribute"); + } + + void readAttrs(int ctype, Collection holders) + throws IOException { + // Decode band values into attributes. + Set sawDefs = new HashSet<>(); + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + for (final Attribute.Holder h : holders) { + if (h.attributes == null) continue; + for (ListIterator j = h.attributes.listIterator(); j.hasNext(); ) { + Attribute a = j.next(); + Attribute.Layout def = a.layout(); + if (def.bandCount == 0) { + if (def == attrInnerClassesEmpty) { + // Special logic to read this attr. + readLocalInnerClasses((Package.Class) h); + continue; + } + // Canonical empty attr works fine (e.g., Synthetic). + continue; + } + sawDefs.add(def); + boolean isCV = (ctype == Constants.ATTR_CONTEXT_FIELD && def == attrConstantValue); + if (isCV) setConstantValueIndex((Package.Class.Field)h); + if (verbose > 2) + Utils.log.fine("read "+a+" in "+h); + final Band[] ab = attrBandTable.get(def); + // Read one attribute of type def from ab into a byte array. + buf.reset(); + Object fixups = a.unparse(new Attribute.ValueStream() { + public int getInt(int bandIndex) { + return ((IntBand) ab[bandIndex]).getInt(); + } + public ConstantPool.Entry getRef(int bandIndex) { + return ((CPRefBand) ab[bandIndex]).getRef(); + } + public int decodeBCI(int bciCode) { + Code code = (Code) h; + return code.decodeBCI(bciCode); + } + }, buf); + // Replace the canonical attr with the one just read. + j.set(a.addContent(buf.toByteArray(), fixups)); + if (isCV) setConstantValueIndex(null); // clean up + } + } + + // Mark the bands we just used as done disbursing. + for (Attribute.Layout def : sawDefs) { + if (def == null) continue; // unused index + Band[] ab = attrBandTable.get(def); + for (int j = 0; j < ab.length; j++) { + ab[j].doneDisbursing(); + } + } + + if (ctype == Constants.ATTR_CONTEXT_CLASS) { + class_InnerClasses_N.doneDisbursing(); + class_InnerClasses_RC.doneDisbursing(); + class_InnerClasses_F.doneDisbursing(); + class_InnerClasses_outer_RCN.doneDisbursing(); + class_InnerClasses_name_RUN.doneDisbursing(); + } + + MultiBand xxx_attr_bands = attrBands[ctype]; + for (int i = 0; i < xxx_attr_bands.size(); i++) { + Band b = xxx_attr_bands.get(i); + if (b instanceof MultiBand) + b.doneDisbursing(); + } + xxx_attr_bands.doneDisbursing(); + } + + private + void readAttrBands(Attribute.Layout.Element[] elems, + int count, int[] forwardCounts, + Band[] ab) + throws IOException { + for (int i = 0; i < elems.length; i++) { + Attribute.Layout.Element e = elems[i]; + Band eBand = null; + if (e.hasBand()) { + eBand = ab[e.bandIndex]; + eBand.expectLength(count); + eBand.readFrom(in); + } + switch (e.kind) { + case Attribute.EK_REPL: + // Recursive call. + int repCount = ((IntBand)eBand).getIntTotal(); + // Note: getIntTotal makes an extra pass over this band. + readAttrBands(e.body, repCount, forwardCounts, ab); + break; + case Attribute.EK_UN: + int remainingCount = count; + for (int j = 0; j < e.body.length; j++) { + int caseCount; + if (j == e.body.length-1) { + caseCount = remainingCount; + } else { + caseCount = 0; + for (int j0 = j; + (j == j0) + || (j < e.body.length + && e.body[j].flagTest(Attribute.EF_BACK)); + j++) { + caseCount += ((IntBand)eBand).getIntCount(e.body[j].value); + } + --j; // back up to last occurrence of this body + } + remainingCount -= caseCount; + readAttrBands(e.body[j].body, caseCount, forwardCounts, ab); + } + assert(remainingCount == 0); + break; + case Attribute.EK_CALL: + assert(e.body.length == 1); + assert(e.body[0].kind == Attribute.EK_CBLE); + if (!e.flagTest(Attribute.EF_BACK)) { + // Backward calls are pre-counted, but forwards are not. + // Push the present count forward. + assert(forwardCounts[e.value] >= 0); + forwardCounts[e.value] += count; + } + break; + case Attribute.EK_CBLE: + assert(false); + break; + } + } + } + + void readByteCodes() throws IOException { + // bc_bands: + // *bc_codes :BYTE1 + // *bc_case_count :UNSIGNED5 + // *bc_case_value :DELTA5 + // *bc_byte :BYTE1 + // *bc_short :DELTA5 + // *bc_local :UNSIGNED5 + // *bc_label :BRANCH5 + // *bc_intref :DELTA5 (cp_Int) + // *bc_floatref :DELTA5 (cp_Float) + // *bc_longref :DELTA5 (cp_Long) + // *bc_doubleref :DELTA5 (cp_Double) + // *bc_stringref :DELTA5 (cp_String) + // *bc_classref :UNSIGNED5 (current class or cp_Class) + // *bc_fieldref :DELTA5 (cp_Field) + // *bc_methodref :UNSIGNED5 (cp_Method) + // *bc_imethodref :DELTA5 (cp_Imethod) + // *bc_thisfield :UNSIGNED5 (cp_Field, only for current class) + // *bc_superfield :UNSIGNED5 (cp_Field, only for current super) + // *bc_thismethod :UNSIGNED5 (cp_Method, only for current class) + // *bc_supermethod :UNSIGNED5 (cp_Method, only for current super) + // *bc_initref :UNSIGNED5 (cp_Field, only for most recent new) + // *bc_escref :UNSIGNED5 (cp_All) + // *bc_escrefsize :UNSIGNED5 + // *bc_escsize :UNSIGNED5 + // *bc_escbyte :BYTE1 + bc_codes.elementCountForDebug = allCodes.length; + bc_codes.setInputStreamFrom(in); + readByteCodeOps(); // reads from bc_codes and bc_case_count + bc_codes.doneDisbursing(); + + // All the operand bands have now been sized. Read them all in turn. + Band[] operand_bands = { + bc_case_value, + bc_byte, bc_short, + bc_local, bc_label, + bc_intref, bc_floatref, + bc_longref, bc_doubleref, bc_stringref, + bc_loadablevalueref, + bc_classref, bc_fieldref, + bc_methodref, bc_imethodref, + bc_indyref, + bc_thisfield, bc_superfield, + bc_thismethod, bc_supermethod, + bc_initref, + bc_escref, bc_escrefsize, bc_escsize + }; + for (int i = 0; i < operand_bands.length; i++) { + operand_bands[i].readFrom(in); + } + bc_escbyte.expectLength(bc_escsize.getIntTotal()); + bc_escbyte.readFrom(in); + + expandByteCodeOps(); + + // Done fetching values from operand bands: + bc_case_count.doneDisbursing(); + for (int i = 0; i < operand_bands.length; i++) { + operand_bands[i].doneDisbursing(); + } + bc_escbyte.doneDisbursing(); + bc_bands.doneDisbursing(); + + // We must delay the parsing of Code attributes until we + // have a complete model of bytecodes, for BCI encodings. + readAttrs(Constants.ATTR_CONTEXT_CODE, codesWithFlags); + // Ditto for exception handlers in codes. + fixupCodeHandlers(); + // Now we can finish with class_bands; cf. readClasses(). + code_bands.doneDisbursing(); + class_bands.doneDisbursing(); + } + + private void readByteCodeOps() throws IOException { + // scratch buffer for collecting code:: + byte[] buf = new byte[1<<12]; + // record of all switch opcodes (these are variable-length) + List allSwitchOps = new ArrayList<>(); + for (int k = 0; k < allCodes.length; k++) { + Code c = allCodes[k]; + scanOneMethod: + for (int i = 0; ; i++) { + int bc = bc_codes.getByte(); + if (i + 10 > buf.length) buf = realloc(buf); + buf[i] = (byte)bc; + boolean isWide = false; + if (bc == Constants._wide) { + bc = bc_codes.getByte(); + buf[++i] = (byte)bc; + isWide = true; + } + assert(bc == (0xFF & bc)); + // Adjust expectations of various band sizes. + switch (bc) { + case Constants._tableswitch: + case Constants._lookupswitch: + bc_case_count.expectMoreLength(1); + allSwitchOps.add(bc); + break; + case Constants._iinc: + bc_local.expectMoreLength(1); + if (isWide) + bc_short.expectMoreLength(1); + else + bc_byte.expectMoreLength(1); + break; + case Constants._sipush: + bc_short.expectMoreLength(1); + break; + case Constants._bipush: + bc_byte.expectMoreLength(1); + break; + case Constants._newarray: + bc_byte.expectMoreLength(1); + break; + case Constants._multianewarray: + assert(getCPRefOpBand(bc) == bc_classref); + bc_classref.expectMoreLength(1); + bc_byte.expectMoreLength(1); + break; + case Constants._ref_escape: + bc_escrefsize.expectMoreLength(1); + bc_escref.expectMoreLength(1); + break; + case Constants._byte_escape: + bc_escsize.expectMoreLength(1); + // bc_escbyte will have to be counted too + break; + default: + if (Instruction.isInvokeInitOp(bc)) { + bc_initref.expectMoreLength(1); + break; + } + if (Instruction.isSelfLinkerOp(bc)) { + CPRefBand bc_which = selfOpRefBand(bc); + bc_which.expectMoreLength(1); + break; + } + if (Instruction.isBranchOp(bc)) { + bc_label.expectMoreLength(1); + break; + } + if (Instruction.isCPRefOp(bc)) { + CPRefBand bc_which = getCPRefOpBand(bc); + bc_which.expectMoreLength(1); + assert(bc != Constants._multianewarray); // handled elsewhere + break; + } + if (Instruction.isLocalSlotOp(bc)) { + bc_local.expectMoreLength(1); + break; + } + break; + case Constants._end_marker: + { + // Transfer from buf to a more permanent place: + c.bytes = realloc(buf, i); + break scanOneMethod; + } + } + } + } + + // To size instruction bands correctly, we need info on switches: + bc_case_count.readFrom(in); + for (Integer i : allSwitchOps) { + int bc = i.intValue(); + int caseCount = bc_case_count.getInt(); + bc_label.expectMoreLength(1+caseCount); // default label + cases + bc_case_value.expectMoreLength(bc == Constants._tableswitch ? 1 : caseCount); + } + bc_case_count.resetForSecondPass(); + } + + private void expandByteCodeOps() throws IOException { + // scratch buffer for collecting code: + byte[] buf = new byte[1<<12]; + // scratch buffer for collecting instruction boundaries: + int[] insnMap = new int[1<<12]; + // list of label carriers, for label decoding post-pass: + int[] labels = new int[1<<10]; + // scratch buffer for registering CP refs: + Fixups fixupBuf = new Fixups(); + + for (int k = 0; k < allCodes.length; k++) { + Code code = allCodes[k]; + byte[] codeOps = code.bytes; + code.bytes = null; // just for now, while we accumulate bits + + Package.Class curClass = code.thisClass(); + + Set ldcRefSet = ldcRefMap.get(curClass); + if (ldcRefSet == null) + ldcRefMap.put(curClass, ldcRefSet = new HashSet<>()); + + ConstantPool.ClassEntry thisClass = curClass.thisClass; + ConstantPool.ClassEntry superClass = curClass.superClass; + ConstantPool.ClassEntry newClass = null; // class of last _new opcode + + int pc = 0; // fill pointer in buf; actual bytecode PC + int numInsns = 0; + int numLabels = 0; + boolean hasEscs = false; + fixupBuf.clear(); + for (int i = 0; i < codeOps.length; i++) { + int bc = Instruction.getByte(codeOps, i); + int curPC = pc; + insnMap[numInsns++] = curPC; + if (pc + 10 > buf.length) buf = realloc(buf); + if (numInsns+10 > insnMap.length) insnMap = realloc(insnMap); + if (numLabels+10 > labels.length) labels = realloc(labels); + boolean isWide = false; + if (bc == Constants._wide) { + buf[pc++] = (byte) bc; + bc = Instruction.getByte(codeOps, ++i); + isWide = true; + } + switch (bc) { + case Constants._tableswitch: // apc: (df, lo, hi, (hi-lo+1)*(label)) + case Constants._lookupswitch: // apc: (df, nc, nc*(case, label)) + { + int caseCount = bc_case_count.getInt(); + while ((pc + 30 + caseCount*8) > buf.length) + buf = realloc(buf); + buf[pc++] = (byte) bc; + //initialize apc, df, lo, hi bytes to reasonable bits: + Arrays.fill(buf, pc, pc+30, (byte)0); + Instruction.Switch isw = (Instruction.Switch) + Instruction.at(buf, curPC); + //isw.setDefaultLabel(getLabel(bc_label, code, curPC)); + isw.setCaseCount(caseCount); + if (bc == Constants._tableswitch) { + isw.setCaseValue(0, bc_case_value.getInt()); + } else { + for (int j = 0; j < caseCount; j++) { + isw.setCaseValue(j, bc_case_value.getInt()); + } + } + // Make our getLabel calls later. + labels[numLabels++] = curPC; + pc = isw.getNextPC(); + continue; + } + case Constants._iinc: + { + buf[pc++] = (byte) bc; + int local = bc_local.getInt(); + int delta; + if (isWide) { + delta = bc_short.getInt(); + Instruction.setShort(buf, pc, local); pc += 2; + Instruction.setShort(buf, pc, delta); pc += 2; + } else { + delta = (byte) bc_byte.getByte(); + buf[pc++] = (byte)local; + buf[pc++] = (byte)delta; + } + continue; + } + case Constants._sipush: + { + int val = bc_short.getInt(); + buf[pc++] = (byte) bc; + Instruction.setShort(buf, pc, val); pc += 2; + continue; + } + case Constants._bipush: + case Constants._newarray: + { + int val = bc_byte.getByte(); + buf[pc++] = (byte) bc; + buf[pc++] = (byte) val; + continue; + } + case Constants._ref_escape: + { + // Note that insnMap has one entry for this. + hasEscs = true; + int size = bc_escrefsize.getInt(); + ConstantPool.Entry ref = bc_escref.getRef(); + if (size == 1) ldcRefSet.add(ref); + int fmt; + switch (size) { + case 1: fixupBuf.addU1(pc, ref); break; + case 2: fixupBuf.addU2(pc, ref); break; + default: assert(false); fmt = 0; + } + buf[pc+0] = buf[pc+1] = 0; + pc += size; + } + continue; + case Constants._byte_escape: + { + // Note that insnMap has one entry for all these bytes. + hasEscs = true; + int size = bc_escsize.getInt(); + while ((pc + size) > buf.length) + buf = realloc(buf); + while (size-- > 0) { + buf[pc++] = (byte) bc_escbyte.getByte(); + } + } + continue; + default: + if (Instruction.isInvokeInitOp(bc)) { + int idx = (bc - Constants._invokeinit_op); + int origBC = Constants._invokespecial; + ConstantPool.ClassEntry classRef; + switch (idx) { + case Constants._invokeinit_self_option: + classRef = thisClass; break; + case Constants._invokeinit_super_option: + classRef = superClass; break; + default: + assert(idx == Constants._invokeinit_new_option); + classRef = newClass; break; + } + buf[pc++] = (byte) origBC; + int coding = bc_initref.getInt(); + // Find the nth overloading of in classRef. + ConstantPool.MemberEntry ref = pkg.cp.getOverloadingForIndex(Constants.CONSTANT_Methodref, classRef, "", coding); + fixupBuf.addU2(pc, ref); + buf[pc+0] = buf[pc+1] = 0; + pc += 2; + assert(Instruction.opLength(origBC) == (pc - curPC)); + continue; + } + if (Instruction.isSelfLinkerOp(bc)) { + int idx = (bc - Constants._self_linker_op); + boolean isSuper = (idx >= Constants._self_linker_super_flag); + if (isSuper) idx -= Constants._self_linker_super_flag; + boolean isAload = (idx >= Constants._self_linker_aload_flag); + if (isAload) idx -= Constants._self_linker_aload_flag; + int origBC = Constants._first_linker_op + idx; + boolean isField = Instruction.isFieldOp(origBC); + CPRefBand bc_which; + ConstantPool.ClassEntry which_cls = isSuper ? superClass : thisClass; + ConstantPool.Index which_ix; + if (isField) { + bc_which = isSuper ? bc_superfield : bc_thisfield; + which_ix = pkg.cp.getMemberIndex(Constants.CONSTANT_Fieldref, which_cls); + } else { + bc_which = isSuper ? bc_supermethod : bc_thismethod; + which_ix = pkg.cp.getMemberIndex(Constants.CONSTANT_Methodref, which_cls); + } + assert(bc_which == selfOpRefBand(bc)); + ConstantPool.MemberEntry ref = (ConstantPool.MemberEntry) bc_which.getRef(which_ix); + if (isAload) { + buf[pc++] = (byte) Constants._aload_0; + curPC = pc; + // Note: insnMap keeps the _aload_0 separate. + insnMap[numInsns++] = curPC; + } + buf[pc++] = (byte) origBC; + fixupBuf.addU2(pc, ref); + buf[pc+0] = buf[pc+1] = 0; + pc += 2; + assert(Instruction.opLength(origBC) == (pc - curPC)); + continue; + } + if (Instruction.isBranchOp(bc)) { + buf[pc++] = (byte) bc; + assert(!isWide); // no wide prefix for branches + int nextPC = curPC + Instruction.opLength(bc); + // Make our getLabel calls later. + labels[numLabels++] = curPC; + //Instruction.at(buf, curPC).setBranchLabel(getLabel(bc_label, code, curPC)); + while (pc < nextPC) buf[pc++] = 0; + continue; + } + if (Instruction.isCPRefOp(bc)) { + CPRefBand bc_which = getCPRefOpBand(bc); + ConstantPool.Entry ref = bc_which.getRef(); + if (ref == null) { + if (bc_which == bc_classref) { + // Shorthand for class self-references. + ref = thisClass; + } else { + assert(false); + } + } + int origBC = bc; + int size = 2; + switch (bc) { + case Constants._invokestatic_int: + origBC = Constants._invokestatic; + break; + case Constants._invokespecial_int: + origBC = Constants._invokespecial; + break; + case Constants._ildc: + case Constants._cldc: + case Constants._fldc: + case Constants._sldc: + case Constants._qldc: + origBC = Constants._ldc; + size = 1; + ldcRefSet.add(ref); + break; + case Constants._ildc_w: + case Constants._cldc_w: + case Constants._fldc_w: + case Constants._sldc_w: + case Constants._qldc_w: + origBC = Constants._ldc_w; + break; + case Constants._lldc2_w: + case Constants._dldc2_w: + origBC = Constants._ldc2_w; + break; + case Constants._new: + newClass = (ConstantPool.ClassEntry) ref; + break; + } + buf[pc++] = (byte) origBC; + int fmt; + switch (size) { + case 1: fixupBuf.addU1(pc, ref); break; + case 2: fixupBuf.addU2(pc, ref); break; + default: assert(false); fmt = 0; + } + buf[pc+0] = buf[pc+1] = 0; + pc += size; + if (origBC == Constants._multianewarray) { + // Copy the trailing byte also. + int val = bc_byte.getByte(); + buf[pc++] = (byte) val; + } else if (origBC == Constants._invokeinterface) { + int argSize = ((ConstantPool.MemberEntry)ref).descRef.typeRef.computeSize(true); + buf[pc++] = (byte)( 1 + argSize ); + buf[pc++] = 0; + } else if (origBC == Constants._invokedynamic) { + buf[pc++] = 0; + buf[pc++] = 0; + } + assert(Instruction.opLength(origBC) == (pc - curPC)); + continue; + } + if (Instruction.isLocalSlotOp(bc)) { + buf[pc++] = (byte) bc; + int local = bc_local.getInt(); + if (isWide) { + Instruction.setShort(buf, pc, local); + pc += 2; + if (bc == Constants._iinc) { + int iVal = bc_short.getInt(); + Instruction.setShort(buf, pc, iVal); + pc += 2; + } + } else { + Instruction.setByte(buf, pc, local); + pc += 1; + if (bc == Constants._iinc) { + int iVal = bc_byte.getByte(); + Instruction.setByte(buf, pc, iVal); + pc += 1; + } + } + assert(Instruction.opLength(bc) == (pc - curPC)); + continue; + } + // Random bytecode. Just copy it. + if (bc >= Constants._bytecode_limit) + Utils.log.warning("unrecognized bytescode "+bc + +" "+Instruction.byteName(bc)); + assert(bc < Constants._bytecode_limit); + buf[pc++] = (byte) bc; + assert(Instruction.opLength(bc) == (pc - curPC)); + continue; + } + } + // now make a permanent copy of the bytecodes + code.setBytes(realloc(buf, pc)); + code.setInstructionMap(insnMap, numInsns); + // fix up labels, now that code has its insnMap + Instruction ibr = null; // temporary branch instruction + for (int i = 0; i < numLabels; i++) { + int curPC = labels[i]; + // (Note: Passing ibr in allows reuse, a speed hack.) + ibr = Instruction.at(code.bytes, curPC, ibr); + if (ibr instanceof Instruction.Switch) { + Instruction.Switch isw = (Instruction.Switch) ibr; + isw.setDefaultLabel(getLabel(bc_label, code, curPC)); + int caseCount = isw.getCaseCount(); + for (int j = 0; j < caseCount; j++) { + isw.setCaseLabel(j, getLabel(bc_label, code, curPC)); + } + } else { + ibr.setBranchLabel(getLabel(bc_label, code, curPC)); + } + } + if (fixupBuf.size() > 0) { + if (verbose > 2) + Utils.log.fine("Fixups in code: "+fixupBuf); + code.addFixups(fixupBuf); + } + } + } +} diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/PackageWriter.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/PackageWriter.java new file mode 100644 index 000000000..795b28fdc --- /dev/null +++ b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/PackageWriter.java @@ -0,0 +1,1738 @@ +/* + * Copyright (c) 2001, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package net.fabricmc.shade.com.sun.java.util.jar.pack; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Writer for a package file. + * @author John Rose + */ +class PackageWriter extends BandStructure { + Package pkg; + OutputStream finalOut; + Package.Version packageVersion; + + PackageWriter(Package pkg, OutputStream out) throws IOException { + this.pkg = pkg; + this.finalOut = out; + // Caller has specified maximum class file version in the package: + initHighestClassVersion(pkg.getHighestClassVersion()); + } + + void write() throws IOException { + boolean ok = false; + try { + if (verbose > 0) { + Utils.log.info("Setting up constant pool..."); + } + setup(); + + if (verbose > 0) { + Utils.log.info("Packing..."); + } + + // writeFileHeader() is done last, since it has ultimate counts + // writeBandHeaders() is called after all other bands are done + writeConstantPool(); + writeFiles(); + writeAttrDefs(); + writeInnerClasses(); + writeClassesAndByteCodes(); + writeAttrCounts(); + + if (verbose > 1) printCodeHist(); + + // choose codings (fill band_headers if needed) + if (verbose > 0) { + Utils.log.info("Coding..."); + } + all_bands.chooseBandCodings(); + + // now we can write the headers: + writeFileHeader(); + + writeAllBandsTo(finalOut); + + ok = true; + } catch (Exception ee) { + Utils.log.warning("Error on output: "+ee, ee); + //if (verbose > 0) ee.printStackTrace(); + // Write partial output only if we are verbose. + if (verbose > 0) finalOut.close(); + if (ee instanceof IOException) throw (IOException)ee; + if (ee instanceof RuntimeException) throw (RuntimeException)ee; + throw new Error("error packing", ee); + } + } + + Set requiredEntries; // for the CP + Map backCountTable; // for layout callables + int[][] attrCounts; // count attr. occurrences + + void setup() { + requiredEntries = new HashSet<>(); + setArchiveOptions(); + trimClassAttributes(); + collectAttributeLayouts(); + pkg.buildGlobalConstantPool(requiredEntries); + setBandIndexes(); + makeNewAttributeBands(); + collectInnerClasses(); + } + + /* + * Convenience function to choose an archive version based + * on the class file versions observed within the archive + * or set the user defined version preset via properties. + */ + void chooseDefaultPackageVersion() throws IOException { + if (pkg.packageVersion != null) { + packageVersion = pkg.packageVersion; + if (verbose > 0) { + Utils.log.info("package version overridden with: " + + packageVersion); + } + return; + } + + Package.Version highV = getHighestClassVersion(); + // set the package version now + if (highV.lessThan(Constants.JAVA6_MAX_CLASS_VERSION)) { + // There are only old classfiles in this segment or resources + packageVersion = Constants.JAVA5_PACKAGE_VERSION; + } else if (highV.equals(Constants.JAVA6_MAX_CLASS_VERSION) || + (highV.equals(Constants.JAVA7_MAX_CLASS_VERSION) && !pkg.cp.haveExtraTags())) { + // force down the package version if we have jdk7 classes without + // any Indy references, this is because jdk7 class file (51.0) without + // Indy is identical to jdk6 class file (50.0). + packageVersion = Constants.JAVA6_PACKAGE_VERSION; + } else if (highV.equals(Constants.JAVA7_MAX_CLASS_VERSION)) { + packageVersion = Constants.JAVA7_PACKAGE_VERSION; + } else { + // Normal case. Use the newest archive format, when available + packageVersion = Constants.JAVA8_PACKAGE_VERSION; + } + + if (verbose > 0) { + Utils.log.info("Highest version class file: " + highV + + " package version: " + packageVersion); + } + } + + void checkVersion() throws IOException { + assert(packageVersion != null); + + if (packageVersion.lessThan(Constants.JAVA7_PACKAGE_VERSION)) { + // this bit was reserved for future use in previous versions + if (testBit(archiveOptions, Constants.AO_HAVE_CP_EXTRAS)) { + throw new IOException("Format bits for Java 7 must be zero in previous releases"); + } + } + if (testBit(archiveOptions, Constants.AO_UNUSED_MBZ)) { + throw new IOException("High archive option bits are reserved and must be zero: " + Integer.toHexString(archiveOptions)); + } + } + + void setArchiveOptions() { + // Decide on some archive options early. + // Does not decide on: AO_HAVE_SPECIAL_FORMATS, + // AO_HAVE_CP_NUMBERS, AO_HAVE_FILE_HEADERS. + // Also, AO_HAVE_FILE_OPTIONS may be forced on later. + int minModtime = pkg.default_modtime; + int maxModtime = pkg.default_modtime; + int minOptions = -1; + int maxOptions = 0; + + // Import defaults from package (deflate hint, etc.). + archiveOptions |= pkg.default_options; + + for (Package.File file : pkg.files) { + int modtime = file.modtime; + int options = file.options; + + if (minModtime == Constants.NO_MODTIME) { + minModtime = maxModtime = modtime; + } else { + if (minModtime > modtime) minModtime = modtime; + if (maxModtime < modtime) maxModtime = modtime; + } + minOptions &= options; + maxOptions |= options; + } + if (pkg.default_modtime == Constants.NO_MODTIME) { + // Make everything else be a positive offset from here. + pkg.default_modtime = minModtime; + } + if (minModtime != Constants.NO_MODTIME && minModtime != maxModtime) { + // Put them into a band. + archiveOptions |= Constants.AO_HAVE_FILE_MODTIME; + } + // If the archive deflation is set do not bother with each file. + if (!testBit(archiveOptions,Constants.AO_DEFLATE_HINT) && minOptions != -1) { + if (testBit(minOptions, Constants.FO_DEFLATE_HINT)) { + // Every file has the deflate_hint set. + // Set it for the whole archive, and omit options. + archiveOptions |= Constants.AO_DEFLATE_HINT; + minOptions -= Constants.FO_DEFLATE_HINT; + maxOptions -= Constants.FO_DEFLATE_HINT; + } + pkg.default_options |= minOptions; + if (minOptions != maxOptions + || minOptions != pkg.default_options) { + archiveOptions |= Constants.AO_HAVE_FILE_OPTIONS; + } + } + // Decide on default version number (majority rule). + Map verCounts = new HashMap<>(); + int bestCount = 0; + Package.Version bestVersion = null; + for (Package.Class cls : pkg.classes) { + Package.Version version = cls.getVersion(); + int[] var = verCounts.get(version); + if (var == null) { + var = new int[1]; + verCounts.put(version, var); + } + int count = (var[0] += 1); + //System.out.println("version="+version+" count="+count); + if (bestCount < count) { + bestCount = count; + bestVersion = version; + } + } + verCounts.clear(); + if (bestVersion == null) bestVersion = Constants.JAVA_MIN_CLASS_VERSION; // degenerate case + pkg.defaultClassVersion = bestVersion; + if (verbose > 0) + Utils.log.info("Consensus version number in segment is " + bestVersion); + if (verbose > 0) + Utils.log.info("Highest version number in segment is " + + pkg.getHighestClassVersion()); + + // Now add explicit pseudo-attrs. to classes with odd versions. + for (Package.Class cls : pkg.classes) { + if (!cls.getVersion().equals(bestVersion)) { + Attribute a = makeClassFileVersionAttr(cls.getVersion()); + if (verbose > 1) { + Utils.log.fine("Version "+cls.getVersion() + " of " + cls + + " doesn't match package version " + + bestVersion); + } + // Note: Does not add in "natural" order. (Who cares?) + cls.addAttribute(a); + } + } + + // Decide if we are transmitting a huge resource file: + for (Package.File file : pkg.files) { + long len = file.getFileLength(); + if (len != (int)len) { + archiveOptions |= Constants.AO_HAVE_FILE_SIZE_HI; + if (verbose > 0) + Utils.log.info("Note: Huge resource file "+file.getFileName()+" forces 64-bit sizing"); + break; + } + } + + // Decide if code attributes typically have sub-attributes. + // In that case, to preserve compact 1-byte code headers, + // we must declare unconditional presence of code flags. + int cost0 = 0; + int cost1 = 0; + for (Package.Class cls : pkg.classes) { + for (Package.Class.Method m : cls.getMethods()) { + if (m.code != null) { + if (m.code.attributeSize() == 0) { + // cost of a useless unconditional flags byte + cost1 += 1; + } else if (shortCodeHeader(m.code) != LONG_CODE_HEADER) { + // cost of inflating a short header + cost0 += 3; + } + } + } + } + if (cost0 > cost1) { + archiveOptions |= Constants.AO_HAVE_ALL_CODE_FLAGS; + } + if (verbose > 0) + Utils.log.info("archiveOptions = " + +"0b"+Integer.toBinaryString(archiveOptions)); + } + + void writeFileHeader() throws IOException { + chooseDefaultPackageVersion(); + writeArchiveMagic(); + writeArchiveHeader(); + } + + // Local routine used to format fixed-format scalars + // in the file_header: + private void putMagicInt32(int val) throws IOException { + int res = val; + for (int i = 0; i < 4; i++) { + archive_magic.putByte(0xFF & (res >>> 24)); + res <<= 8; + } + } + + void writeArchiveMagic() throws IOException { + putMagicInt32(pkg.magic); + } + + void writeArchiveHeader() throws IOException { + // for debug only: number of words optimized away + int headerSizeForDebug = AH_LENGTH_MIN; + + // AO_HAVE_SPECIAL_FORMATS is set if non-default + // coding techniques are used, or if there are + // compressor-defined attributes transmitted. + boolean haveSpecial = testBit(archiveOptions, Constants.AO_HAVE_SPECIAL_FORMATS); + if (!haveSpecial) { + haveSpecial |= (band_headers.length() != 0); + haveSpecial |= (attrDefsWritten.length != 0); + if (haveSpecial) + archiveOptions |= Constants.AO_HAVE_SPECIAL_FORMATS; + } + if (haveSpecial) + headerSizeForDebug += AH_SPECIAL_FORMAT_LEN; + + // AO_HAVE_FILE_HEADERS is set if there is any + // file or segment envelope information present. + boolean haveFiles = testBit(archiveOptions, Constants.AO_HAVE_FILE_HEADERS); + if (!haveFiles) { + haveFiles |= (archiveNextCount > 0); + haveFiles |= (pkg.default_modtime != Constants.NO_MODTIME); + if (haveFiles) + archiveOptions |= Constants.AO_HAVE_FILE_HEADERS; + } + if (haveFiles) + headerSizeForDebug += AH_FILE_HEADER_LEN; + + // AO_HAVE_CP_NUMBERS is set if there are any numbers + // in the global constant pool. (Numbers are in 15% of classes.) + boolean haveNumbers = testBit(archiveOptions, Constants.AO_HAVE_CP_NUMBERS); + if (!haveNumbers) { + haveNumbers |= pkg.cp.haveNumbers(); + if (haveNumbers) + archiveOptions |= Constants.AO_HAVE_CP_NUMBERS; + } + if (haveNumbers) + headerSizeForDebug += AH_CP_NUMBER_LEN; + + // AO_HAVE_CP_EXTRAS is set if there are constant pool entries + // beyond the Java 6 version of the class file format. + boolean haveCPExtra = testBit(archiveOptions, Constants.AO_HAVE_CP_EXTRAS); + if (!haveCPExtra) { + haveCPExtra |= pkg.cp.haveExtraTags(); + if (haveCPExtra) + archiveOptions |= Constants.AO_HAVE_CP_EXTRAS; + } + if (haveCPExtra) + headerSizeForDebug += AH_CP_EXTRA_LEN; + + // the archiveOptions are all initialized, sanity check now!. + checkVersion(); + + archive_header_0.putInt(packageVersion.minor); + archive_header_0.putInt(packageVersion.major); + if (verbose > 0) + Utils.log.info("Package Version for this segment:" + packageVersion); + archive_header_0.putInt(archiveOptions); // controls header format + assert(archive_header_0.length() == AH_LENGTH_0); + + final int DUMMY = 0; + if (haveFiles) { + assert(archive_header_S.length() == AH_ARCHIVE_SIZE_HI); + archive_header_S.putInt(DUMMY); // (archiveSize1 >>> 32) + assert(archive_header_S.length() == AH_ARCHIVE_SIZE_LO); + archive_header_S.putInt(DUMMY); // (archiveSize1 >>> 0) + assert(archive_header_S.length() == AH_LENGTH_S); + } + + // Done with unsized part of header.... + + if (haveFiles) { + archive_header_1.putInt(archiveNextCount); // usually zero + archive_header_1.putInt(pkg.default_modtime); + archive_header_1.putInt(pkg.files.size()); + } else { + assert(pkg.files.isEmpty()); + } + + if (haveSpecial) { + archive_header_1.putInt(band_headers.length()); + archive_header_1.putInt(attrDefsWritten.length); + } else { + assert(band_headers.length() == 0); + assert(attrDefsWritten.length == 0); + } + + writeConstantPoolCounts(haveNumbers, haveCPExtra); + + archive_header_1.putInt(pkg.getAllInnerClasses().size()); + archive_header_1.putInt(pkg.defaultClassVersion.minor); + archive_header_1.putInt(pkg.defaultClassVersion.major); + archive_header_1.putInt(pkg.classes.size()); + + // Sanity: Make sure we came out to 29 (less optional fields): + assert(archive_header_0.length() + + archive_header_S.length() + + archive_header_1.length() + == headerSizeForDebug); + + // Figure out all the sizes now, first cut: + archiveSize0 = 0; + archiveSize1 = all_bands.outputSize(); + // Second cut: + archiveSize0 += archive_magic.outputSize(); + archiveSize0 += archive_header_0.outputSize(); + archiveSize0 += archive_header_S.outputSize(); + // Make the adjustments: + archiveSize1 -= archiveSize0; + + // Patch the header: + if (haveFiles) { + int archiveSizeHi = (int)(archiveSize1 >>> 32); + int archiveSizeLo = (int)(archiveSize1 >>> 0); + archive_header_S.patchValue(AH_ARCHIVE_SIZE_HI, archiveSizeHi); + archive_header_S.patchValue(AH_ARCHIVE_SIZE_LO, archiveSizeLo); + int zeroLen = UNSIGNED5.getLength(DUMMY); + archiveSize0 += UNSIGNED5.getLength(archiveSizeHi) - zeroLen; + archiveSize0 += UNSIGNED5.getLength(archiveSizeLo) - zeroLen; + } + if (verbose > 1) + Utils.log.fine("archive sizes: "+ + archiveSize0+"+"+archiveSize1); + assert(all_bands.outputSize() == archiveSize0+archiveSize1); + } + + void writeConstantPoolCounts(boolean haveNumbers, boolean haveCPExtra) throws IOException { + for (byte tag : ConstantPool.TAGS_IN_ORDER) { + int count = pkg.cp.getIndexByTag(tag).size(); + switch (tag) { + case Constants.CONSTANT_Utf8: + // The null string is always first. + if (count > 0) + assert(pkg.cp.getIndexByTag(tag).get(0) + == ConstantPool.getUtf8Entry("")); + break; + + case Constants.CONSTANT_Integer: + case Constants.CONSTANT_Float: + case Constants.CONSTANT_Long: + case Constants.CONSTANT_Double: + // Omit counts for numbers if possible. + if (!haveNumbers) { + assert(count == 0); + continue; + } + break; + + case Constants.CONSTANT_MethodHandle: + case Constants.CONSTANT_MethodType: + case Constants.CONSTANT_InvokeDynamic: + case Constants.CONSTANT_BootstrapMethod: + // Omit counts for newer entities if possible. + if (!haveCPExtra) { + assert(count == 0); + continue; + } + break; + } + archive_header_1.putInt(count); + } + } + + protected ConstantPool.Index getCPIndex(byte tag) { + return pkg.cp.getIndexByTag(tag); + } + +// (The following observations are out of date; they apply only to +// "banding" the constant pool itself. Later revisions of this algorithm +// applied the banding technique to every part of the package file, +// applying the benefits more broadly.) + +// Note: Keeping the data separate in passes (or "bands") allows the +// compressor to issue significantly shorter indexes for repeated data. +// The difference in zipped size is 4%, which is remarkable since the +// unzipped sizes are the same (only the byte order differs). + +// After moving similar data into bands, it becomes natural to delta-encode +// each band. (This is especially useful if we sort the constant pool first.) +// Delta encoding saves an extra 5% in the output size (13% of the CP itself). +// Because a typical delta usees much less data than a byte, the savings after +// zipping is even better: A zipped delta-encoded package is 8% smaller than +// a zipped non-delta-encoded package. Thus, in the zipped file, a banded, +// delta-encoded constant pool saves over 11% (of the total file size) compared +// with a zipped unbanded file. + + void writeConstantPool() throws IOException { + ConstantPool.IndexGroup cp = pkg.cp; + + if (verbose > 0) Utils.log.info("Writing CP"); + + for (byte tag : ConstantPool.TAGS_IN_ORDER) { + ConstantPool.Index index = cp.getIndexByTag(tag); + + ConstantPool.Entry[] cpMap = index.cpMap; + if (verbose > 0) + Utils.log.info("Writing "+cpMap.length+" "+ConstantPool.tagName(tag)+" entries..."); + + if (optDumpBands) { + try (PrintStream ps = new PrintStream(getDumpStream(index, ".idx"))) { + printArrayTo(ps, cpMap, 0, cpMap.length); + } + } + + switch (tag) { + case Constants.CONSTANT_Utf8: + writeUtf8Bands(cpMap); + break; + case Constants.CONSTANT_Integer: + for (int i = 0; i < cpMap.length; i++) { + ConstantPool.NumberEntry e = (ConstantPool.NumberEntry) cpMap[i]; + int x = ((Integer)e.numberValue()).intValue(); + cp_Int.putInt(x); + } + break; + case Constants.CONSTANT_Float: + for (int i = 0; i < cpMap.length; i++) { + ConstantPool.NumberEntry e = (ConstantPool.NumberEntry) cpMap[i]; + float fx = ((Float)e.numberValue()).floatValue(); + int x = Float.floatToIntBits(fx); + cp_Float.putInt(x); + } + break; + case Constants.CONSTANT_Long: + for (int i = 0; i < cpMap.length; i++) { + ConstantPool.NumberEntry e = (ConstantPool.NumberEntry) cpMap[i]; + long x = ((Long)e.numberValue()).longValue(); + cp_Long_hi.putInt((int)(x >>> 32)); + cp_Long_lo.putInt((int)(x >>> 0)); + } + break; + case Constants.CONSTANT_Double: + for (int i = 0; i < cpMap.length; i++) { + ConstantPool.NumberEntry e = (ConstantPool.NumberEntry) cpMap[i]; + double dx = ((Double)e.numberValue()).doubleValue(); + long x = Double.doubleToLongBits(dx); + cp_Double_hi.putInt((int)(x >>> 32)); + cp_Double_lo.putInt((int)(x >>> 0)); + } + break; + case Constants.CONSTANT_String: + for (int i = 0; i < cpMap.length; i++) { + ConstantPool.StringEntry e = (ConstantPool.StringEntry) cpMap[i]; + cp_String.putRef(e.ref); + } + break; + case Constants.CONSTANT_Class: + for (int i = 0; i < cpMap.length; i++) { + ConstantPool.ClassEntry e = (ConstantPool.ClassEntry) cpMap[i]; + cp_Class.putRef(e.ref); + } + break; + case Constants.CONSTANT_Signature: + writeSignatureBands(cpMap); + break; + case Constants.CONSTANT_NameandType: + for (int i = 0; i < cpMap.length; i++) { + ConstantPool.DescriptorEntry e = (ConstantPool.DescriptorEntry) cpMap[i]; + cp_Descr_name.putRef(e.nameRef); + cp_Descr_type.putRef(e.typeRef); + } + break; + case Constants.CONSTANT_Fieldref: + writeMemberRefs(tag, cpMap, cp_Field_class, cp_Field_desc); + break; + case Constants.CONSTANT_Methodref: + writeMemberRefs(tag, cpMap, cp_Method_class, cp_Method_desc); + break; + case Constants.CONSTANT_InterfaceMethodref: + writeMemberRefs(tag, cpMap, cp_Imethod_class, cp_Imethod_desc); + break; + case Constants.CONSTANT_MethodHandle: + for (int i = 0; i < cpMap.length; i++) { + ConstantPool.MethodHandleEntry e = (ConstantPool.MethodHandleEntry) cpMap[i]; + cp_MethodHandle_refkind.putInt(e.refKind); + cp_MethodHandle_member.putRef(e.memRef); + } + break; + case Constants.CONSTANT_MethodType: + for (int i = 0; i < cpMap.length; i++) { + ConstantPool.MethodTypeEntry e = (ConstantPool.MethodTypeEntry) cpMap[i]; + cp_MethodType.putRef(e.typeRef); + } + break; + case Constants.CONSTANT_InvokeDynamic: + for (int i = 0; i < cpMap.length; i++) { + ConstantPool.InvokeDynamicEntry e = (ConstantPool.InvokeDynamicEntry) cpMap[i]; + cp_InvokeDynamic_spec.putRef(e.bssRef); + cp_InvokeDynamic_desc.putRef(e.descRef); + } + break; + case Constants.CONSTANT_BootstrapMethod: + for (int i = 0; i < cpMap.length; i++) { + ConstantPool.BootstrapMethodEntry e = (ConstantPool.BootstrapMethodEntry) cpMap[i]; + cp_BootstrapMethod_ref.putRef(e.bsmRef); + cp_BootstrapMethod_arg_count.putInt(e.argRefs.length); + for (ConstantPool.Entry argRef : e.argRefs) { + cp_BootstrapMethod_arg.putRef(argRef); + } + } + break; + default: + throw new AssertionError("unexpected CP tag in package"); + } + } + if (optDumpBands || verbose > 1) { + for (byte tag = Constants.CONSTANT_GroupFirst; tag < Constants.CONSTANT_GroupLimit; tag++) { + ConstantPool.Index index = cp.getIndexByTag(tag); + if (index == null || index.isEmpty()) continue; + ConstantPool.Entry[] cpMap = index.cpMap; + if (verbose > 1) + Utils.log.info("Index group "+ConstantPool.tagName(tag)+" contains "+cpMap.length+" entries."); + if (optDumpBands) { + try (PrintStream ps = new PrintStream(getDumpStream(index.debugName, tag, ".gidx", index))) { + printArrayTo(ps, cpMap, 0, cpMap.length, true); + } + } + } + } + } + + void writeUtf8Bands(ConstantPool.Entry[] cpMap) throws IOException { + if (cpMap.length == 0) + return; // nothing to write + + // The first element must always be the empty string. + assert(cpMap[0].stringValue().equals("")); + final int SUFFIX_SKIP_1 = 1; + final int PREFIX_SKIP_2 = 2; + + // Fetch the char arrays, first of all. + char[][] chars = new char[cpMap.length][]; + for (int i = 0; i < chars.length; i++) { + chars[i] = cpMap[i].stringValue().toCharArray(); + } + + // First band: Write lengths of shared prefixes. + int[] prefixes = new int[cpMap.length]; // includes 2 skipped zeroes + char[] prevChars = {}; + for (int i = 0; i < chars.length; i++) { + int prefix = 0; + char[] curChars = chars[i]; + int limit = Math.min(curChars.length, prevChars.length); + while (prefix < limit && curChars[prefix] == prevChars[prefix]) + prefix++; + prefixes[i] = prefix; + if (i >= PREFIX_SKIP_2) + cp_Utf8_prefix.putInt(prefix); + else + assert(prefix == 0); + prevChars = curChars; + } + + // Second band: Write lengths of unshared suffixes. + // Third band: Write the char values in the unshared suffixes. + for (int i = 0; i < chars.length; i++) { + char[] str = chars[i]; + int prefix = prefixes[i]; + int suffix = str.length - prefixes[i]; + boolean isPacked = false; + if (suffix == 0) { + // Zero suffix length is special flag to indicate + // separate treatment in cp_Utf8_big bands. + // This suffix length never occurs naturally, + // except in the one case of a zero-length string. + // (If it occurs, it is the first, due to sorting.) + // The zero length string must, paradoxically, be + // encoded as a zero-length cp_Utf8_big band. + // This wastes exactly (& tolerably) one null byte. + isPacked = (i >= SUFFIX_SKIP_1); + // Do not bother to add an empty "(Utf8_big_0)" band. + // Also, the initial empty string does not require a band. + } else if (optBigStrings && effort > 1 && suffix > 100) { + int numWide = 0; + for (int n = 0; n < suffix; n++) { + if (str[prefix+n] > 127) { + numWide++; + } + } + if (numWide > 100) { + // Try packing the chars with an alternate encoding. + isPacked = tryAlternateEncoding(i, numWide, str, prefix); + } + } + if (i < SUFFIX_SKIP_1) { + // No output. + assert(!isPacked); + assert(suffix == 0); + } else if (isPacked) { + // Mark packed string with zero-length suffix count. + // This tells the unpacker to go elsewhere for the suffix bits. + // Fourth band: Write unshared suffix with alternate coding. + cp_Utf8_suffix.putInt(0); + cp_Utf8_big_suffix.putInt(suffix); + } else { + assert(suffix != 0); // would be ambiguous + // Normal string. Save suffix in third and fourth bands. + cp_Utf8_suffix.putInt(suffix); + for (int n = 0; n < suffix; n++) { + int ch = str[prefix+n]; + cp_Utf8_chars.putInt(ch); + } + } + } + if (verbose > 0) { + int normCharCount = cp_Utf8_chars.length(); + int packCharCount = cp_Utf8_big_chars.length(); + int charCount = normCharCount + packCharCount; + Utils.log.info("Utf8string #CHARS="+charCount+" #PACKEDCHARS="+packCharCount); + } + } + + private boolean tryAlternateEncoding(int i, int numWide, + char[] str, int prefix) { + int suffix = str.length - prefix; + int[] cvals = new int[suffix]; + for (int n = 0; n < suffix; n++) { + cvals[n] = str[prefix+n]; + } + CodingChooser cc = getCodingChooser(); + Coding bigRegular = cp_Utf8_big_chars.regularCoding; + String bandName = "(Utf8_big_"+i+")"; + int[] sizes = { 0, 0 }; + final int BYTE_SIZE = CodingChooser.BYTE_SIZE; + final int ZIP_SIZE = CodingChooser.ZIP_SIZE; + if (verbose > 1 || cc.verbose > 1) { + Utils.log.fine("--- chooseCoding "+bandName); + } + CodingMethod special = cc.choose(cvals, bigRegular, sizes); + Coding charRegular = cp_Utf8_chars.regularCoding; + if (verbose > 1) + Utils.log.fine("big string["+i+"] len="+suffix+" #wide="+numWide+" size="+sizes[BYTE_SIZE]+"/z="+sizes[ZIP_SIZE]+" coding "+special); + if (special != charRegular) { + int specialZipSize = sizes[ZIP_SIZE]; + int[] normalSizes = cc.computeSize(charRegular, cvals); + int normalZipSize = normalSizes[ZIP_SIZE]; + int minWin = Math.max(5, normalZipSize/1000); + if (verbose > 1) + Utils.log.fine("big string["+i+"] normalSize="+normalSizes[BYTE_SIZE]+"/z="+normalSizes[ZIP_SIZE]+" win="+(specialZipSize>> 32)); + if (haveModtime) + file_modtime.putInt(file.modtime - pkg.default_modtime); + if (haveOptions) + file_options.putInt(file.options); + file.writeTo(file_bits.collectorStream()); + if (verbose > 1) + Utils.log.fine("Wrote "+len+" bytes of "+file.name.stringValue()); + } + if (verbose > 0) + Utils.log.info("Wrote "+numFiles+" resource files"); + } + + void collectAttributeLayouts() { + maxFlags = new int[Constants.ATTR_CONTEXT_LIMIT]; + allLayouts = new FixedList<>(Constants.ATTR_CONTEXT_LIMIT); + for (int i = 0; i < Constants.ATTR_CONTEXT_LIMIT; i++) { + allLayouts.set(i, new HashMap<>()); + } + // Collect maxFlags and allLayouts. + for (Package.Class cls : pkg.classes) { + visitAttributeLayoutsIn(Constants.ATTR_CONTEXT_CLASS, cls); + for (Package.Class.Field f : cls.getFields()) { + visitAttributeLayoutsIn(Constants.ATTR_CONTEXT_FIELD, f); + } + for (Package.Class.Method m : cls.getMethods()) { + visitAttributeLayoutsIn(Constants.ATTR_CONTEXT_METHOD, m); + if (m.code != null) { + visitAttributeLayoutsIn(Constants.ATTR_CONTEXT_CODE, m.code); + } + } + } + // If there are many species of attributes, use 63-bit flags. + for (int i = 0; i < Constants.ATTR_CONTEXT_LIMIT; i++) { + int nl = allLayouts.get(i).size(); + boolean haveLongFlags = haveFlagsHi(i); + final int TOO_MANY_ATTRS = 32 /*int flag size*/ + - 12 /*typical flag bits in use*/ + + 4 /*typical number of OK overflows*/; + if (nl >= TOO_MANY_ATTRS) { // heuristic + int mask = 1<<(Constants.LG_AO_HAVE_XXX_FLAGS_HI+i); + archiveOptions |= mask; + haveLongFlags = true; + if (verbose > 0) + Utils.log.info("Note: Many "+Attribute.contextName(i)+" attributes forces 63-bit flags"); + } + if (verbose > 1) { + Utils.log.fine(Attribute.contextName(i)+".maxFlags = 0x"+Integer.toHexString(maxFlags[i])); + Utils.log.fine(Attribute.contextName(i)+".#layouts = "+nl); + } + assert(haveFlagsHi(i) == haveLongFlags); + } + initAttrIndexLimit(); + + // Standard indexes can never conflict with flag bits. Assert it. + for (int i = 0; i < Constants.ATTR_CONTEXT_LIMIT; i++) { + assert((attrFlagMask[i] & maxFlags[i]) == 0); + } + // Collect counts for both predefs. and custom defs. + // Decide on custom, local attribute definitions. + backCountTable = new HashMap<>(); + attrCounts = new int[Constants.ATTR_CONTEXT_LIMIT][]; + for (int i = 0; i < Constants.ATTR_CONTEXT_LIMIT; i++) { + // Now the remaining defs in allLayouts[i] need attr. indexes. + // Fill up unused flag bits with new defs. + // Unused bits are those which are not used by predefined attrs, + // and which are always clear in the classfiles. + long avHiBits = ~(maxFlags[i] | attrFlagMask[i]); + assert(attrIndexLimit[i] > 0); + assert(attrIndexLimit[i] < 64); // all bits fit into a Java long + avHiBits &= (1L< defMap = allLayouts.get(i); + @SuppressWarnings({"unchecked", "rawtypes"}) + Map.Entry[] layoutsAndCounts = + new Map.Entry[defMap.size()]; + defMap.entrySet().toArray(layoutsAndCounts); + // Sort by count, most frequent first. + // Predefs. participate in this sort, though it does not matter. + Arrays.sort(layoutsAndCounts, + new Comparator>() { + public int compare(Map.Entry e0, + Map.Entry e1) { + // Primary sort key is count, reversed. + int r = -(e0.getValue()[0] - e1.getValue()[0]); + if (r != 0) return r; + return e0.getKey().compareTo(e1.getKey()); + } + }); + attrCounts[i] = new int[attrIndexLimit[i]+layoutsAndCounts.length]; + for (int j = 0; j < layoutsAndCounts.length; j++) { + Map.Entry e = layoutsAndCounts[j]; + Attribute.Layout def = e.getKey(); + int count = e.getValue()[0]; + int index; + Integer predefIndex = attrIndexTable.get(def); + if (predefIndex != null) { + // The index is already set. + index = predefIndex.intValue(); + } else if (avHiBits != 0) { + while ((avHiBits & 1) == 0) { + avHiBits >>>= 1; + nextLoBit += 1; + } + avHiBits -= 1; // clear low bit; we are using it now + // Update attrIndexTable: + index = setAttributeLayoutIndex(def, nextLoBit); + } else { + // Update attrIndexTable: + index = setAttributeLayoutIndex(def, ATTR_INDEX_OVERFLOW); + } + + // Now that we know the index, record the count of this def. + attrCounts[i][index] = count; + + // For all callables in the def, keep a tally of back-calls. + Attribute.Layout.Element[] cbles = def.getCallables(); + final int[] bc = new int[cbles.length]; + for (int k = 0; k < cbles.length; k++) { + assert(cbles[k].kind == Attribute.EK_CBLE); + if (!cbles[k].flagTest(Attribute.EF_BACK)) { + bc[k] = -1; // no count to accumulate here + } + } + backCountTable.put(def, bc); + + if (predefIndex == null) { + // Make sure the package CP can name the local attribute. + ConstantPool.Entry ne = ConstantPool.getUtf8Entry(def.name()); + String layout = def.layoutForClassVersion(getHighestClassVersion()); + ConstantPool.Entry le = ConstantPool.getUtf8Entry(layout); + requiredEntries.add(ne); + requiredEntries.add(le); + if (verbose > 0) { + if (index < attrIndexLimit[i]) + Utils.log.info("Using free flag bit 1<<"+index+" for "+count+" occurrences of "+def); + else + Utils.log.info("Using overflow index "+index+" for "+count+" occurrences of "+def); + } + } + } + } + // Later, when emitting attr_definition_bands, we will look at + // attrDefSeen and attrDefs at position 32/63 and beyond. + // The attrIndexTable will provide elements of xxx_attr_indexes bands. + + // Done with scratch variables: + maxFlags = null; + allLayouts = null; + } + + // Scratch variables for processing attributes and flags. + int[] maxFlags; + List> allLayouts; + + void visitAttributeLayoutsIn(int ctype, Attribute.Holder h) { + // Make note of which flags appear in the class file. + // Set them in maxFlags. + maxFlags[ctype] |= h.flags; + for (Attribute a : h.getAttributes()) { + Attribute.Layout def = a.layout(); + Map defMap = allLayouts.get(ctype); + int[] count = defMap.get(def); + if (count == null) { + defMap.put(def, count = new int[1]); + } + if (count[0] < Integer.MAX_VALUE) { + count[0] += 1; + } + } + } + + Attribute.Layout[] attrDefsWritten; + + void writeAttrDefs() throws IOException { + List defList = new ArrayList<>(); + for (int i = 0; i < Constants.ATTR_CONTEXT_LIMIT; i++) { + int limit = attrDefs.get(i).size(); + for (int j = 0; j < limit; j++) { + int header = i; // ctype + if (j < attrIndexLimit[i]) { + header |= ((j + ADH_BIT_IS_LSB) << ADH_BIT_SHIFT); + assert(header < 0x100); // must fit into a byte + // (...else header is simply ctype, with zero high bits.) + if (!testBit(attrDefSeen[i], 1L<() { + public int compare(Object[] a0, Object[] a1) { + // Primary sort key is attr def header. + @SuppressWarnings("unchecked") + int r = ((Comparable)a0[0]).compareTo(a1[0]); + if (r != 0) return r; + Integer ind0 = attrIndexTable.get(a0[1]); + Integer ind1 = attrIndexTable.get(a1[1]); + // Secondary sort key is attribute index. + // (This must be so, in order to keep overflow attr order.) + assert(ind0 != null); + assert(ind1 != null); + return ind0.compareTo(ind1); + } + }); + attrDefsWritten = new Attribute.Layout[numAttrDefs]; + try (PrintStream dump = !optDumpBands ? null + : new PrintStream(getDumpStream(attr_definition_headers, ".def"))) + { + int[] indexForDebug = Arrays.copyOf(attrIndexLimit, Constants.ATTR_CONTEXT_LIMIT); + for (int i = 0; i < defs.length; i++) { + int header = ((Integer)defs[i][0]).intValue(); + Attribute.Layout def = (Attribute.Layout) defs[i][1]; + attrDefsWritten[i] = def; + assert((header & ADH_CONTEXT_MASK) == def.ctype()); + attr_definition_headers.putByte(header); + attr_definition_name.putRef(ConstantPool.getUtf8Entry(def.name())); + String layout = def.layoutForClassVersion(getHighestClassVersion()); + attr_definition_layout.putRef(ConstantPool.getUtf8Entry(layout)); + // Check that we are transmitting that correct attribute index: + boolean debug = false; + assert(debug = true); + if (debug) { + int hdrIndex = (header >> ADH_BIT_SHIFT) - ADH_BIT_IS_LSB; + if (hdrIndex < 0) hdrIndex = indexForDebug[def.ctype()]++; + int realIndex = (attrIndexTable.get(def)).intValue(); + assert(hdrIndex == realIndex); + } + if (dump != null) { + int index = (header >> ADH_BIT_SHIFT) - ADH_BIT_IS_LSB; + dump.println(index+" "+def); + } + } + } + } + + void writeAttrCounts() throws IOException { + // Write the four xxx_attr_calls bands. + for (int ctype = 0; ctype < Constants.ATTR_CONTEXT_LIMIT; ctype++) { + MultiBand xxx_attr_bands = attrBands[ctype]; + IntBand xxx_attr_calls = getAttrBand(xxx_attr_bands, AB_ATTR_CALLS); + Attribute.Layout[] defs = new Attribute.Layout[attrDefs.get(ctype).size()]; + attrDefs.get(ctype).toArray(defs); + for (boolean predef = true; ; predef = false) { + for (int ai = 0; ai < defs.length; ai++) { + Attribute.Layout def = defs[ai]; + if (def == null) continue; // unused index + if (predef != isPredefinedAttr(ctype, ai)) + continue; // wrong pass + int totalCount = attrCounts[ctype][ai]; + if (totalCount == 0) + continue; // irrelevant + int[] bc = backCountTable.get(def); + for (int j = 0; j < bc.length; j++) { + if (bc[j] >= 0) { + int backCount = bc[j]; + bc[j] = -1; // close out; do not collect further counts + xxx_attr_calls.putInt(backCount); + assert(def.getCallables()[j].flagTest(Attribute.EF_BACK)); + } else { + assert(!def.getCallables()[j].flagTest(Attribute.EF_BACK)); + } + } + } + if (!predef) break; + } + } + } + + void trimClassAttributes() { + for (Package.Class cls : pkg.classes) { + // Replace "obvious" SourceFile attrs by null. + cls.minimizeSourceFile(); + // BootstrapMethods should never have been inserted. + assert(cls.getAttribute(Package.attrBootstrapMethodsEmpty) == null); + } + } + + void collectInnerClasses() { + // Capture inner classes, removing them from individual classes. + // Irregular inner classes must stay local, though. + Map allICMap = new HashMap<>(); + // First, collect a consistent global set. + for (Package.Class cls : pkg.classes) { + if (!cls.hasInnerClasses()) continue; + for (Package.InnerClass ic : cls.getInnerClasses()) { + Package.InnerClass pic = allICMap.put(ic.thisClass, ic); + if (pic != null && !pic.equals(ic) && pic.predictable) { + // Different ICs. Choose the better to make global. + allICMap.put(pic.thisClass, pic); + } + } + } + + Package.InnerClass[] allICs = new Package.InnerClass[allICMap.size()]; + allICMap.values().toArray(allICs); + allICMap = null; // done with it + + // Note: The InnerClasses attribute must be in a valid order, + // so that A$B always occurs earlier than A$B$C. This is an + // important side-effect of sorting lexically by class name. + Arrays.sort(allICs); // put in canonical order + pkg.setAllInnerClasses(Arrays.asList(allICs)); + + // Next, empty out of every local set the consistent entries. + // Calculate whether there is any remaining need to have a local + // set, and whether it needs to be locked. + for (Package.Class cls : pkg.classes) { + cls.minimizeLocalICs(); + } + } + + void writeInnerClasses() throws IOException { + for (Package.InnerClass ic : pkg.getAllInnerClasses()) { + int flags = ic.flags; + assert((flags & Constants.ACC_IC_LONG_FORM) == 0); + if (!ic.predictable) { + flags |= Constants.ACC_IC_LONG_FORM; + } + ic_this_class.putRef(ic.thisClass); + ic_flags.putInt(flags); + if (!ic.predictable) { + ic_outer_class.putRef(ic.outerClass); + ic_name.putRef(ic.name); + } + } + } + + /** If there are any extra InnerClasses entries to write which are + * not already implied by the global table, put them into a + * local attribute. This is expected to be rare. + */ + void writeLocalInnerClasses(Package.Class cls) throws IOException { + List localICs = cls.getInnerClasses(); + class_InnerClasses_N.putInt(localICs.size()); + for(Package.InnerClass ic : localICs) { + class_InnerClasses_RC.putRef(ic.thisClass); + // Is it redundant with the global version? + if (ic.equals(pkg.getGlobalInnerClass(ic.thisClass))) { + // A zero flag means copy a global IC here. + class_InnerClasses_F.putInt(0); + } else { + int flags = ic.flags; + if (flags == 0) + flags = Constants.ACC_IC_LONG_FORM; // force it to be non-zero + class_InnerClasses_F.putInt(flags); + class_InnerClasses_outer_RCN.putRef(ic.outerClass); + class_InnerClasses_name_RUN.putRef(ic.name); + } + } + } + + void writeClassesAndByteCodes() throws IOException { + Package.Class[] classes = new Package.Class[pkg.classes.size()]; + pkg.classes.toArray(classes); + // Note: This code respects the order in which caller put classes. + if (verbose > 0) + Utils.log.info(" ...scanning "+classes.length+" classes..."); + + int nwritten = 0; + for (int i = 0; i < classes.length; i++) { + // Collect the class body, sans bytecodes. + Package.Class cls = classes[i]; + if (verbose > 1) + Utils.log.fine("Scanning "+cls); + + ConstantPool.ClassEntry thisClass = cls.thisClass; + ConstantPool.ClassEntry superClass = cls.superClass; + ConstantPool.ClassEntry[] interfaces = cls.interfaces; + // Encode rare case of null superClass as thisClass: + assert(superClass != thisClass); // bad class file!? + if (superClass == null) superClass = thisClass; + class_this.putRef(thisClass); + class_super.putRef(superClass); + class_interface_count.putInt(cls.interfaces.length); + for (int j = 0; j < interfaces.length; j++) { + class_interface.putRef(interfaces[j]); + } + + writeMembers(cls); + writeAttrs(Constants.ATTR_CONTEXT_CLASS, cls, cls); + + nwritten++; + if (verbose > 0 && (nwritten % 1000) == 0) + Utils.log.info("Have scanned "+nwritten+" classes..."); + } + } + + void writeMembers(Package.Class cls) throws IOException { + List fields = cls.getFields(); + class_field_count.putInt(fields.size()); + for (Package.Class.Field f : fields) { + field_descr.putRef(f.getDescriptor()); + writeAttrs(Constants.ATTR_CONTEXT_FIELD, f, cls); + } + + List methods = cls.getMethods(); + class_method_count.putInt(methods.size()); + for (Package.Class.Method m : methods) { + method_descr.putRef(m.getDescriptor()); + writeAttrs(Constants.ATTR_CONTEXT_METHOD, m, cls); + assert((m.code != null) == (m.getAttribute(attrCodeEmpty) != null)); + if (m.code != null) { + writeCodeHeader(m.code); + writeByteCodes(m.code); + } + } + } + + void writeCodeHeader(Code c) throws IOException { + boolean attrsOK = testBit(archiveOptions, Constants.AO_HAVE_ALL_CODE_FLAGS); + int na = c.attributeSize(); + int sc = shortCodeHeader(c); + if (!attrsOK && na > 0) + // We must write flags, and can only do so for long headers. + sc = LONG_CODE_HEADER; + if (verbose > 2) { + int siglen = c.getMethod().getArgumentSize(); + Utils.log.fine("Code sizes info "+c.max_stack+" "+c.max_locals+" "+c.getHandlerCount()+" "+siglen+" "+na+(sc > 0 ? " SHORT="+sc : "")); + } + code_headers.putByte(sc); + if (sc == LONG_CODE_HEADER) { + code_max_stack.putInt(c.getMaxStack()); + code_max_na_locals.putInt(c.getMaxNALocals()); + code_handler_count.putInt(c.getHandlerCount()); + } else { + assert(attrsOK || na == 0); + assert(c.getHandlerCount() < shortCodeHeader_h_limit); + } + writeCodeHandlers(c); + if (sc == LONG_CODE_HEADER || attrsOK) + writeAttrs(Constants.ATTR_CONTEXT_CODE, c, c.thisClass()); + } + + void writeCodeHandlers(Code c) throws IOException { + int sum, del; + for (int j = 0, jmax = c.getHandlerCount(); j < jmax; j++) { + code_handler_class_RCN.putRef(c.handler_class[j]); // null OK + // Encode end as offset from start, and catch as offset from end, + // because they are strongly correlated. + sum = c.encodeBCI(c.handler_start[j]); + code_handler_start_P.putInt(sum); + del = c.encodeBCI(c.handler_end[j]) - sum; + code_handler_end_PO.putInt(del); + sum += del; + del = c.encodeBCI(c.handler_catch[j]) - sum; + code_handler_catch_PO.putInt(del); + } + } + + // Generic routines for writing attributes and flags of + // classes, fields, methods, and codes. + void writeAttrs(int ctype, + final Attribute.Holder h, + Package.Class cls) throws IOException { + MultiBand xxx_attr_bands = attrBands[ctype]; + IntBand xxx_flags_hi = getAttrBand(xxx_attr_bands, AB_FLAGS_HI); + IntBand xxx_flags_lo = getAttrBand(xxx_attr_bands, AB_FLAGS_LO); + boolean haveLongFlags = haveFlagsHi(ctype); + assert(attrIndexLimit[ctype] == (haveLongFlags? 63: 32)); + if (h.attributes == null) { + xxx_flags_lo.putInt(h.flags); // no extra bits to set here + if (haveLongFlags) + xxx_flags_hi.putInt(0); + return; + } + if (verbose > 3) + Utils.log.fine("Transmitting attrs for "+h+" flags="+Integer.toHexString(h.flags)); + + long flagMask = attrFlagMask[ctype]; // which flags are attr bits? + long flagsToAdd = 0; + int overflowCount = 0; + for (Attribute a : h.attributes) { + Attribute.Layout def = a.layout(); + int index = (attrIndexTable.get(def)).intValue(); + assert(attrDefs.get(ctype).get(index) == def); + if (verbose > 3) + Utils.log.fine("add attr @"+index+" "+a+" in "+h); + if (index < attrIndexLimit[ctype] && testBit(flagMask, 1L< 3) + Utils.log.fine("Adding flag bit 1<<"+index+" in "+Long.toHexString(flagMask)); + assert(!testBit(h.flags, 1L< 3) + Utils.log.fine("Adding overflow attr #"+overflowCount); + IntBand xxx_attr_indexes = getAttrBand(xxx_attr_bands, AB_ATTR_INDEXES); + xxx_attr_indexes.putInt(index); + // System.out.println("overflow @"+index); + } + if (def.bandCount == 0) { + if (def == attrInnerClassesEmpty) { + // Special logic to write this attr. + writeLocalInnerClasses((Package.Class) h); + continue; + } + // Empty attr; nothing more to write here. + continue; + } + assert(a.fixups == null); + final Band[] ab = attrBandTable.get(def); + assert(ab != null); + assert(ab.length == def.bandCount); + final int[] bc = backCountTable.get(def); + assert(bc != null); + assert(bc.length == def.getCallables().length); + // Write one attribute of type def into ab. + if (verbose > 2) Utils.log.fine("writing "+a+" in "+h); + boolean isCV = (ctype == Constants.ATTR_CONTEXT_FIELD && def == attrConstantValue); + if (isCV) setConstantValueIndex((Package.Class.Field)h); + a.parse(cls, a.bytes(), 0, a.size(), + new Attribute.ValueStream() { + public void putInt(int bandIndex, int value) { + ((IntBand) ab[bandIndex]).putInt(value); + } + public void putRef(int bandIndex, ConstantPool.Entry ref) { + ((CPRefBand) ab[bandIndex]).putRef(ref); + } + public int encodeBCI(int bci) { + Code code = (Code) h; + return code.encodeBCI(bci); + } + public void noteBackCall(int whichCallable) { + assert(bc[whichCallable] >= 0); + bc[whichCallable] += 1; + } + }); + if (isCV) setConstantValueIndex(null); // clean up + } + + if (overflowCount > 0) { + IntBand xxx_attr_count = getAttrBand(xxx_attr_bands, AB_ATTR_COUNT); + xxx_attr_count.putInt(overflowCount); + } + + xxx_flags_lo.putInt(h.flags | (int)flagsToAdd); + if (haveLongFlags) + xxx_flags_hi.putInt((int)(flagsToAdd >>> 32)); + else + assert((flagsToAdd >>> 32) == 0); + assert((h.flags & flagsToAdd) == 0) + : (h+".flags=" + +Integer.toHexString(h.flags)+"^" + +Long.toHexString(flagsToAdd)); + } + + // temporary scratch variables for processing code blocks + private Code curCode; + private Package.Class curClass; + private ConstantPool.Entry[] curCPMap; + private void beginCode(Code c) { + assert(curCode == null); + curCode = c; + curClass = c.m.thisClass(); + curCPMap = c.getCPMap(); + } + private void endCode() { + curCode = null; + curClass = null; + curCPMap = null; + } + + // Return an _invokeinit_op variant, if the instruction matches one, + // else -1. + private int initOpVariant(Instruction i, ConstantPool.Entry newClass) { + if (i.getBC() != Constants._invokespecial) return -1; + ConstantPool.MemberEntry ref = (ConstantPool.MemberEntry) i.getCPRef(curCPMap); + if ("".equals(ref.descRef.nameRef.stringValue()) == false) + return -1; + ConstantPool.ClassEntry refClass = ref.classRef; + if (refClass == curClass.thisClass) + return Constants._invokeinit_op+Constants._invokeinit_self_option; + if (refClass == curClass.superClass) + return Constants._invokeinit_op+Constants._invokeinit_super_option; + if (refClass == newClass) + return Constants._invokeinit_op+Constants._invokeinit_new_option; + return -1; + } + + // Return a _self_linker_op variant, if the instruction matches one, + // else -1. + private int selfOpVariant(Instruction i) { + int bc = i.getBC(); + if (!(bc >= Constants._first_linker_op && bc <= Constants._last_linker_op)) return -1; + ConstantPool.MemberEntry ref = (ConstantPool.MemberEntry) i.getCPRef(curCPMap); + // do not optimize this case, simply fall back to regular coding + if ((bc == Constants._invokespecial || bc == Constants._invokestatic) && + ref.tagEquals(Constants.CONSTANT_InterfaceMethodref)) + return -1; + ConstantPool.ClassEntry refClass = ref.classRef; + int self_bc = Constants._self_linker_op + (bc - Constants._first_linker_op); + if (refClass == curClass.thisClass) + return self_bc; + if (refClass == curClass.superClass) + return self_bc + Constants._self_linker_super_flag; + return -1; + } + + void writeByteCodes(Code code) throws IOException { + beginCode(code); + ConstantPool.IndexGroup cp = pkg.cp; + + // true if the previous instruction is an aload to absorb + boolean prevAload = false; + + // class of most recent new; helps compress calls + ConstantPool.Entry newClass = null; + + for (Instruction i = code.instructionAt(0); i != null; i = i.next()) { + // %%% Add a stress mode which issues _ref/_byte_escape. + if (verbose > 3) Utils.log.fine(i.toString()); + + if (i.isNonstandard()) { + // Crash and burn with a complaint if there are funny + // bytecodes in this class file. + String complaint = code.getMethod() + +" contains an unrecognized bytecode "+i + +"; please use the pass-file option on this class."; + Utils.log.warning(complaint); + throw new IOException(complaint); + } + + if (i.isWide()) { + if (verbose > 1) { + Utils.log.fine("_wide opcode in "+code); + Utils.log.fine(i.toString()); + } + bc_codes.putByte(Constants._wide); + codeHist[Constants._wide]++; + } + + int bc = i.getBC(); + + // Begin "bc_linker" compression. + if (bc == Constants._aload_0) { + // Try to group aload_0 with a following operation. + Instruction ni = code.instructionAt(i.getNextPC()); + if (selfOpVariant(ni) >= 0) { + prevAload = true; + continue; + } + } + + // Test for invocations: + int init_bc = initOpVariant(i, newClass); + if (init_bc >= 0) { + if (prevAload) { + // get rid of it + bc_codes.putByte(Constants._aload_0); + codeHist[Constants._aload_0]++; + prevAload = false; //used up + } + // Write special bytecode. + bc_codes.putByte(init_bc); + codeHist[init_bc]++; + ConstantPool.MemberEntry ref = (ConstantPool.MemberEntry) i.getCPRef(curCPMap); + // Write operand to a separate band. + int coding = cp.getOverloadingIndex(ref); + bc_initref.putInt(coding); + continue; + } + + int self_bc = selfOpVariant(i); + if (self_bc >= 0) { + boolean isField = Instruction.isFieldOp(bc); + boolean isSuper = (self_bc >= Constants._self_linker_op+Constants._self_linker_super_flag); + boolean isAload = prevAload; + prevAload = false; //used up + if (isAload) + self_bc += Constants._self_linker_aload_flag; + // Write special bytecode. + bc_codes.putByte(self_bc); + codeHist[self_bc]++; + // Write field or method ref to a separate band. + ConstantPool.MemberEntry ref = (ConstantPool.MemberEntry) i.getCPRef(curCPMap); + CPRefBand bc_which = selfOpRefBand(self_bc); + ConstantPool.Index which_ix = cp.getMemberIndex(ref.tag, ref.classRef); + bc_which.putRef(ref, which_ix); + continue; + } + assert(!prevAload); + // End "bc_linker" compression. + + // Normal bytecode. + codeHist[bc]++; + switch (bc) { + case Constants._tableswitch: // apc: (df, lo, hi, (hi-lo+1)*(label)) + case Constants._lookupswitch: // apc: (df, nc, nc*(case, label)) + bc_codes.putByte(bc); + Instruction.Switch isw = (Instruction.Switch) i; + // Note that we do not write the alignment bytes. + int apc = isw.getAlignedPC(); + int npc = isw.getNextPC(); + // write a length specification into the bytecode stream + int caseCount = isw.getCaseCount(); + bc_case_count.putInt(caseCount); + putLabel(bc_label, code, i.getPC(), isw.getDefaultLabel()); + for (int j = 0; j < caseCount; j++) { + putLabel(bc_label, code, i.getPC(), isw.getCaseLabel(j)); + } + // Transmit case values in their own band. + if (bc == Constants._tableswitch) { + bc_case_value.putInt(isw.getCaseValue(0)); + } else { + for (int j = 0; j < caseCount; j++) { + bc_case_value.putInt(isw.getCaseValue(j)); + } + } + // Done with the switch. + continue; + } + + int branch = i.getBranchLabel(); + if (branch >= 0) { + bc_codes.putByte(bc); + putLabel(bc_label, code, i.getPC(), branch); + continue; + } + ConstantPool.Entry ref = i.getCPRef(curCPMap); + if (ref != null) { + if (bc == Constants._new) newClass = ref; + if (bc == Constants._ldc) ldcHist[ref.tag]++; + CPRefBand bc_which; + int vbc = bc; + switch (i.getCPTag()) { + case Constants.CONSTANT_LoadableValue: + switch (ref.tag) { + case Constants.CONSTANT_Integer: + bc_which = bc_intref; + switch (bc) { + case Constants._ldc: vbc = Constants._ildc; break; + case Constants._ldc_w: vbc = Constants._ildc_w; break; + default: assert(false); + } + break; + case Constants.CONSTANT_Float: + bc_which = bc_floatref; + switch (bc) { + case Constants._ldc: vbc = Constants._fldc; break; + case Constants._ldc_w: vbc = Constants._fldc_w; break; + default: assert(false); + } + break; + case Constants.CONSTANT_Long: + bc_which = bc_longref; + assert(bc == Constants._ldc2_w); + vbc = Constants._lldc2_w; + break; + case Constants.CONSTANT_Double: + bc_which = bc_doubleref; + assert(bc == Constants._ldc2_w); + vbc = Constants._dldc2_w; + break; + case Constants.CONSTANT_String: + bc_which = bc_stringref; + switch (bc) { + case Constants._ldc: vbc = Constants._sldc; break; + case Constants._ldc_w: vbc = Constants._sldc_w; break; + default: assert(false); + } + break; + case Constants.CONSTANT_Class: + bc_which = bc_classref; + switch (bc) { + case Constants._ldc: vbc = Constants._cldc; break; + case Constants._ldc_w: vbc = Constants._cldc_w; break; + default: assert(false); + } + break; + default: + // CONSTANT_MethodHandle, etc. + if (getHighestClassVersion().lessThan(Constants.JAVA7_MAX_CLASS_VERSION)) { + throw new IOException("bad class file major version for Java 7 ldc"); + } + bc_which = bc_loadablevalueref; + switch (bc) { + case Constants._ldc: vbc = Constants._qldc; break; + case Constants._ldc_w: vbc = Constants._qldc_w; break; + default: assert(false); + } + } + break; + case Constants.CONSTANT_Class: + // Use a special shorthand for the current class: + if (ref == curClass.thisClass) ref = null; + bc_which = bc_classref; break; + case Constants.CONSTANT_Fieldref: + bc_which = bc_fieldref; break; + case Constants.CONSTANT_Methodref: + if (ref.tagEquals(Constants.CONSTANT_InterfaceMethodref)) { + if (bc == Constants._invokespecial) + vbc = Constants._invokespecial_int; + if (bc == Constants._invokestatic) + vbc = Constants._invokestatic_int; + bc_which = bc_imethodref; + } else { + bc_which = bc_methodref; + } + break; + case Constants.CONSTANT_InterfaceMethodref: + bc_which = bc_imethodref; break; + case Constants.CONSTANT_InvokeDynamic: + bc_which = bc_indyref; break; + default: + bc_which = null; + assert(false); + } + if (ref != null && bc_which.index != null && !bc_which.index.contains(ref)) { + // Crash and burn with a complaint if there are funny + // references for this bytecode instruction. + // Example: invokestatic of a CONSTANT_InterfaceMethodref. + String complaint = code.getMethod() + + " contains a bytecode " + i + + " with an unsupported constant reference; please use the pass-file option on this class."; + Utils.log.warning(complaint); + throw new IOException(complaint); + } + bc_codes.putByte(vbc); + bc_which.putRef(ref); + // handle trailing junk + if (bc == Constants._multianewarray) { + assert(i.getConstant() == code.getByte(i.getPC()+3)); + // Just dump the byte into the bipush pile + bc_byte.putByte(0xFF & i.getConstant()); + } else if (bc == Constants._invokeinterface) { + assert(i.getLength() == 5); + // Make sure the discarded bytes are sane: + assert(i.getConstant() == (1+((ConstantPool.MemberEntry)ref).descRef.typeRef.computeSize(true)) << 8); + } else if (bc == Constants._invokedynamic) { + if (getHighestClassVersion().lessThan(Constants.JAVA7_MAX_CLASS_VERSION)) { + throw new IOException("bad class major version for Java 7 invokedynamic"); + } + assert(i.getLength() == 5); + assert(i.getConstant() == 0); // last 2 bytes MBZ + } else { + // Make sure there is nothing else to write. + assert(i.getLength() == ((bc == Constants._ldc)?2:3)); + } + continue; + } + int slot = i.getLocalSlot(); + if (slot >= 0) { + bc_codes.putByte(bc); + bc_local.putInt(slot); + int con = i.getConstant(); + if (bc == Constants._iinc) { + if (!i.isWide()) { + bc_byte.putByte(0xFF & con); + } else { + bc_short.putInt(0xFFFF & con); + } + } else { + assert(con == 0); + } + continue; + } + // Generic instruction. Copy the body. + bc_codes.putByte(bc); + int pc = i.getPC()+1; + int npc = i.getNextPC(); + if (pc < npc) { + // Do a few remaining multi-byte instructions. + switch (bc) { + case Constants._sipush: + bc_short.putInt(0xFFFF & i.getConstant()); + break; + case Constants._bipush: + bc_byte.putByte(0xFF & i.getConstant()); + break; + case Constants._newarray: + bc_byte.putByte(0xFF & i.getConstant()); + break; + default: + assert(false); // that's it + } + } + } + bc_codes.putByte(Constants._end_marker); + bc_codes.elementCountForDebug++; + codeHist[Constants._end_marker]++; + endCode(); + } + + int[] codeHist = new int[1<<8]; + int[] ldcHist = new int[20]; + void printCodeHist() { + assert(verbose > 0); + String[] hist = new String[codeHist.length]; + int totalBytes = 0; + for (int bc = 0; bc < codeHist.length; bc++) { + totalBytes += codeHist[bc]; + } + for (int bc = 0; bc < codeHist.length; bc++) { + if (codeHist[bc] == 0) { hist[bc] = ""; continue; } + String iname = Instruction.byteName(bc); + String count = "" + codeHist[bc]; + count = " ".substring(count.length()) + count; + String pct = "" + (codeHist[bc] * 10000 / totalBytes); + while (pct.length() < 4) { + pct = "0" + pct; + } + pct = pct.substring(0, pct.length()-2) + "." + pct.substring(pct.length()-2); + hist[bc] = count + " " + pct + "% " + iname; + } + Arrays.sort(hist); + System.out.println("Bytecode histogram ["+totalBytes+"]"); + for (int i = hist.length; --i >= 0; ) { + if ("".equals(hist[i])) continue; + System.out.println(hist[i]); + } + for (int tag = 0; tag < ldcHist.length; tag++) { + int count = ldcHist[tag]; + if (count == 0) continue; + System.out.println("ldc "+ConstantPool.tagName(tag)+" "+count); + } + } +} diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/PackerImpl.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/PackerImpl.java new file mode 100644 index 000000000..617d8851a --- /dev/null +++ b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/PackerImpl.java @@ -0,0 +1,607 @@ +/* + * Copyright (c) 2003, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package net.fabricmc.shade.com.sun.java.util.jar.pack; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.SortedMap; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarInputStream; +import net.fabricmc.shade.java.util.jar.Pack200; + + +/* + * Implementation of the Pack provider. + * + * @author John Rose + * @author Kumar Srinivasan + */ + +@SuppressWarnings({"removal"}) +public class PackerImpl extends TLGlobals implements Pack200.Packer { + + /** + * Constructs a Packer object and sets the initial state of + * the packer engines. + */ + public PackerImpl() {} + + /** + * Get the set of options for the pack and unpack engines. + * @return A sorted association of option key strings to option values. + */ + public SortedMap properties() { + return props; + } + + //Driver routines + + /** + * Takes a JarFile and converts into a pack-stream. + *

+ * Closes its input but not its output. (Pack200 archives are appendable.) + * @param in a JarFile + * @param out an OutputStream + * @exception IOException if an error is encountered. + */ + public synchronized void pack(JarFile in, OutputStream out) throws IOException { + assert(Utils.currentInstance.get() == null); + try { + Utils.currentInstance.set(this); + if ("0".equals(props.getProperty(Pack200.Packer.EFFORT))) { + Utils.copyJarFile(in, out); + } else { + (new DoPack()).run(in, out); + } + } finally { + Utils.currentInstance.set(null); + in.close(); + } + } + + /** + * Takes a JarInputStream and converts into a pack-stream. + *

+ * Closes its input but not its output. (Pack200 archives are appendable.) + *

+ * The modification time and deflation hint attributes are not available, + * for the jar-manifest file and the directory containing the file. + * + * @see #MODIFICATION_TIME + * @param in a JarInputStream + * @param out an OutputStream + * @exception IOException if an error is encountered. + */ + public synchronized void pack(JarInputStream in, OutputStream out) throws IOException { + assert(Utils.currentInstance.get() == null); + try { + Utils.currentInstance.set(this); + if ("0".equals(props.getProperty(Pack200.Packer.EFFORT))) { + Utils.copyJarFile(in, out); + } else { + (new DoPack()).run(in, out); + } + } finally { + Utils.currentInstance.set(null); + in.close(); + } + } + + // All the worker bees..... + // The packer worker. + private class DoPack { + final int verbose = props.getInteger(Utils.DEBUG_VERBOSE); + + { + props.setInteger(Pack200.Packer.PROGRESS, 0); + if (verbose > 0) Utils.log.info(props.toString()); + } + + // Here's where the bits are collected before getting packed, we also + // initialize the version numbers now. + final Package pkg = new Package( + Package.Version.makeVersion(props, "min.class"), + Package.Version.makeVersion(props, "max.class"), + Package.Version.makeVersion(props, "package")); + + final String unknownAttrCommand; + { + String uaMode = props.getProperty(Pack200.Packer.UNKNOWN_ATTRIBUTE, Pack200.Packer.PASS); + if (!(Pack200.Packer.STRIP.equals(uaMode) || + Pack200.Packer.PASS.equals(uaMode) || + Pack200.Packer.ERROR.equals(uaMode))) { + throw new RuntimeException("Bad option: " + Pack200.Packer.UNKNOWN_ATTRIBUTE + " = " + uaMode); + } + unknownAttrCommand = uaMode.intern(); + } + final String classFormatCommand; + { + String fmtMode = props.getProperty(Utils.CLASS_FORMAT_ERROR, Pack200.Packer.PASS); + if (!(Pack200.Packer.PASS.equals(fmtMode) || + Pack200.Packer.ERROR.equals(fmtMode))) { + throw new RuntimeException("Bad option: " + Utils.CLASS_FORMAT_ERROR + " = " + fmtMode); + } + classFormatCommand = fmtMode.intern(); + } + + final Map attrDefs; + final Map attrCommands; + { + Map lattrDefs = new HashMap<>(); + Map lattrCommands = new HashMap<>(); + String[] keys = { + Pack200.Packer.CLASS_ATTRIBUTE_PFX, + Pack200.Packer.FIELD_ATTRIBUTE_PFX, + Pack200.Packer.METHOD_ATTRIBUTE_PFX, + Pack200.Packer.CODE_ATTRIBUTE_PFX + }; + int[] ctypes = { + Constants.ATTR_CONTEXT_CLASS, + Constants.ATTR_CONTEXT_FIELD, + Constants.ATTR_CONTEXT_METHOD, + Constants.ATTR_CONTEXT_CODE + }; + for (int i = 0; i < ctypes.length; i++) { + String pfx = keys[i]; + Map map = props.prefixMap(pfx); + for (String key : map.keySet()) { + assert(key.startsWith(pfx)); + String name = key.substring(pfx.length()); + String layout = props.getProperty(key); + Attribute.Layout lkey = Attribute.keyForLookup(ctypes[i], name); + if (Pack200.Packer.STRIP.equals(layout) || + Pack200.Packer.PASS.equals(layout) || + Pack200.Packer.ERROR.equals(layout)) { + lattrCommands.put(lkey, layout.intern()); + } else { + Attribute.define(lattrDefs, ctypes[i], name, layout); + if (verbose > 1) { + Utils.log.fine("Added layout for "+Constants.ATTR_CONTEXT_NAME[i]+" attribute "+name+" = "+layout); + } + assert(lattrDefs.containsKey(lkey)); + } + } + } + this.attrDefs = (lattrDefs.isEmpty()) ? null : lattrDefs; + this.attrCommands = (lattrCommands.isEmpty()) ? null : lattrCommands; + } + + final boolean keepFileOrder + = props.getBoolean(Pack200.Packer.KEEP_FILE_ORDER); + final boolean keepClassOrder + = props.getBoolean(Utils.PACK_KEEP_CLASS_ORDER); + + final boolean keepModtime + = Pack200.Packer.KEEP.equals(props.getProperty(Pack200.Packer.MODIFICATION_TIME)); + final boolean latestModtime + = Pack200.Packer.LATEST.equals(props.getProperty(Pack200.Packer.MODIFICATION_TIME)); + final boolean keepDeflateHint + = Pack200.Packer.KEEP.equals(props.getProperty(Pack200.Packer.DEFLATE_HINT)); + { + if (!keepModtime && !latestModtime) { + int modtime = props.getTime(Pack200.Packer.MODIFICATION_TIME); + if (modtime != Constants.NO_MODTIME) { + pkg.default_modtime = modtime; + } + } + if (!keepDeflateHint) { + boolean deflate_hint = props.getBoolean(Pack200.Packer.DEFLATE_HINT); + if (deflate_hint) { + pkg.default_options |= Constants.AO_DEFLATE_HINT; + } + } + } + + long totalOutputSize = 0; + int segmentCount = 0; + long segmentTotalSize = 0; + long segmentSize = 0; // running counter + final long segmentLimit; + { + long limit; + if (props.getProperty(Pack200.Packer.SEGMENT_LIMIT, "").equals("")) + limit = -1; + else + limit = props.getLong(Pack200.Packer.SEGMENT_LIMIT); + limit = Math.min(Integer.MAX_VALUE, limit); + limit = Math.max(-1, limit); + if (limit == -1) + limit = Long.MAX_VALUE; + segmentLimit = limit; + } + + final List passFiles; // parsed pack.pass.file options + { + // Which class files will be passed through? + passFiles = props.getProperties(Pack200.Packer.PASS_FILE_PFX); + for (ListIterator i = passFiles.listIterator(); i.hasNext(); ) { + String file = i.next(); + if (file == null) { i.remove(); continue; } + file = Utils.getJarEntryName(file); // normalize '\\' to '/' + if (file.endsWith("/")) + file = file.substring(0, file.length()-1); + i.set(file); + } + if (verbose > 0) Utils.log.info("passFiles = " + passFiles); + } + + { + // Hook for testing: Forces use of special archive modes. + int opt = props.getInteger(Utils.COM_PREFIX+"archive.options"); + if (opt != 0) + pkg.default_options |= opt; + } + + // (Done collecting options from props.) + + // Get a new package, based on the old one. + private void makeNextPackage() { + pkg.reset(); + } + + final class InFile { + final String name; + final JarFile jf; + final JarEntry je; + final File f; + int modtime = Constants.NO_MODTIME; + int options; + InFile(String name) { + this.name = Utils.getJarEntryName(name); + this.f = new File(name); + this.jf = null; + this.je = null; + int timeSecs = getModtime(f.lastModified()); + if (keepModtime && timeSecs != Constants.NO_MODTIME) { + this.modtime = timeSecs; + } else if (latestModtime && timeSecs > pkg.default_modtime) { + pkg.default_modtime = timeSecs; + } + } + InFile(JarFile jf, JarEntry je) { + this.name = Utils.getJarEntryName(je.getName()); + this.f = null; + this.jf = jf; + this.je = je; + int timeSecs = (int) je.getTime(); + if (keepModtime && timeSecs != Constants.NO_MODTIME) { + this.modtime = timeSecs; + } else if (latestModtime && timeSecs > pkg.default_modtime) { + pkg.default_modtime = timeSecs; + } + if (keepDeflateHint && je.getMethod() == JarEntry.DEFLATED) { + options |= Constants.FO_DEFLATE_HINT; + } + } + InFile(JarEntry je) { + this(null, je); + } + boolean isClassFile() { + if (!name.endsWith(".class") || name.endsWith("module-info.class")) { + return false; + } + for (String prefix = name;;) { + if (passFiles.contains(prefix)) { + return false; + } + int chop = prefix.lastIndexOf('/'); + if (chop < 0) { + break; + } + prefix = prefix.substring(0, chop); + } + return true; + } + boolean isMetaInfFile() { + return name.startsWith("/" + Utils.METAINF) + || name.startsWith(Utils.METAINF); + } + boolean mustProcess() { + return !isMetaInfFile() && isClassFile(); + } + long getInputLength() { + long len = (je != null)? je.getSize(): f.length(); + assert(len >= 0) : this+".len="+len; + // Bump size by pathname length and modtime/def-hint bytes. + return Math.max(0, len) + name.length() + 5; + } + int getModtime(long timeMillis) { + // Convert milliseconds to seconds. + long seconds = (timeMillis+500) / 1000; + if ((int)seconds == seconds) { + return (int)seconds; + } else { + Utils.log.warning("overflow in modtime for "+f); + return Constants.NO_MODTIME; + } + } + void copyTo(Package.File file) { + if (modtime != Constants.NO_MODTIME) + file.modtime = modtime; + file.options |= options; + } + InputStream getInputStream() throws IOException { + if (jf != null) + return jf.getInputStream(je); + else + return new FileInputStream(f); + } + + public String toString() { + return name; + } + } + + private int nread = 0; // used only if (verbose > 0) + private void noteRead(InFile f) { + nread++; + if (verbose > 2) + Utils.log.fine("...read "+f.name); + if (verbose > 0 && (nread % 1000) == 0) + Utils.log.info("Have read "+nread+" files..."); + } + + void run(JarInputStream in, OutputStream out) throws IOException { + // First thing we do is get the manifest, as JIS does + // not provide the Manifest as an entry. + if (in.getManifest() != null) { + ByteArrayOutputStream tmp = new ByteArrayOutputStream(); + in.getManifest().write(tmp); + InputStream tmpIn = new ByteArrayInputStream(tmp.toByteArray()); + pkg.addFile(readFile(JarFile.MANIFEST_NAME, tmpIn)); + } + for (JarEntry je; (je = in.getNextJarEntry()) != null; ) { + InFile inFile = new InFile(je); + + String name = inFile.name; + Package.File bits = readFile(name, in); + Package.File file = null; + // (5078608) : discount the resource files in META-INF + // from segment computation. + long inflen = (inFile.isMetaInfFile()) + ? 0L + : inFile.getInputLength(); + + if ((segmentSize += inflen) > segmentLimit) { + segmentSize -= inflen; + int nextCount = -1; // don't know; it's a stream + flushPartial(out, nextCount); + } + if (verbose > 1) { + Utils.log.fine("Reading " + name); + } + + assert(je.isDirectory() == name.endsWith("/")); + + if (inFile.mustProcess()) { + file = readClass(name, bits.getInputStream()); + } + if (file == null) { + file = bits; + pkg.addFile(file); + } + inFile.copyTo(file); + noteRead(inFile); + } + flushAll(out); + } + + void run(JarFile in, OutputStream out) throws IOException { + List inFiles = scanJar(in); + + if (verbose > 0) + Utils.log.info("Reading " + inFiles.size() + " files..."); + + int numDone = 0; + for (InFile inFile : inFiles) { + String name = inFile.name; + // (5078608) : discount the resource files completely from segmenting + long inflen = (inFile.isMetaInfFile()) + ? 0L + : inFile.getInputLength() ; + if ((segmentSize += inflen) > segmentLimit) { + segmentSize -= inflen; + // Estimate number of remaining segments: + float filesDone = numDone+1; + float segsDone = segmentCount+1; + float filesToDo = inFiles.size() - filesDone; + float segsToDo = filesToDo * (segsDone/filesDone); + if (verbose > 1) + Utils.log.fine("Estimated segments to do: "+segsToDo); + flushPartial(out, (int) Math.ceil(segsToDo)); + } + InputStream strm = inFile.getInputStream(); + if (verbose > 1) + Utils.log.fine("Reading " + name); + Package.File file = null; + if (inFile.mustProcess()) { + file = readClass(name, strm); + if (file == null) { + strm.close(); + strm = inFile.getInputStream(); + } + } + if (file == null) { + file = readFile(name, strm); + pkg.addFile(file); + } + inFile.copyTo(file); + strm.close(); // tidy up + noteRead(inFile); + numDone += 1; + } + flushAll(out); + } + + Package.File readClass(String fname, InputStream in) throws IOException { + Package.Class cls = pkg.new Class(fname); + in = new BufferedInputStream(in); + ClassReader reader = new ClassReader(cls, in); + reader.setAttrDefs(attrDefs); + reader.setAttrCommands(attrCommands); + reader.unknownAttrCommand = unknownAttrCommand; + try { + reader.read(); + } catch (IOException ioe) { + String message = "Passing class file uncompressed due to"; + if (ioe instanceof Attribute.FormatException) { + Attribute.FormatException ee = (Attribute.FormatException) ioe; + // He passed up the category to us in layout. + if (ee.layout.equals(Pack200.Packer.PASS)) { + Utils.log.info(ee.toString()); + Utils.log.warning(message + " unrecognized attribute: " + + fname); + return null; + } + } else if (ioe instanceof ClassReader.ClassFormatException) { + ClassReader.ClassFormatException ce = (ClassReader.ClassFormatException) ioe; + if (classFormatCommand.equals(Pack200.Packer.PASS)) { + Utils.log.info(ce.toString()); + Utils.log.warning(message + " unknown class format: " + + fname); + return null; + } + } + // Otherwise, it must be an error. + throw ioe; + } + pkg.addClass(cls); + return cls.file; + } + + // Read raw data. + Package.File readFile(String fname, InputStream in) throws IOException { + + Package.File file = pkg.new File(fname); + file.readFrom(in); + if (file.isDirectory() && file.getFileLength() != 0) + throw new IllegalArgumentException("Non-empty directory: "+file.getFileName()); + return file; + } + + void flushPartial(OutputStream out, int nextCount) throws IOException { + if (pkg.files.isEmpty() && pkg.classes.isEmpty()) { + return; // do not flush an empty segment + } + flushPackage(out, Math.max(1, nextCount)); + props.setInteger(Pack200.Packer.PROGRESS, 25); + // In case there will be another segment: + makeNextPackage(); + segmentCount += 1; + segmentTotalSize += segmentSize; + segmentSize = 0; + } + + void flushAll(OutputStream out) throws IOException { + props.setInteger(Pack200.Packer.PROGRESS, 50); + flushPackage(out, 0); + out.flush(); + props.setInteger(Pack200.Packer.PROGRESS, 100); + segmentCount += 1; + segmentTotalSize += segmentSize; + segmentSize = 0; + if (verbose > 0 && segmentCount > 1) { + Utils.log.info("Transmitted " + +segmentTotalSize+" input bytes in " + +segmentCount+" segments totaling " + +totalOutputSize+" bytes"); + } + } + + + /** Write all information in the current package segment + * to the output stream. + */ + void flushPackage(OutputStream out, int nextCount) throws IOException { + int nfiles = pkg.files.size(); + if (!keepFileOrder) { + // Keeping the order of classes costs about 1% + // Keeping the order of all files costs something more. + if (verbose > 1) Utils.log.fine("Reordering files."); + boolean stripDirectories = true; + pkg.reorderFiles(keepClassOrder, stripDirectories); + } else { + // Package builder must have created a stub for each class. + assert(pkg.files.containsAll(pkg.getClassStubs())); + // Order of stubs in file list must agree with classes. + List res = pkg.files; + assert((res = new ArrayList<>(pkg.files)) + .retainAll(pkg.getClassStubs()) || true); + assert(res.equals(pkg.getClassStubs())); + } + pkg.trimStubs(); + + // Do some stripping, maybe. + if (props.getBoolean(Utils.COM_PREFIX+"strip.debug")) pkg.stripAttributeKind("Debug"); + if (props.getBoolean(Utils.COM_PREFIX+"strip.compile")) pkg.stripAttributeKind("Compile"); + if (props.getBoolean(Utils.COM_PREFIX+"strip.constants")) pkg.stripAttributeKind("Constant"); + if (props.getBoolean(Utils.COM_PREFIX+"strip.exceptions")) pkg.stripAttributeKind("Exceptions"); + if (props.getBoolean(Utils.COM_PREFIX+"strip.innerclasses")) pkg.stripAttributeKind("InnerClasses"); + + PackageWriter pw = new PackageWriter(pkg, out); + pw.archiveNextCount = nextCount; + pw.write(); + out.flush(); + if (verbose > 0) { + long outSize = pw.archiveSize0+pw.archiveSize1; + totalOutputSize += outSize; + long inSize = segmentSize; + Utils.log.info("Transmitted " + +nfiles+" files of " + +inSize+" input bytes in a segment of " + +outSize+" bytes"); + } + } + + List scanJar(JarFile jf) throws IOException { + // Collect jar entries, preserving order. + List inFiles = new ArrayList<>(); + try { + for (JarEntry je : Collections.list(jf.entries())) { + InFile inFile = new InFile(jf, je); + assert(je.isDirectory() == inFile.name.endsWith("/")); + inFiles.add(inFile); + } + } catch (IllegalStateException ise) { + throw new IOException(ise.getLocalizedMessage(), ise); + } + return inFiles; + } + } +} diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/PopulationCoding.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/PopulationCoding.java new file mode 100644 index 000000000..e731aeb8e --- /dev/null +++ b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/PopulationCoding.java @@ -0,0 +1,499 @@ +/* + * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package net.fabricmc.shade.com.sun.java.util.jar.pack; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +/** + * Population-based coding. + * See the section "Encodings of Uncorrelated Values" in the Pack200 spec. + * @author John Rose + */ +// This tactic alone reduces the final zipped rt.jar by about a percent. +class PopulationCoding implements CodingMethod { + Histogram vHist; // histogram of all values + int[] fValues; // list of favored values + int fVlen; // inclusive max index + long[] symtab; // int map of favored value -> token [1..#fValues] + + CodingMethod favoredCoding; + CodingMethod tokenCoding; + CodingMethod unfavoredCoding; + + int L = -1; //preferred L value for tokenCoding + + public void setFavoredValues(int[] fValues, int fVlen) { + // Note: {f} is allFavoredValues[1..fvlen], not [0..fvlen-1]. + // This is because zero is an exceptional favored value index. + assert(fValues[0] == 0); // must be empty + assert(this.fValues == null); // do not do this twice + this.fValues = fValues; + this.fVlen = fVlen; + if (L >= 0) { + setL(L); // reassert + } + } + public void setFavoredValues(int[] fValues) { + int lfVlen = fValues.length-1; + setFavoredValues(fValues, lfVlen); + } + public void setHistogram(Histogram vHist) { + this.vHist = vHist; + } + public void setL(int L) { + this.L = L; + if (L >= 0 && fValues != null && tokenCoding == null) { + tokenCoding = fitTokenCoding(fVlen, L); + assert(tokenCoding != null); + } + } + + public static Coding fitTokenCoding(int fVlen, int L) { + // Find the smallest B s.t. (B,H,0) covers fVlen. + if (fVlen < 256) + // H/L do not matter when B==1 + return BandStructure.BYTE1; + Coding longest = BandStructure.UNSIGNED5.setL(L); + if (!longest.canRepresentUnsigned(fVlen)) + return null; // failure; L is too sharp and fVlen too large + Coding tc = longest; + for (Coding shorter = longest; ; ) { + shorter = shorter.setB(shorter.B()-1); + if (shorter.umax() < fVlen) + break; + tc = shorter; // shorten it by reducing B + } + return tc; + } + + public void setFavoredCoding(CodingMethod favoredCoding) { + this.favoredCoding = favoredCoding; + } + public void setTokenCoding(CodingMethod tokenCoding) { + this.tokenCoding = tokenCoding; + this.L = -1; + if (tokenCoding instanceof Coding && fValues != null) { + Coding tc = (Coding) tokenCoding; + if (tc == fitTokenCoding(fVlen, tc.L())) + this.L = tc.L(); + // Otherwise, it's a non-default coding. + } + } + public void setUnfavoredCoding(CodingMethod unfavoredCoding) { + this.unfavoredCoding = unfavoredCoding; + } + + public int favoredValueMaxLength() { + if (L == 0) + return Integer.MAX_VALUE; + else + return BandStructure.UNSIGNED5.setL(L).umax(); + } + + public void resortFavoredValues() { + Coding tc = (Coding) tokenCoding; + // Make a local copy before reordering. + fValues = BandStructure.realloc(fValues, 1+fVlen); + // Resort favoredValues within each byte-size cadre. + int fillp = 1; // skip initial zero + for (int n = 1; n <= tc.B(); n++) { + int nmax = tc.byteMax(n); + if (nmax > fVlen) + nmax = fVlen; + if (nmax < tc.byteMin(n)) + break; + int low = fillp; + int high = nmax+1; + if (high == low) continue; + assert(high > low) + : high+"!>"+low; + assert(tc.getLength(low) == n) + : n+" != len("+(low)+") == "+ + tc.getLength(low); + assert(tc.getLength(high-1) == n) + : n+" != len("+(high-1)+") == "+ + tc.getLength(high-1); + int midTarget = low + (high-low)/2; + int mid = low; + // Divide the values into cadres, and sort within each. + int prevCount = -1; + int prevLimit = low; + for (int i = low; i < high; i++) { + int val = fValues[i]; + int count = vHist.getFrequency(val); + if (prevCount != count) { + if (n == 1) { + // For the single-byte encoding, keep strict order + // among frequency groups. + Arrays.sort(fValues, prevLimit, i); + } else if (Math.abs(mid - midTarget) > + Math.abs(i - midTarget)) { + // Find a single inflection point + // close to the middle of the byte-size cadre. + mid = i; + } + prevCount = count; + prevLimit = i; + } + } + if (n == 1) { + Arrays.sort(fValues, prevLimit, high); + } else { + // Sort up to the midpoint, if any. + Arrays.sort(fValues, low, mid); + Arrays.sort(fValues, mid, high); + } + assert(tc.getLength(low) == tc.getLength(mid)); + assert(tc.getLength(low) == tc.getLength(high-1)); + fillp = nmax+1; + } + assert(fillp == fValues.length); + + // Reset symtab. + symtab = null; + } + + public int getToken(int value) { + if (symtab == null) + symtab = makeSymtab(); + int pos = Arrays.binarySearch(symtab, (long)value << 32); + if (pos < 0) pos = -pos-1; + if (pos < symtab.length && value == (int)(symtab[pos] >>> 32)) + return (int)symtab[pos]; + else + return 0; + } + + public int[][] encodeValues(int[] values, int start, int end) { + // Compute token sequence. + int[] tokens = new int[end-start]; + int nuv = 0; + for (int i = 0; i < tokens.length; i++) { + int val = values[start+i]; + int tok = getToken(val); + if (tok != 0) + tokens[i] = tok; + else + nuv += 1; + } + // Compute unfavored value sequence. + int[] unfavoredValues = new int[nuv]; + nuv = 0; // reset + for (int i = 0; i < tokens.length; i++) { + if (tokens[i] != 0) continue; // already covered + int val = values[start+i]; + unfavoredValues[nuv++] = val; + } + assert(nuv == unfavoredValues.length); + return new int[][]{ tokens, unfavoredValues }; + } + + private long[] makeSymtab() { + long[] lsymtab = new long[fVlen]; + for (int token = 1; token <= fVlen; token++) { + lsymtab[token-1] = ((long)fValues[token] << 32) | token; + } + // Index by value: + Arrays.sort(lsymtab); + return lsymtab; + } + + private Coding getTailCoding(CodingMethod c) { + while (c instanceof AdaptiveCoding) + c = ((AdaptiveCoding)c).tailCoding; + return (Coding) c; + } + + // CodingMethod methods. + public void writeArrayTo(OutputStream out, int[] a, int start, int end) throws IOException { + int[][] vals = encodeValues(a, start, end); + writeSequencesTo(out, vals[0], vals[1]); + } + void writeSequencesTo(OutputStream out, int[] tokens, int[] uValues) throws IOException { + favoredCoding.writeArrayTo(out, fValues, 1, 1+fVlen); + getTailCoding(favoredCoding).writeTo(out, computeSentinelValue()); + tokenCoding.writeArrayTo(out, tokens, 0, tokens.length); + if (uValues.length > 0) + unfavoredCoding.writeArrayTo(out, uValues, 0, uValues.length); + } + + int computeSentinelValue() { + Coding fc = getTailCoding(favoredCoding); + if (fc.isDelta()) { + // repeat the last favored value, using delta=0 + return 0; + } else { + // else repeat the shorter of the min or last value + int min = fValues[1]; + int last = min; + // (remember that fVlen is an inclusive limit in fValues) + for (int i = 2; i <= fVlen; i++) { + last = fValues[i]; + min = moreCentral(min, last); + } + int endVal; + if (fc.getLength(min) <= fc.getLength(last)) + return min; + else + return last; + } + } + + public void readArrayFrom(InputStream in, int[] a, int start, int end) throws IOException { + // Parameters are fCode, L, uCode. + setFavoredValues(readFavoredValuesFrom(in, end-start)); + // Read the tokens. Read them into the final array, for the moment. + tokenCoding.readArrayFrom(in, a, start, end); + // Decode the favored tokens. + int headp = 0, tailp = -1; + int uVlen = 0; + for (int i = start; i < end; i++) { + int tok = a[i]; + if (tok == 0) { + // Make a linked list, and decode in a second pass. + if (tailp < 0) { + headp = i; + } else { + a[tailp] = i; + } + tailp = i; + uVlen += 1; + } else { + a[i] = fValues[tok]; + } + } + // Walk the linked list of "zero" locations, decoding unfavored vals. + int[] uValues = new int[uVlen]; + if (uVlen > 0) + unfavoredCoding.readArrayFrom(in, uValues, 0, uVlen); + for (int i = 0; i < uVlen; i++) { + int nextp = a[headp]; + a[headp] = uValues[i]; + headp = nextp; + } + } + + int[] readFavoredValuesFrom(InputStream in, int maxForDebug) throws IOException { + int[] lfValues = new int[1000]; // realloc as needed + // The set uniqueValuesForDebug records all favored values. + // As each new value is added, we assert that the value + // was not already in the set. + Set uniqueValuesForDebug = null; + assert((uniqueValuesForDebug = new HashSet<>()) != null); + int fillp = 1; + maxForDebug += fillp; + int min = Integer.MIN_VALUE; // farthest from the center + //int min2 = Integer.MIN_VALUE; // emulate buggy 150.7 spec. + int last = 0; + CodingMethod fcm = favoredCoding; + while (fcm instanceof AdaptiveCoding) { + AdaptiveCoding ac = (AdaptiveCoding) fcm; + int len = ac.headLength; + while (fillp + len > lfValues.length) { + lfValues = BandStructure.realloc(lfValues); + } + int newFillp = fillp + len; + ac.headCoding.readArrayFrom(in, lfValues, fillp, newFillp); + while (fillp < newFillp) { + int val = lfValues[fillp++]; + assert(uniqueValuesForDebug.add(val)); + assert(fillp <= maxForDebug); + last = val; + min = moreCentral(min, val); + //min2 = moreCentral2(min2, val, min); + } + fcm = ac.tailCoding; + } + Coding fc = (Coding) fcm; + if (fc.isDelta()) { + for (long state = 0;;) { + // Read a new value: + state += fc.readFrom(in); + int val; + if (fc.isSubrange()) + val = fc.reduceToUnsignedRange(state); + else + val = (int)state; + state = val; + if (fillp > 1 && (val == last || val == min)) //|| val == min2 + break; + if (fillp == lfValues.length) + lfValues = BandStructure.realloc(lfValues); + lfValues[fillp++] = val; + assert(uniqueValuesForDebug.add(val)); + assert(fillp <= maxForDebug); + last = val; + min = moreCentral(min, val); + //min2 = moreCentral(min2, val); + } + } else { + for (;;) { + int val = fc.readFrom(in); + if (fillp > 1 && (val == last || val == min)) //|| val == min2 + break; + if (fillp == lfValues.length) + lfValues = BandStructure.realloc(lfValues); + lfValues[fillp++] = val; + assert(uniqueValuesForDebug.add(val)); + assert(fillp <= maxForDebug); + last = val; + min = moreCentral(min, val); + //min2 = moreCentral2(min2, val, min); + } + } + return BandStructure.realloc(lfValues, fillp); + } + + private static int moreCentral(int x, int y) { + int kx = (x >> 31) ^ (x << 1); + int ky = (y >> 31) ^ (y << 1); + // bias kx/ky to get an unsigned comparison: + kx -= Integer.MIN_VALUE; + ky -= Integer.MIN_VALUE; + int xy = (kx < ky? x: y); + // assert that this ALU-ish version is the same: + assert(xy == moreCentralSlow(x, y)); + return xy; + } +// private static int moreCentral2(int x, int y, int min) { +// // Strict implementation of buggy 150.7 specification. +// // The bug is that the spec. says absolute-value ties are broken +// // in favor of positive numbers, but the suggested implementation +// // (also mentioned in the spec.) breaks ties in favor of negatives. +// if (x + y == 0) return (x > y? x : y); +// return min; +// } + private static int moreCentralSlow(int x, int y) { + int ax = x; + if (ax < 0) ax = -ax; + if (ax < 0) return y; //x is MIN_VALUE + int ay = y; + if (ay < 0) ay = -ay; + if (ay < 0) return x; //y is MIN_VALUE + if (ax < ay) return x; + if (ax > ay) return y; + // At this point the absolute values agree, and the negative wins. + return x < y ? x : y; + } + + static final int[] LValuesCoded + = { -1, 4, 8, 16, 32, 64, 128, 192, 224, 240, 248, 252 }; + + public byte[] getMetaCoding(Coding dflt) { + int K = fVlen; + int LCoded = 0; + if (tokenCoding instanceof Coding) { + Coding tc = (Coding) tokenCoding; + if (tc.B() == 1) { + LCoded = 1; + } else if (L >= 0) { + assert(L == tc.L()); + for (int i = 1; i < LValuesCoded.length; i++) { + if (LValuesCoded[i] == L) { LCoded = i; break; } + } + } + } + CodingMethod tokenDflt = null; + if (LCoded != 0 && tokenCoding == fitTokenCoding(fVlen, L)) { + // A simple L value is enough to recover the tokenCoding. + tokenDflt = tokenCoding; + } + int FDef = (favoredCoding == dflt)?1:0; + int UDef = (unfavoredCoding == dflt || unfavoredCoding == null)?1:0; + int TDef = (tokenCoding == tokenDflt)?1:0; + int TDefL = (TDef == 1) ? LCoded : 0; + assert(TDef == ((TDefL>0)?1:0)); + ByteArrayOutputStream bytes = new ByteArrayOutputStream(10); + bytes.write(Constants._meta_pop + FDef + 2*UDef + 4*TDefL); + try { + if (FDef == 0) bytes.write(favoredCoding.getMetaCoding(dflt)); + if (TDef == 0) bytes.write(tokenCoding.getMetaCoding(dflt)); + if (UDef == 0) bytes.write(unfavoredCoding.getMetaCoding(dflt)); + } catch (IOException ee) { + throw new RuntimeException(ee); + } + return bytes.toByteArray(); + } + public static int parseMetaCoding(byte[] bytes, int pos, Coding dflt, CodingMethod res[]) { + int op = bytes[pos++] & 0xFF; + if (op < Constants._meta_pop || op >= Constants._meta_limit) return pos-1; // backup + op -= Constants._meta_pop; + int FDef = op % 2; + int UDef = (op / 2) % 2; + int TDefL = (op / 4); + int TDef = (TDefL > 0)?1:0; + int L = LValuesCoded[TDefL]; + CodingMethod[] FCode = {dflt}, TCode = {null}, UCode = {dflt}; + if (FDef == 0) + pos = BandStructure.parseMetaCoding(bytes, pos, dflt, FCode); + if (TDef == 0) + pos = BandStructure.parseMetaCoding(bytes, pos, dflt, TCode); + if (UDef == 0) + pos = BandStructure.parseMetaCoding(bytes, pos, dflt, UCode); + PopulationCoding pop = new PopulationCoding(); + pop.L = L; // might be -1 + pop.favoredCoding = FCode[0]; + pop.tokenCoding = TCode[0]; // might be null! + pop.unfavoredCoding = UCode[0]; + res[0] = pop; + return pos; + } + + private String keyString(CodingMethod m) { + if (m instanceof Coding) + return ((Coding)m).keyString(); + if (m == null) + return "none"; + return m.toString(); + } + public String toString() { + PropMap p200 = Utils.currentPropMap(); + boolean verbose + = (p200 != null && + p200.getBoolean(Utils.COM_PREFIX+"verbose.pop")); + StringBuilder res = new StringBuilder(100); + res.append("pop(").append("fVlen=").append(fVlen); + if (verbose && fValues != null) { + res.append(" fV=["); + for (int i = 1; i <= fVlen; i++) { + res.append(i==1?"":",").append(fValues[i]); + } + res.append(";").append(computeSentinelValue()); + res.append("]"); + } + res.append(" fc=").append(keyString(favoredCoding)); + res.append(" tc=").append(keyString(tokenCoding)); + res.append(" uc=").append(keyString(unfavoredCoding)); + res.append(")"); + return res.toString(); + } +} diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/PropMap.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/PropMap.java new file mode 100644 index 000000000..9a7e6b071 --- /dev/null +++ b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/PropMap.java @@ -0,0 +1,315 @@ +/* + * Copyright (c) 2003, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package net.fabricmc.shade.com.sun.java.util.jar.pack; + +import net.fabricmc.shade.java.util.jar.Pack200; + +import java.io.PrintStream; +import java.io.PrintWriter; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; + +/** + * Control block for publishing Pack200 options to the other classes. + */ + +@SuppressWarnings({"removal"}) +final class PropMap implements SortedMap { + private final TreeMap theMap = new TreeMap<>();; + + // Override: + public String put(String key, String value) { + String oldValue = theMap.put(key, value); + return oldValue; + } + + // All this other stuff is private to the current package. + // Outide clients of Pack200 do not need to use it; they can + // get by with generic SortedMap functionality. + private static Map defaultProps; + static { + Properties props = new Properties(); + + // Allow implementation selected via -Dpack.disable.native=true + String propValue = getPropertyValue(Utils.DEBUG_DISABLE_NATIVE, "false"); + props.put(Utils.DEBUG_DISABLE_NATIVE, + String.valueOf(Boolean.parseBoolean(propValue))); + + // Set the DEBUG_VERBOSE from system + int verbose = 0; + try { + verbose = Integer.decode(getPropertyValue(Utils.DEBUG_VERBOSE, "0")); + } catch (NumberFormatException e) { + } + props.put(Utils.DEBUG_VERBOSE, String.valueOf(verbose)); + + // The segment size is unlimited + props.put(Pack200.Packer.SEGMENT_LIMIT, "-1"); + + // Preserve file ordering by default. + props.put(Pack200.Packer.KEEP_FILE_ORDER, Pack200.Packer.TRUE); + + // Preserve all modification times by default. + props.put(Pack200.Packer.MODIFICATION_TIME, Pack200.Packer.KEEP); + + // Preserve deflation hints by default. + props.put(Pack200.Packer.DEFLATE_HINT, Pack200.Packer.KEEP); + + // Pass through files with unrecognized attributes by default. + props.put(Pack200.Packer.UNKNOWN_ATTRIBUTE, Pack200.Packer.PASS); + + // Pass through files with unrecognized format by default, also + // allow system property to be set + props.put(Utils.CLASS_FORMAT_ERROR, + getPropertyValue(Utils.CLASS_FORMAT_ERROR, Pack200.Packer.PASS)); + + // Default effort is 5, midway between 1 and 9. + props.put(Pack200.Packer.EFFORT, "5"); + + for (Map.Entry e : props.entrySet()) { + String key = (String) e.getKey(); + String val = (String) e.getValue(); + if (key.startsWith("attribute.")) { + e.setValue(Attribute.normalizeLayoutString(val)); + } + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + HashMap temp = new HashMap(props); // shrink to fit + defaultProps = temp; + } + + private static String getPropertyValue(String key, String defaultValue) { + PrivilegedAction pa = () -> System.getProperty(key); + String s = AccessController.doPrivileged(pa); + return s != null ? s : defaultValue; + } + + PropMap() { + theMap.putAll(defaultProps); + } + + // Return a view of this map which includes only properties + // that begin with the given prefix. This is easy because + // the map is sorted, and has a subMap accessor. + SortedMap prefixMap(String prefix) { + int len = prefix.length(); + if (len == 0) + return this; + char nextch = (char)(prefix.charAt(len-1) + 1); + String limit = prefix.substring(0, len-1)+nextch; + //System.out.println(prefix+" => "+subMap(prefix, limit)); + return subMap(prefix, limit); + } + + String getProperty(String s) { + return get(s); + } + String getProperty(String s, String defaultVal) { + String val = getProperty(s); + if (val == null) + return defaultVal; + return val; + } + String setProperty(String s, String val) { + return put(s, val); + } + + // Get sequence of props for "prefix", and "prefix.*". + List getProperties(String prefix) { + Collection values = prefixMap(prefix).values(); + List res = new ArrayList<>(values.size()); + res.addAll(values); + while (res.remove(null)); + return res; + } + + private boolean toBoolean(String val) { + return Boolean.valueOf(val).booleanValue(); + } + boolean getBoolean(String s) { + return toBoolean(getProperty(s)); + } + boolean setBoolean(String s, boolean val) { + return toBoolean(setProperty(s, String.valueOf(val))); + } + int toInteger(String val) { + return toInteger(val, 0); + } + int toInteger(String val, int def) { + if (val == null) return def; + if (Pack200.Packer.TRUE.equals(val)) return 1; + if (Pack200.Packer.FALSE.equals(val)) return 0; + return Integer.parseInt(val); + } + int getInteger(String s, int def) { + return toInteger(getProperty(s), def); + } + int getInteger(String s) { + return toInteger(getProperty(s)); + } + int setInteger(String s, int val) { + return toInteger(setProperty(s, String.valueOf(val))); + } + + long toLong(String val) { + try { + return val == null ? 0 : Long.parseLong(val); + } catch (java.lang.NumberFormatException nfe) { + throw new IllegalArgumentException("Invalid value"); + } + } + long getLong(String s) { + return toLong(getProperty(s)); + } + long setLong(String s, long val) { + return toLong(setProperty(s, String.valueOf(val))); + } + + int getTime(String s) { + String sval = getProperty(s, "0"); + if (Utils.NOW.equals(sval)) { + return (int)((System.currentTimeMillis()+500)/1000); + } + long lval = toLong(sval); + final long recentSecondCount = 1000000000; + + if (lval < recentSecondCount*10 && !"0".equals(sval)) + Utils.log.warning("Supplied modtime appears to be seconds rather than milliseconds: "+sval); + + return (int)((lval+500)/1000); + } + + void list(PrintStream out) { + PrintWriter outw = new PrintWriter(out); + list(outw); + outw.flush(); + } + void list(PrintWriter out) { + out.println("#"+Utils.PACK_ZIP_ARCHIVE_MARKER_COMMENT+"["); + Set> defaults = defaultProps.entrySet(); + for (Map.Entry e : theMap.entrySet()) { + if (defaults.contains(e)) continue; + out.println(" " + e.getKey() + " = " + e.getValue()); + } + out.println("#]"); + } + + @Override + public int size() { + return theMap.size(); + } + + @Override + public boolean isEmpty() { + return theMap.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return theMap.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return theMap.containsValue(value); + } + + @Override + public String get(Object key) { + return theMap.get(key); + } + + @Override + public String remove(Object key) { + return theMap.remove(key); + } + + @Override + public void putAll(Map m) { + theMap.putAll(m); + } + + @Override + public void clear() { + theMap.clear(); + } + + @Override + public Set keySet() { + return theMap.keySet(); + } + + @Override + public Collection values() { + return theMap.values(); + } + + @Override + public Set> entrySet() { + return theMap.entrySet(); + } + + @Override + public Comparator comparator() { + return theMap.comparator(); + } + + @Override + public SortedMap subMap(String fromKey, String toKey) { + return theMap.subMap(fromKey, toKey); + } + + @Override + public SortedMap headMap(String toKey) { + return theMap.headMap(toKey); + } + + @Override + public SortedMap tailMap(String fromKey) { + return theMap.tailMap(fromKey); + } + + @Override + public String firstKey() { + return theMap.firstKey(); + } + + @Override + public String lastKey() { + return theMap.lastKey(); + } +} diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/TLGlobals.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/TLGlobals.java new file mode 100644 index 000000000..78bef9049 --- /dev/null +++ b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/TLGlobals.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package net.fabricmc.shade.com.sun.java.util.jar.pack; + +import java.util.HashMap; +import java.util.Map; +import java.util.SortedMap; + +/* + * @author ksrini + */ + +/* + * This class provides a container to hold the global variables, for packer + * and unpacker instances. This is typically stashed away in a ThreadLocal, + * and the storage is destroyed upon completion. Therefore any local + * references to these members must be eliminated appropriately to prevent a + * memory leak. + */ +class TLGlobals { + // Global environment + final PropMap props; + + // Needed by ConstantPool.java + private final Map utf8Entries; + private final Map classEntries; + private final Map literalEntries; + private final Map signatureEntries; + private final Map descriptorEntries; + private final Map memberEntries; + private final Map methodHandleEntries; + private final Map methodTypeEntries; + private final Map invokeDynamicEntries; + private final Map bootstrapMethodEntries; + + TLGlobals() { + utf8Entries = new HashMap<>(); + classEntries = new HashMap<>(); + literalEntries = new HashMap<>(); + signatureEntries = new HashMap<>(); + descriptorEntries = new HashMap<>(); + memberEntries = new HashMap<>(); + methodHandleEntries = new HashMap<>(); + methodTypeEntries = new HashMap<>(); + invokeDynamicEntries = new HashMap<>(); + bootstrapMethodEntries = new HashMap<>(); + props = new PropMap(); + } + + SortedMap getPropMap() { + return props; + } + + Map getUtf8Entries() { + return utf8Entries; + } + + Map getClassEntries() { + return classEntries; + } + + Map getLiteralEntries() { + return literalEntries; + } + + Map getDescriptorEntries() { + return descriptorEntries; + } + + Map getSignatureEntries() { + return signatureEntries; + } + + Map getMemberEntries() { + return memberEntries; + } + + Map getMethodHandleEntries() { + return methodHandleEntries; + } + + Map getMethodTypeEntries() { + return methodTypeEntries; + } + + Map getInvokeDynamicEntries() { + return invokeDynamicEntries; + } + + Map getBootstrapMethodEntries() { + return bootstrapMethodEntries; + } +} diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/UnpackerImpl.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/UnpackerImpl.java new file mode 100644 index 000000000..3e2d18cf4 --- /dev/null +++ b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/UnpackerImpl.java @@ -0,0 +1,264 @@ +/* + * Copyright (c) 2003, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package net.fabricmc.shade.com.sun.java.util.jar.pack; + +import net.fabricmc.shade.java.util.jar.Pack200; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.HashSet; +import java.util.Set; +import java.util.SortedMap; +import java.util.jar.JarEntry; +import java.util.jar.JarInputStream; +import java.util.jar.JarOutputStream; +import java.util.zip.CRC32; +import java.util.zip.CheckedOutputStream; +import java.util.zip.ZipEntry; + +/* + * Implementation of the Pack provider. + * + * @author John Rose + * @author Kumar Srinivasan + */ + + +@SuppressWarnings({"removal"}) +public class UnpackerImpl extends TLGlobals implements Pack200.Unpacker { + + public UnpackerImpl() {} + + + + /** + * Get the set of options for the pack and unpack engines. + * @return A sorted association of option key strings to option values. + */ + public SortedMap properties() { + return props; + } + + // Back-pointer to NativeUnpacker, when active. + Object _nunp; + + + public String toString() { + return Utils.getVersionString(); + } + + //Driver routines + + // The unpack worker... + /** + * Takes a packed-stream InputStream, and writes to a JarOutputStream. Internally + * the entire buffer must be read, it may be more efficient to read the packed-stream + * to a file and pass the File object, in the alternate method described below. + *

+ * Closes its input but not its output. (The output can accumulate more elements.) + * @param in an InputStream. + * @param out a JarOutputStream. + * @exception IOException if an error is encountered. + */ + public synchronized void unpack(InputStream in, JarOutputStream out) throws IOException { + if (in == null) { + throw new NullPointerException("null input"); + } + if (out == null) { + throw new NullPointerException("null output"); + } + assert(Utils.currentInstance.get() == null); + + try { + Utils.currentInstance.set(this); + final int verbose = props.getInteger(Utils.DEBUG_VERBOSE); + BufferedInputStream in0 = new BufferedInputStream(in); + if (Utils.isJarMagic(Utils.readMagic(in0))) { + if (verbose > 0) + Utils.log.info("Copying unpacked JAR file..."); + Utils.copyJarFile(new JarInputStream(in0), out); + } else if (props.getBoolean(Utils.DEBUG_DISABLE_NATIVE)) { + (new DoUnpack()).run(in0, out); + in0.close(); + Utils.markJarFile(out); + } else { + try { + (new NativeUnpack(this)).run(in0, out); + } catch (UnsatisfiedLinkError | NoClassDefFoundError ex) { + // failover to java implementation + (new DoUnpack()).run(in0, out); + } + in0.close(); + Utils.markJarFile(out); + } + } finally { + _nunp = null; + Utils.currentInstance.set(null); + } + } + + /** + * Takes an input File containing the pack file, and generates a JarOutputStream. + *

+ * Does not close its output. (The output can accumulate more elements.) + * @param in a File. + * @param out a JarOutputStream. + * @exception IOException if an error is encountered. + */ + public synchronized void unpack(File in, JarOutputStream out) throws IOException { + if (in == null) { + throw new NullPointerException("null input"); + } + if (out == null) { + throw new NullPointerException("null output"); + } + // Use the stream-based implementation. + // %%% Reconsider if native unpacker learns to memory-map the file. + try (FileInputStream instr = new FileInputStream(in)) { + unpack(instr, out); + } + if (props.getBoolean(Utils.UNPACK_REMOVE_PACKFILE)) { + in.delete(); + } + } + + private class DoUnpack { + final int verbose = props.getInteger(Utils.DEBUG_VERBOSE); + + { + props.setInteger(Pack200.Unpacker.PROGRESS, 0); + } + + // Here's where the bits are read from disk: + final Package pkg = new Package(); + + final boolean keepModtime + = Pack200.Packer.KEEP.equals( + props.getProperty(Utils.UNPACK_MODIFICATION_TIME, Pack200.Packer.KEEP)); + final boolean keepDeflateHint + = Pack200.Packer.KEEP.equals( + props.getProperty(Pack200.Unpacker.DEFLATE_HINT, Pack200.Packer.KEEP)); + final int modtime; + final boolean deflateHint; + { + if (!keepModtime) { + modtime = props.getTime(Utils.UNPACK_MODIFICATION_TIME); + } else { + modtime = pkg.default_modtime; + } + + deflateHint = (keepDeflateHint) ? false : + props.getBoolean(Pack200.Unpacker.DEFLATE_HINT); + } + + // Checksum apparatus. + final CRC32 crc = new CRC32(); + final ByteArrayOutputStream bufOut = new ByteArrayOutputStream(); + final OutputStream crcOut = new CheckedOutputStream(bufOut, crc); + + public void run(BufferedInputStream in, JarOutputStream out) throws IOException { + if (verbose > 0) { + props.list(System.out); + } + for (int seg = 1; ; seg++) { + unpackSegment(in, out); + + // Try to get another segment. + if (!Utils.isPackMagic(Utils.readMagic(in))) break; + if (verbose > 0) + Utils.log.info("Finished segment #"+seg); + } + } + + private void unpackSegment(InputStream in, JarOutputStream out) throws IOException { + props.setProperty(Pack200.Unpacker.PROGRESS,"0"); + // Process the output directory or jar output. + new PackageReader(pkg, in).read(); + + if (props.getBoolean("unpack.strip.debug")) pkg.stripAttributeKind("Debug"); + if (props.getBoolean("unpack.strip.compile")) pkg.stripAttributeKind("Compile"); + props.setProperty(Pack200.Unpacker.PROGRESS,"50"); + pkg.ensureAllClassFiles(); + // Now write out the files. + Set classesToWrite = new HashSet<>(pkg.getClasses()); + for (Package.File file : pkg.getFiles()) { + String name = file.nameString; + JarEntry je = new JarEntry(Utils.getJarEntryName(name)); + boolean deflate; + + deflate = (keepDeflateHint) + ? (((file.options & Constants.FO_DEFLATE_HINT) != 0) || + ((pkg.default_options & Constants.AO_DEFLATE_HINT) != 0)) + : deflateHint; + + boolean needCRC = !deflate; // STORE mode requires CRC + + if (needCRC) crc.reset(); + bufOut.reset(); + if (file.isClassStub()) { + Package.Class cls = file.getStubClass(); + assert(cls != null); + new ClassWriter(cls, needCRC ? crcOut : bufOut).write(); + classesToWrite.remove(cls); // for an error check + } else { + // collect data & maybe CRC + file.writeTo(needCRC ? crcOut : bufOut); + } + je.setMethod(deflate ? JarEntry.DEFLATED : JarEntry.STORED); + if (needCRC) { + if (verbose > 0) + Utils.log.info("stored size="+bufOut.size()+" and crc="+crc.getValue()); + + je.setMethod(JarEntry.STORED); + je.setSize(bufOut.size()); + je.setCrc(crc.getValue()); + } + if (keepModtime) { + LocalDateTime ldt = LocalDateTime + .ofEpochSecond(file.modtime, 0, ZoneOffset.UTC); + je.setTimeLocal(ldt); + } else { + je.setTime((long)modtime * 1000); + } + out.putNextEntry(je); + bufOut.writeTo(out); + out.closeEntry(); + if (verbose > 0) + Utils.log.info("Writing "+Utils.zeString((ZipEntry)je)); + } + assert(classesToWrite.isEmpty()); + props.setProperty(Pack200.Unpacker.PROGRESS,"100"); + pkg.reset(); // reset for the next segment, if any + } + } +} diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Utils.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Utils.java new file mode 100644 index 000000000..c7381fa13 --- /dev/null +++ b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Utils.java @@ -0,0 +1,321 @@ +/* + * Copyright (c) 2003, 2015, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package net.fabricmc.shade.com.sun.java.util.jar.pack; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Collections; +import java.util.Date; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarInputStream; +import java.util.jar.JarOutputStream; +import java.util.logging.LogManager; +import java.util.logging.Logger; +import java.util.zip.ZipEntry; + +class Utils { + static final String COM_PREFIX = "com.sun.java.util.jar.pack."; + static final String METAINF = "META-INF"; + + /* + * Outputs various diagnostic support information. + * If >0, print summary comments (e.g., constant pool info). + * If >1, print unit comments (e.g., processing of classes). + * If >2, print many comments (e.g., processing of members). + * If >3, print tons of comments (e.g., processing of references). + * (installer only) + */ + static final String DEBUG_VERBOSE = COM_PREFIX+"verbose"; + + /* + * Disables use of native code, prefers the Java-coded implementation. + * (installer only) + */ + static final String DEBUG_DISABLE_NATIVE = COM_PREFIX+"disable.native"; + + /* + * Property indicating that the unpacker should + * ignore the transmitted PACK_MODIFICATION_TIME, + * replacing it by the given value. The value can + * be a numeric string, representing the number of + * mSecs since the epoch (UTC), or the special string + * {@link #NOW}, meaning the current time (UTC). + * The default value is the special string {@link #KEEP}, + * which asks the unpacker to preserve all transmitted + * modification time information. + * (installer only) + */ + static final String UNPACK_MODIFICATION_TIME = COM_PREFIX+"unpack.modification.time"; + + /* + * Property indicating that the unpacker strip the + * Debug Attributes, if they are present, in the pack stream. + * The default value is false. + * (installer only) + */ + static final String UNPACK_STRIP_DEBUG = COM_PREFIX+"unpack.strip.debug"; + + /* + * Remove the input file after unpacking. + * (installer only) + */ + static final String UNPACK_REMOVE_PACKFILE = COM_PREFIX+"unpack.remove.packfile"; + + /* + * A possible value for MODIFICATION_TIME + */ + static final String NOW = "now"; + // Other debug options: + // com...debug.bands=false add band IDs to pack file, to verify sync + // com...dump.bands=false dump band contents to local disk + // com...no.vary.codings=false turn off coding variation heuristics + // com...no.big.strings=false turn off "big string" feature + + /* + * If this property is set to {@link #TRUE}, the packer will preserve + * the ordering of class files of the original jar in the output archive. + * The ordering is preserved only for class-files; resource files + * may be reordered. + *

+ * If the packer is allowed to reorder class files, it can marginally + * decrease the transmitted size of the archive. + */ + static final String PACK_KEEP_CLASS_ORDER = COM_PREFIX+"keep.class.order"; + /* + * This string PACK200 is given as a zip comment on all JAR files + * produced by this utility. + */ + static final String PACK_ZIP_ARCHIVE_MARKER_COMMENT = "PACK200"; + + /* + * behaviour when we hit a class format error, but not necessarily + * an unknown attribute, by default it is allowed to PASS. + */ + static final String CLASS_FORMAT_ERROR = COM_PREFIX+"class.format.error"; + + // Keep a TLS point to the global data and environment. + // This makes it simpler to supply environmental options + // to the engine code, especially the native code. + static final ThreadLocal currentInstance = new ThreadLocal<>(); + + // convenience method to access the TL globals + static TLGlobals getTLGlobals() { + return currentInstance.get(); + } + + static PropMap currentPropMap() { + Object obj = currentInstance.get(); + if (obj instanceof PackerImpl) + return ((PackerImpl)obj).props; + if (obj instanceof UnpackerImpl) + return ((UnpackerImpl)obj).props; + return null; + } + + static final boolean nolog + = Boolean.getBoolean(COM_PREFIX+"nolog"); + + static final boolean SORT_MEMBERS_DESCR_MAJOR + = Boolean.getBoolean(COM_PREFIX+"sort.members.descr.major"); + + static final boolean SORT_HANDLES_KIND_MAJOR + = Boolean.getBoolean(COM_PREFIX+"sort.handles.kind.major"); + + static final boolean SORT_INDY_BSS_MAJOR + = Boolean.getBoolean(COM_PREFIX+"sort.indy.bss.major"); + + static final boolean SORT_BSS_BSM_MAJOR + = Boolean.getBoolean(COM_PREFIX+"sort.bss.bsm.major"); + + static class Pack200Logger { + private final String name; + private Logger log; + Pack200Logger(String name) { + this.name = name; + } + + private synchronized Logger getLogger() { + if (log == null) { + log = LogManager.getLogManager().getLogger(name); + } + return log; + } + + public void warning(String msg, Object param) { + getLogger().warning(msg + param); + } + + public void warning(String msg) { + warning(msg, null); + } + + public void info(String msg) { + int verbose = currentPropMap().getInteger(DEBUG_VERBOSE); + if (verbose > 0) { + if (nolog) { + System.out.println(msg); + } else { + getLogger().info(msg); + } + } + } + + public void fine(String msg) { + int verbose = currentPropMap().getInteger(DEBUG_VERBOSE); + if (verbose > 0) { + System.out.println(msg); + } + } + } + + static final Pack200Logger log + = new Pack200Logger("net.minecraftforge.java.util.jar.Pack200"); + + // Returns the Max Version String of this implementation + static String getVersionString() { + return "Pack200, Vendor: " + + System.getProperty("java.vendor") + + ", Version: " + Constants.MAX_PACKAGE_VERSION; + } + + static void markJarFile(JarOutputStream out) throws IOException { + out.setComment(PACK_ZIP_ARCHIVE_MARKER_COMMENT); + } + + // -0 mode helper + static void copyJarFile(JarInputStream in, JarOutputStream out) throws IOException { + if (in.getManifest() != null) { + ZipEntry me = new ZipEntry(JarFile.MANIFEST_NAME); + out.putNextEntry(me); + in.getManifest().write(out); + out.closeEntry(); + } + byte[] buffer = new byte[1 << 14]; + for (JarEntry je; (je = in.getNextJarEntry()) != null; ) { + out.putNextEntry(je); + for (int nr; 0 < (nr = in.read(buffer)); ) { + out.write(buffer, 0, nr); + } + } + in.close(); + markJarFile(out); // add PACK200 comment + } + static void copyJarFile(JarFile in, JarOutputStream out) throws IOException { + byte[] buffer = new byte[1 << 14]; + for (JarEntry je : Collections.list(in.entries())) { + out.putNextEntry(je); + InputStream ein = in.getInputStream(je); + for (int nr; 0 < (nr = ein.read(buffer)); ) { + out.write(buffer, 0, nr); + } + } + in.close(); + markJarFile(out); // add PACK200 comment + } + static void copyJarFile(JarInputStream in, OutputStream out) throws IOException { + // 4947205 : Peformance is slow when using pack-effort=0 + out = new BufferedOutputStream(out); + out = new NonCloser(out); // protect from JarOutputStream.close() + try (JarOutputStream jout = new JarOutputStream(out)) { + copyJarFile(in, jout); + } + } + static void copyJarFile(JarFile in, OutputStream out) throws IOException { + + // 4947205 : Peformance is slow when using pack-effort=0 + out = new BufferedOutputStream(out); + out = new NonCloser(out); // protect from JarOutputStream.close() + try (JarOutputStream jout = new JarOutputStream(out)) { + copyJarFile(in, jout); + } + } + // Wrapper to prevent closing of client-supplied stream. + private static + class NonCloser extends FilterOutputStream { + NonCloser(OutputStream out) { super(out); } + public void close() throws IOException { flush(); } + } + static String getJarEntryName(String name) { + if (name == null) return null; + return name.replace(File.separatorChar, '/'); + } + + static String zeString(ZipEntry ze) { + int store = (ze.getCompressedSize() > 0) ? + (int)( (1.0 - ((double)ze.getCompressedSize()/(double)ze.getSize()))*100 ) + : 0 ; + // Follow unzip -lv output + return ze.getSize() + "\t" + ze.getMethod() + + "\t" + ze.getCompressedSize() + "\t" + + store + "%\t" + + new Date(ze.getTime()) + "\t" + + Long.toHexString(ze.getCrc()) + "\t" + + ze.getName() ; + } + + + + static byte[] readMagic(BufferedInputStream in) throws IOException { + in.mark(4); + byte[] magic = new byte[4]; + for (int i = 0; i < magic.length; i++) { + // read 1 byte at a time, so we always get 4 + if (1 != in.read(magic, i, 1)) + break; + } + in.reset(); + return magic; + } + + // magic number recognizers + static boolean isJarMagic(byte[] magic) { + return (magic[0] == (byte)'P' && + magic[1] == (byte)'K' && + magic[2] >= 1 && + magic[2] < 8 && + magic[3] == magic[2] + 1); + } + static boolean isPackMagic(byte[] magic) { + return (magic[0] == (byte)0xCA && + magic[1] == (byte)0xFE && + magic[2] == (byte)0xD0 && + magic[3] == (byte)0x0D); + } + static boolean isGZIPMagic(byte[] magic) { + return (magic[0] == (byte)0x1F && + magic[1] == (byte)0x8B && + magic[2] == (byte)0x08); + // fourth byte is variable "flg" field + } + + private Utils() { } // do not instantiate +} diff --git a/src/main/java/net/fabricmc/shade/java/util/jar/Pack200.java b/src/main/java/net/fabricmc/shade/java/util/jar/Pack200.java new file mode 100644 index 000000000..6cfcb5ce8 --- /dev/null +++ b/src/main/java/net/fabricmc/shade/java/util/jar/Pack200.java @@ -0,0 +1,738 @@ +/* + * Copyright (c) 2003, 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package net.fabricmc.shade.java.util.jar; + +import java.util.SortedMap; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.File; +import java.io.IOException; +import java.util.jar.JarFile; +import java.util.jar.JarInputStream; +import java.util.jar.JarOutputStream; + +import net.fabricmc.shade.com.sun.java.util.jar.pack.PackerImpl; +import net.fabricmc.shade.com.sun.java.util.jar.pack.UnpackerImpl; + + +/** + * Transforms a JAR file to or from a packed stream in Pack200 format. + * Please refer to Network Transfer Format JSR 200 Specification + *

+ * Typically the packer engine is used by application developers + * to deploy or host JAR files on a website. + * The unpacker engine is used by deployment applications to + * transform the byte-stream back to JAR format. + *

+ * Here is an example using packer and unpacker: + *

{@code
+ *    import net.minecraftforge.java.util.jar.Pack200;
+ *    import net.minecraftforge.java.util.jar.Pack200.*;
+ *    ...
+ *    // Create the Packer object
+ *    Packer packer = Pack200.newPacker();
+ *
+ *    // Initialize the state by setting the desired properties
+ *    Map p = packer.properties();
+ *    // take more time choosing codings for better compression
+ *    p.put(Packer.EFFORT, "7");  // default is "5"
+ *    // use largest-possible archive segments (>10% better compression).
+ *    p.put(Packer.SEGMENT_LIMIT, "-1");
+ *    // reorder files for better compression.
+ *    p.put(Packer.KEEP_FILE_ORDER, Packer.FALSE);
+ *    // smear modification times to a single value.
+ *    p.put(Packer.MODIFICATION_TIME, Packer.LATEST);
+ *    // ignore all JAR deflation requests,
+ *    // transmitting a single request to use "store" mode.
+ *    p.put(Packer.DEFLATE_HINT, Packer.FALSE);
+ *    // discard debug attributes
+ *    p.put(Packer.CODE_ATTRIBUTE_PFX+"LineNumberTable", Packer.STRIP);
+ *    // throw an error if an attribute is unrecognized
+ *    p.put(Packer.UNKNOWN_ATTRIBUTE, Packer.ERROR);
+ *    // pass one class file uncompressed:
+ *    p.put(Packer.PASS_FILE_PFX+0, "mutants/Rogue.class");
+ *    try {
+ *        JarFile jarFile = new JarFile("/tmp/testref.jar");
+ *        FileOutputStream fos = new FileOutputStream("/tmp/test.pack");
+ *        // Call the packer
+ *        packer.pack(jarFile, fos);
+ *        jarFile.close();
+ *        fos.close();
+ *
+ *        File f = new File("/tmp/test.pack");
+ *        FileOutputStream fostream = new FileOutputStream("/tmp/test.jar");
+ *        JarOutputStream jostream = new JarOutputStream(fostream);
+ *        Unpacker unpacker = Pack200.newUnpacker();
+ *        // Call the unpacker
+ *        unpacker.unpack(f, jostream);
+ *        // Must explicitly close the output.
+ *        jostream.close();
+ *    } catch (IOException ioe) {
+ *        ioe.printStackTrace();
+ *    }
+ * }
+ *

+ * A Pack200 file compressed with gzip can be hosted on HTTP/1.1 web servers. + * The deployment applications can use "Accept-Encoding=pack200-gzip". This + * indicates to the server that the client application desires a version of + * the file encoded with Pack200 and further compressed with gzip. Please + * refer to the Java Deployment Guide for techniques and details. + *

+ * Unless otherwise noted, passing a {@code null} argument to a constructor or + * method in this class will cause a {@link NullPointerException} to be thrown. + * + * @author John Rose + * @author Kumar Srinivasan + * @since 1.5 + * @deprecated This class is deprecated, and is planned for removal in a future + * release. + */ +@Deprecated +public abstract class Pack200 { + private Pack200() {} //prevent instantiation + + // Static methods of the Pack200 class. + /** + * Obtain new instance of a class that implements Packer. + *

    + *
  • If the system property {@code net.minecraftforge.java.util.jar.Pack200.Packer} + * is defined, then the value is taken to be the fully-qualified name + * of a concrete implementation class, which must implement Packer. + * This class is loaded and instantiated. If this process fails + * then an unspecified error is thrown.

  • + * + *
  • If an implementation has not been specified with the system + * property, then the system-default implementation class is instantiated, + * and the result is returned.

  • + *
+ * + *

Note: The returned object is not guaranteed to operate + * correctly if multiple threads use it at the same time. + * A multi-threaded application should either allocate multiple + * packer engines, or else serialize use of one engine with a lock. + * + * @return A newly allocated Packer engine. + */ + public static synchronized Packer newPacker() { + return (Packer) newInstance(PACK_PROVIDER); + } + + + /** + * Obtain new instance of a class that implements Unpacker. + *

    + *
  • If the system property {@code net.minecraftforge.java.util.jar.Pack200.Unpacker} + * is defined, then the value is taken to be the fully-qualified + * name of a concrete implementation class, which must implement Unpacker. + * The class is loaded and instantiated. If this process fails + * then an unspecified error is thrown.

  • + * + *
  • If an implementation has not been specified with the + * system property, then the system-default implementation class + * is instantiated, and the result is returned.

  • + *
+ * + *

Note: The returned object is not guaranteed to operate + * correctly if multiple threads use it at the same time. + * A multi-threaded application should either allocate multiple + * unpacker engines, or else serialize use of one engine with a lock. + * + * @return A newly allocated Unpacker engine. + */ + + public static Unpacker newUnpacker() { + return (Unpacker) newInstance(UNPACK_PROVIDER); + } + + // Interfaces + /** + * The packer engine applies various transformations to the input JAR file, + * making the pack stream highly compressible by a compressor such as + * gzip or zip. An instance of the engine can be obtained + * using {@link #newPacker}. + + * The high degree of compression is achieved + * by using a number of techniques described in the JSR 200 specification. + * Some of the techniques are sorting, re-ordering and co-location of the + * constant pool. + *

+ * The pack engine is initialized to an initial state as described + * by their properties below. + * The initial state can be manipulated by getting the + * engine properties (using {@link #properties}) and storing + * the modified properties on the map. + * The resource files will be passed through with no changes at all. + * The class files will not contain identical bytes, since the unpacker + * is free to change minor class file features such as constant pool order. + * However, the class files will be semantically identical, + * as specified in + * The Java™ Virtual Machine Specification. + *

+ * By default, the packer does not change the order of JAR elements. + * Also, the modification time and deflation hint of each + * JAR element is passed unchanged. + * (Any other ZIP-archive information, such as extra attributes + * giving Unix file permissions, are lost.) + *

+ * Note that packing and unpacking a JAR will in general alter the + * bytewise contents of classfiles in the JAR. This means that packing + * and unpacking will in general invalidate any digital signatures + * which rely on bytewise images of JAR elements. In order both to sign + * and to pack a JAR, you must first pack and unpack the JAR to + * "normalize" it, then compute signatures on the unpacked JAR elements, + * and finally repack the signed JAR. + * Both packing steps should + * use precisely the same options, and the segment limit may also + * need to be set to "-1", to prevent accidental variation of segment + * boundaries as class file sizes change slightly. + *

+ * (Here's why this works: Any reordering the packer does + * of any classfile structures is idempotent, so the second packing + * does not change the orderings produced by the first packing. + * Also, the unpacker is guaranteed by the JSR 200 specification + * to produce a specific bytewise image for any given transmission + * ordering of archive elements.) + *

+ * In order to maintain backward compatibility, the pack file's version is + * set to accommodate the class files present in the input JAR file. In + * other words, the pack file version will be the latest, if the class files + * are the latest and conversely the pack file version will be the oldest + * if the class file versions are also the oldest. For intermediate class + * file versions the corresponding pack file version will be used. + * For example: + * If the input JAR-files are solely comprised of 1.5 (or lesser) + * class files, a 1.5 compatible pack file is produced. This will also be + * the case for archives that have no class files. + * If the input JAR-files contains a 1.6 class file, then the pack file + * version will be set to 1.6. + *

+ * Note: Unless otherwise noted, passing a {@code null} argument to a + * constructor or method in this class will cause a {@link NullPointerException} + * to be thrown. + * + * @since 1.5 + * @deprecated This interface is deprecated, and is planned for removal in a + * future release. + */ + @Deprecated + public interface Packer { + /** + * This property is a numeral giving the estimated target size N + * (in bytes) of each archive segment. + * If a single input file requires more than N bytes, + * it will be given its own archive segment. + *

+ * As a special case, a value of -1 will produce a single large + * segment with all input files, while a value of 0 will + * produce one segment for each class. + * Larger archive segments result in less fragmentation and + * better compression, but processing them requires more memory. + *

+ * The size of each segment is estimated by counting the size of each + * input file to be transmitted in the segment, along with the size + * of its name and other transmitted properties. + *

+ * The default is -1, which means the packer will always create a single + * segment output file. In cases where extremely large output files are + * generated, users are strongly encouraged to use segmenting or break + * up the input file into smaller JARs. + *

+ * A 10Mb JAR packed without this limit will + * typically pack about 10% smaller, but the packer may require + * a larger Java heap (about ten times the segment limit). + */ + String SEGMENT_LIMIT = "pack.segment.limit"; + + /** + * If this property is set to {@link #TRUE}, the packer will transmit + * all elements in their original order within the source archive. + *

+ * If it is set to {@link #FALSE}, the packer may reorder elements, + * and also remove JAR directory entries, which carry no useful + * information for Java applications. + * (Typically this enables better compression.) + *

+ * The default is {@link #TRUE}, which preserves the input information, + * but may cause the transmitted archive to be larger than necessary. + */ + String KEEP_FILE_ORDER = "pack.keep.file.order"; + + + /** + * If this property is set to a single decimal digit, the packer will + * use the indicated amount of effort in compressing the archive. + * Level 1 may produce somewhat larger size and faster compression speed, + * while level 9 will take much longer but may produce better compression. + *

+ * The special value 0 instructs the packer to copy through the + * original JAR file directly, with no compression. The JSR 200 + * standard requires any unpacker to understand this special case + * as a pass-through of the entire archive. + *

+ * The default is 5, investing a modest amount of time to + * produce reasonable compression. + */ + String EFFORT = "pack.effort"; + + /** + * If this property is set to {@link #TRUE} or {@link #FALSE}, the packer + * will set the deflation hint accordingly in the output archive, and + * will not transmit the individual deflation hints of archive elements. + *

+ * If this property is set to the special string {@link #KEEP}, the packer + * will attempt to determine an independent deflation hint for each + * available element of the input archive, and transmit this hint separately. + *

+ * The default is {@link #KEEP}, which preserves the input information, + * but may cause the transmitted archive to be larger than necessary. + *

+ * It is up to the unpacker implementation + * to take action upon the hint to suitably compress the elements of + * the resulting unpacked jar. + *

+ * The deflation hint of a ZIP or JAR element indicates + * whether the element was deflated or stored directly. + */ + String DEFLATE_HINT = "pack.deflate.hint"; + + /** + * If this property is set to the special string {@link #LATEST}, + * the packer will attempt to determine the latest modification time, + * among all the available entries in the original archive or the latest + * modification time of all the available entries in each segment. + * This single value will be transmitted as part of the segment and applied + * to all the entries in each segment, {@link #SEGMENT_LIMIT}. + *

+ * This can marginally decrease the transmitted size of the + * archive, at the expense of setting all installed files to a single + * date. + *

+ * If this property is set to the special string {@link #KEEP}, + * the packer transmits a separate modification time for each input + * element. + *

+ * The default is {@link #KEEP}, which preserves the input information, + * but may cause the transmitted archive to be larger than necessary. + *

+ * It is up to the unpacker implementation to take action to suitably + * set the modification time of each element of its output file. + * @see #SEGMENT_LIMIT + */ + String MODIFICATION_TIME = "pack.modification.time"; + + /** + * Indicates that a file should be passed through bytewise, with no + * compression. Multiple files may be specified by specifying + * additional properties with distinct strings appended, to + * make a family of properties with the common prefix. + *

+ * There is no pathname transformation, except + * that the system file separator is replaced by the JAR file + * separator '/'. + *

+ * The resulting file names must match exactly as strings with their + * occurrences in the JAR file. + *

+ * If a property value is a directory name, all files under that + * directory will be passed also. + *

+ * Examples: + *

{@code
+         *     Map p = packer.properties();
+         *     p.put(PASS_FILE_PFX+0, "mutants/Rogue.class");
+         *     p.put(PASS_FILE_PFX+1, "mutants/Wolverine.class");
+         *     p.put(PASS_FILE_PFX+2, "mutants/Storm.class");
+         *     # Pass all files in an entire directory hierarchy:
+         *     p.put(PASS_FILE_PFX+3, "police/");
+         * }
+ */ + String PASS_FILE_PFX = "pack.pass.file."; + + /// Attribute control. + + /** + * Indicates the action to take when a class-file containing an unknown + * attribute is encountered. Possible values are the strings {@link #ERROR}, + * {@link #STRIP}, and {@link #PASS}. + *

+ * The string {@link #ERROR} means that the pack operation + * as a whole will fail, with an exception of type {@code IOException}. + * The string + * {@link #STRIP} means that the attribute will be dropped. + * The string + * {@link #PASS} means that the whole class-file will be passed through + * (as if it were a resource file) without compression, with a suitable warning. + * This is the default value for this property. + *

+ * Examples: + *

{@code
+         *     Map p = pack200.getProperties();
+         *     p.put(UNKNOWN_ATTRIBUTE, ERROR);
+         *     p.put(UNKNOWN_ATTRIBUTE, STRIP);
+         *     p.put(UNKNOWN_ATTRIBUTE, PASS);
+         * }
+ */ + String UNKNOWN_ATTRIBUTE = "pack.unknown.attribute"; + + /** + * When concatenated with a class attribute name, + * indicates the format of that attribute, + * using the layout language specified in the JSR 200 specification. + *

+ * For example, the effect of this option is built in: + * {@code pack.class.attribute.SourceFile=RUH}. + *

+ * The special strings {@link #ERROR}, {@link #STRIP}, and {@link #PASS} are + * also allowed, with the same meaning as {@link #UNKNOWN_ATTRIBUTE}. + * This provides a way for users to request that specific attributes be + * refused, stripped, or passed bitwise (with no class compression). + *

+ * Code like this might be used to support attributes for JCOV: + *

{@code
+         *     Map p = packer.properties();
+         *     p.put(CODE_ATTRIBUTE_PFX+"CoverageTable",       "NH[PHHII]");
+         *     p.put(CODE_ATTRIBUTE_PFX+"CharacterRangeTable", "NH[PHPOHIIH]");
+         *     p.put(CLASS_ATTRIBUTE_PFX+"SourceID",           "RUH");
+         *     p.put(CLASS_ATTRIBUTE_PFX+"CompilationID",      "RUH");
+         * }
+ *

+ * Code like this might be used to strip debugging attributes: + *

{@code
+         *     Map p = packer.properties();
+         *     p.put(CODE_ATTRIBUTE_PFX+"LineNumberTable",    STRIP);
+         *     p.put(CODE_ATTRIBUTE_PFX+"LocalVariableTable", STRIP);
+         *     p.put(CLASS_ATTRIBUTE_PFX+"SourceFile",        STRIP);
+         * }
+ */ + String CLASS_ATTRIBUTE_PFX = "pack.class.attribute."; + + /** + * When concatenated with a field attribute name, + * indicates the format of that attribute. + * For example, the effect of this option is built in: + * {@code pack.field.attribute.Deprecated=}. + * The special strings {@link #ERROR}, {@link #STRIP}, and + * {@link #PASS} are also allowed. + * @see #CLASS_ATTRIBUTE_PFX + */ + String FIELD_ATTRIBUTE_PFX = "pack.field.attribute."; + + /** + * When concatenated with a method attribute name, + * indicates the format of that attribute. + * For example, the effect of this option is built in: + * {@code pack.method.attribute.Exceptions=NH[RCH]}. + * The special strings {@link #ERROR}, {@link #STRIP}, and {@link #PASS} + * are also allowed. + * @see #CLASS_ATTRIBUTE_PFX + */ + String METHOD_ATTRIBUTE_PFX = "pack.method.attribute."; + + /** + * When concatenated with a code attribute name, + * indicates the format of that attribute. + * For example, the effect of this option is built in: + * {@code pack.code.attribute.LocalVariableTable=NH[PHOHRUHRSHH]}. + * The special strings {@link #ERROR}, {@link #STRIP}, and {@link #PASS} + * are also allowed. + * @see #CLASS_ATTRIBUTE_PFX + */ + String CODE_ATTRIBUTE_PFX = "pack.code.attribute."; + + /** + * The packer's progress as a percentage, as periodically + * updated by the packer. + * Values of 0 - 100 are normal, and -1 indicates a stall. + * Progress can be monitored by polling the value of this + * property. + *

+ * At a minimum, the packer must set progress to 0 + * at the beginning of a packing operation, and to 100 + * at the end. + */ + String PROGRESS = "pack.progress"; + + /** The string "keep", a possible value for certain properties. + * @see #DEFLATE_HINT + * @see #MODIFICATION_TIME + */ + String KEEP = "keep"; + + /** The string "pass", a possible value for certain properties. + * @see #UNKNOWN_ATTRIBUTE + * @see #CLASS_ATTRIBUTE_PFX + * @see #FIELD_ATTRIBUTE_PFX + * @see #METHOD_ATTRIBUTE_PFX + * @see #CODE_ATTRIBUTE_PFX + */ + String PASS = "pass"; + + /** The string "strip", a possible value for certain properties. + * @see #UNKNOWN_ATTRIBUTE + * @see #CLASS_ATTRIBUTE_PFX + * @see #FIELD_ATTRIBUTE_PFX + * @see #METHOD_ATTRIBUTE_PFX + * @see #CODE_ATTRIBUTE_PFX + */ + String STRIP = "strip"; + + /** The string "error", a possible value for certain properties. + * @see #UNKNOWN_ATTRIBUTE + * @see #CLASS_ATTRIBUTE_PFX + * @see #FIELD_ATTRIBUTE_PFX + * @see #METHOD_ATTRIBUTE_PFX + * @see #CODE_ATTRIBUTE_PFX + */ + String ERROR = "error"; + + /** The string "true", a possible value for certain properties. + * @see #KEEP_FILE_ORDER + * @see #DEFLATE_HINT + */ + String TRUE = "true"; + + /** The string "false", a possible value for certain properties. + * @see #KEEP_FILE_ORDER + * @see #DEFLATE_HINT + */ + String FALSE = "false"; + + /** The string "latest", a possible value for certain properties. + * @see #MODIFICATION_TIME + */ + String LATEST = "latest"; + + /** + * Get the set of this engine's properties. + * This set is a "live view", so that changing its + * contents immediately affects the Packer engine, and + * changes from the engine (such as progress indications) + * are immediately visible in the map. + * + *

The property map may contain pre-defined implementation + * specific and default properties. Users are encouraged to + * read the information and fully understand the implications, + * before modifying pre-existing properties. + *

+ * Implementation specific properties are prefixed with a + * package name associated with the implementor, beginning + * with {@code com.} or a similar prefix. + * All property names beginning with {@code pack.} and + * {@code unpack.} are reserved for use by this API. + *

+ * Unknown properties may be ignored or rejected with an + * unspecified error, and invalid entries may cause an + * unspecified error to be thrown. + * + *

+ * The returned map implements all optional {@link SortedMap} operations + * @return A sorted association of property key strings to property + * values. + */ + SortedMap properties(); + + /** + * Takes a JarFile and converts it into a Pack200 archive. + *

+ * Closes its input but not its output. (Pack200 archives are appendable.) + * @param in a JarFile + * @param out an OutputStream + * @exception IOException if an error is encountered. + */ + void pack(JarFile in, OutputStream out) throws IOException ; + + /** + * Takes a JarInputStream and converts it into a Pack200 archive. + *

+ * Closes its input but not its output. (Pack200 archives are appendable.) + *

+ * The modification time and deflation hint attributes are not available, + * for the JAR manifest file and its containing directory. + * + * @see #MODIFICATION_TIME + * @see #DEFLATE_HINT + * @param in a JarInputStream + * @param out an OutputStream + * @exception IOException if an error is encountered. + */ + void pack(JarInputStream in, OutputStream out) throws IOException ; + } + + /** + * The unpacker engine converts the packed stream to a JAR file. + * An instance of the engine can be obtained + * using {@link #newUnpacker}. + *

+ * Every JAR file produced by this engine will include the string + * "{@code PACK200}" as a zip file comment. + * This allows a deployer to detect if a JAR archive was packed and unpacked. + *

+ * Note: Unless otherwise noted, passing a {@code null} argument to a + * constructor or method in this class will cause a {@link NullPointerException} + * to be thrown. + *

+ * This version of the unpacker is compatible with all previous versions. + * @since 1.5 + * @deprecated This interface is deprecated, and is planned for removal in a + * future release. + */ + @Deprecated + public interface Unpacker { + + /** The string "keep", a possible value for certain properties. + * @see #DEFLATE_HINT + */ + String KEEP = "keep"; + + /** The string "true", a possible value for certain properties. + * @see #DEFLATE_HINT + */ + String TRUE = "true"; + + /** The string "false", a possible value for certain properties. + * @see #DEFLATE_HINT + */ + String FALSE = "false"; + + /** + * Property indicating that the unpacker should + * ignore all transmitted values for DEFLATE_HINT, + * replacing them by the given value, {@link #TRUE} or {@link #FALSE}. + * The default value is the special string {@link #KEEP}, + * which asks the unpacker to preserve all transmitted + * deflation hints. + */ + String DEFLATE_HINT = "unpack.deflate.hint"; + + + + /** + * The unpacker's progress as a percentage, as periodically + * updated by the unpacker. + * Values of 0 - 100 are normal, and -1 indicates a stall. + * Progress can be monitored by polling the value of this + * property. + *

+ * At a minimum, the unpacker must set progress to 0 + * at the beginning of an unpacking operation, and to 100 + * at the end. + */ + String PROGRESS = "unpack.progress"; + + /** + * Get the set of this engine's properties. This set is + * a "live view", so that changing its + * contents immediately affects the Unpacker engine, and + * changes from the engine (such as progress indications) + * are immediately visible in the map. + * + *

The property map may contain pre-defined implementation + * specific and default properties. Users are encouraged to + * read the information and fully understand the implications, + * before modifying pre-existing properties. + *

+ * Implementation specific properties are prefixed with a + * package name associated with the implementor, beginning + * with {@code com.} or a similar prefix. + * All property names beginning with {@code pack.} and + * {@code unpack.} are reserved for use by this API. + *

+ * Unknown properties may be ignored or rejected with an + * unspecified error, and invalid entries may cause an + * unspecified error to be thrown. + * + * @return A sorted association of option key strings to option values. + */ + SortedMap properties(); + + /** + * Read a Pack200 archive, and write the encoded JAR to + * a JarOutputStream. + * The entire contents of the input stream will be read. + * It may be more efficient to read the Pack200 archive + * to a file and pass the File object, using the alternate + * method described below. + *

+ * Closes its input but not its output. (The output can accumulate more elements.) + * @param in an InputStream. + * @param out a JarOutputStream. + * @exception IOException if an error is encountered. + */ + void unpack(InputStream in, JarOutputStream out) throws IOException; + + /** + * Read a Pack200 archive, and write the encoded JAR to + * a JarOutputStream. + *

+ * Does not close its output. (The output can accumulate more elements.) + * @param in a File. + * @param out a JarOutputStream. + * @exception IOException if an error is encountered. + */ + void unpack(File in, JarOutputStream out) throws IOException; + } + + // Private stuff.... + + private static final String PACK_PROVIDER = "net.minecraftforge.java.util.jar.Pack200.Packer"; + private static final String UNPACK_PROVIDER = "net.minecraftforge.java.util.jar.Pack200.Unpacker"; + + private static Class packerImpl; + private static Class unpackerImpl; + + private static synchronized Object newInstance(String prop) { + String implName = "(unknown)"; + try { + Class impl = (PACK_PROVIDER.equals(prop))? packerImpl: unpackerImpl; + if (impl == null) { + // The first time, we must decide which class to use. + implName = null; + if (implName != null && !implName.equals("")) + impl = Class.forName(implName); + else if (PACK_PROVIDER.equals(prop)) + impl = PackerImpl.class; + else + impl = UnpackerImpl.class; + } + // We have a class. Now instantiate it. + @SuppressWarnings("deprecation") + Object result = impl.newInstance(); + return result; + } catch (ClassNotFoundException e) { + throw new Error("Class not found: " + implName + + ":\ncheck property " + prop + + " in your properties file.", e); + } catch (InstantiationException e) { + throw new Error("Could not instantiate: " + implName + + ":\ncheck property " + prop + + " in your properties file.", e); + } catch (IllegalAccessException e) { + throw new Error("Cannot access class: " + implName + + ":\ncheck property " + prop + + " in your properties file.", e); + } + } + +} diff --git a/src/main/java/net/minecraftforge/fml/relauncher/Side.java b/src/main/java/net/minecraftforge/fml/relauncher/Side.java deleted file mode 100644 index 0acd06b8a..000000000 --- a/src/main/java/net/minecraftforge/fml/relauncher/Side.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Forge Mod Loader - * Copyright (c) 2012-2013 cpw. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser Public License v2.1 - * which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html - * - * Contributors: - * cpw - implementation - */ - -package net.minecraftforge.fml.relauncher; - -public enum Side { - - /** - * The client side. Specifically, an environment where rendering capability exists. - * Usually in the game client. - */ - CLIENT, - /** - * The server side. Specifically, an environment where NO rendering capability exists. - * Usually on the dedicated server. - */ - SERVER; - - /** - * @return If this is the server environment - */ - public boolean isServer() - { - return !isClient(); - } - - /** - * @return if this is the Client environment - */ - public boolean isClient() - { - return this == CLIENT; - } -} diff --git a/src/main/java/net/minecraftforge/fml/relauncher/SideOnly.java b/src/main/java/net/minecraftforge/fml/relauncher/SideOnly.java deleted file mode 100644 index 24e7f7262..000000000 --- a/src/main/java/net/minecraftforge/fml/relauncher/SideOnly.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Forge Mod Loader - * Copyright (c) 2012-2013 cpw. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser Public License v2.1 - * which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html - * - * Contributors: - * cpw - implementation - */ - -package net.minecraftforge.fml.relauncher; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - - -/** - * - * Stolen from FML for use with merging the jars. - * - * @author cpw - * - */ -@Retention(RetentionPolicy.RUNTIME) -@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR}) -public @interface SideOnly -{ - public Side value(); -} From 38234ff5455a3b7abb3b36ad9e2a832921cf4f4a Mon Sep 17 00:00:00 2001 From: Wagyourtail Date: Sun, 12 Dec 2021 02:57:11 -0700 Subject: [PATCH 14/28] fix missing liscenses --- .../forge/MinecraftPatchedProvider.java | 24 +++++++++++++++++++ .../fg2/MinecraftPatchedProviderFG2.java | 24 +++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java index f5d3a999b..1486e918d 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java @@ -1,3 +1,27 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2020-2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + package net.fabricmc.loom.configuration.providers.forge; import com.google.common.base.Preconditions; diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/MinecraftPatchedProviderFG2.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/MinecraftPatchedProviderFG2.java index 6bae728c9..cef38329b 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/MinecraftPatchedProviderFG2.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/MinecraftPatchedProviderFG2.java @@ -1,3 +1,27 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2020-2021 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + package net.fabricmc.loom.configuration.providers.forge.fg2; import com.google.common.base.Stopwatch; From ae9bb93d213cec8136132a3766913746c12bb737 Mon Sep 17 00:00:00 2001 From: Wagyourtail Date: Sun, 12 Dec 2021 05:05:08 -0700 Subject: [PATCH 15/28] slightly closer to working dev env --- .../providers/minecraft/MinecraftMappedProvider.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMappedProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMappedProvider.java index ec0039958..5a0bf03f6 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMappedProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMappedProvider.java @@ -26,6 +26,8 @@ import java.io.File; import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; @@ -339,6 +341,14 @@ public void visit(int version, int access, String name, String signature, String if (forge != null) { OutputRemappingHandler.remap(remapper, forge.assets, outputForge, null, forgeTag); + + //FG2 - remove binpatches for the dev environment + try (FileSystem fs = FileSystems.newFileSystem(outputForge, Map.of("create", "false"))) { + Path binpatches = fs.getPath("binpatches.pack.lzma"); + if (Files.exists(binpatches)) { + Files.delete(binpatches); + } + } } getProject().getLogger().lifecycle(":remapped minecraft (TinyRemapper, " + fromM + " -> " + toM + ") in " + stopwatch); From 8023faad6c1986bbe310488d741ab66d8a090950 Mon Sep 17 00:00:00 2001 From: Wagyourtail Date: Sun, 12 Dec 2021 08:46:02 -0700 Subject: [PATCH 16/28] fix checkstyle (except pack200 shade), closer to working dev env --- .../providers/LaunchProvider.java | 6 +- .../forge/FieldMigratedMappingsProvider.java | 9 +- .../providers/forge/ForgeProvider.java | 1 - .../providers/forge/ForgeUserdevProvider.java | 82 +- .../providers/forge/McpConfigProvider.java | 9 +- .../forge/MinecraftPatchedProvider.java | 1135 +++++++++-------- .../providers/forge/PatchProvider.java | 10 +- .../providers/forge/SrgProvider.java | 34 +- .../forge/fg2/FG2TaskApplyBinPatches.java | 364 +++--- .../fg2/MinecraftPatchedProviderFG2.java | 495 ++++--- .../fg3/MinecraftPatchedProviderFG3.java | 38 +- .../minecraft/MinecraftMappedProvider.java | 20 +- .../net/fabricmc/loom/util/srg/MCPReader.java | 21 +- .../loom/util/srg/SpecialSourceExecutor.java | 33 +- .../net/fabricmc/loom/util/srg/SrgMerger.java | 66 +- 15 files changed, 1149 insertions(+), 1174 deletions(-) diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/LaunchProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/LaunchProvider.java index 4ce6fb6a0..12975736d 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/LaunchProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/LaunchProvider.java @@ -103,7 +103,11 @@ public void provide(DependencyInfo dependency, Consumer postPopulation } addDependency(Constants.Dependencies.DEV_LAUNCH_INJECTOR + Constants.Dependencies.Versions.DEV_LAUNCH_INJECTOR, Constants.Configurations.LOOM_DEVELOPMENT_DEPENDENCIES); - addDependency(Constants.Dependencies.TERMINAL_CONSOLE_APPENDER + Constants.Dependencies.Versions.TERMINAL_CONSOLE_APPENDER, Constants.Configurations.LOOM_DEVELOPMENT_DEPENDENCIES); + + if (!getExtension().isForge() || !getExtension().getForgeProvider().isFG2()) { + addDependency(Constants.Dependencies.TERMINAL_CONSOLE_APPENDER + Constants.Dependencies.Versions.TERMINAL_CONSOLE_APPENDER, Constants.Configurations.LOOM_DEVELOPMENT_DEPENDENCIES); + } + addDependency(Constants.Dependencies.JETBRAINS_ANNOTATIONS + Constants.Dependencies.Versions.JETBRAINS_ANNOTATIONS, JavaPlugin.COMPILE_ONLY_CONFIGURATION_NAME); if (getExtension().isForge()) { diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java index 0f9d128fd..192a3d35a 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java @@ -47,9 +47,7 @@ import com.google.common.reflect.TypeToken; import com.google.gson.Gson; import dev.architectury.refmapremapper.utils.DescriptorRemapper; -import net.fabricmc.loom.configuration.providers.forge.fg3.MinecraftPatchedProviderFG3; import org.gradle.api.Project; -import org.gradle.internal.normalization.java.impl.FieldMember; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.FieldVisitor; @@ -58,6 +56,7 @@ import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.LoomGradlePlugin; import net.fabricmc.loom.api.mappings.layered.MappingsNamespace; +import net.fabricmc.loom.configuration.providers.forge.fg3.MinecraftPatchedProviderFG3; import net.fabricmc.loom.configuration.providers.mappings.MappingsProviderImpl; import net.fabricmc.loom.util.FileSystemUtil; import net.fabricmc.loom.util.ThreadingUtils; @@ -216,8 +215,8 @@ public FieldVisitor visitField(int access, String name, String descriptor, Strin completer.add(() -> { byte[] bytes = Files.readAllBytes(fsPath); new ClassReader(bytes).accept( - visitor, - ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES + visitor, + ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES ); }); } @@ -252,7 +251,7 @@ public FieldVisitor visitField(int access, String name, String descriptor, Strin String fieldIntermediary = fieldDef.getName("intermediary"); String descriptorIntermediary = fieldDef.getDesc("intermediary"); String newDescriptorRemapped = DescriptorRemapper.remapDescriptor(newDescriptor, - clazz -> srgToIntermediary.getOrDefault(clazz, clazz)); + clazz -> srgToIntermediary.getOrDefault(clazz, clazz)); migratedFields.put(new FieldMember(ownerIntermediary, fieldIntermediary), newDescriptorRemapped); getProject().getLogger().info(ownerIntermediary + "#" + fieldIntermediary + ": " + descriptorIntermediary + " -> " + newDescriptorRemapped); } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeProvider.java index f5e103204..e734723c6 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeProvider.java @@ -77,7 +77,6 @@ public String getTargetConfig() { return Constants.Configurations.FORGE; } - public boolean isFG2() { return fg2; } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java index f460c21f3..e692d99c8 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java @@ -34,14 +34,17 @@ import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.nio.file.StandardOpenOption; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; import java.util.stream.StreamSupport; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Lists; import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonObject; @@ -57,10 +60,10 @@ import org.gradle.api.file.FileSystemLocation; import org.gradle.api.provider.Provider; +import net.fabricmc.loom.api.ForgeLocalMod; import net.fabricmc.loom.configuration.DependencyProvider; import net.fabricmc.loom.configuration.ide.RunConfigSettings; import net.fabricmc.loom.configuration.launch.LaunchProviderSettings; -import net.fabricmc.loom.api.ForgeLocalMod; import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.DependencyDownloader; import net.fabricmc.loom.util.FileSystemUtil; @@ -115,7 +118,6 @@ public void provide(DependencyInfo dependency, Consumer postPopulation boolean fg2 = !json.has("mcp"); getExtension().getForgeProvider().setFg2(fg2); - if (fg2) { getProject().getLogger().info("FG2 Userdev, using default mcp_config/universal..."); @@ -147,10 +149,10 @@ public void provide(DependencyInfo dependency, Consumer postPopulation if (launchSettings != null) { launchSettings.evaluateLater(() -> { - launchSettings.arg(Arrays.stream(json.get("minecraftArguments").getAsString().split(" ")) - .map(this::processTemplates) - .collect(Collectors.toList())); + launchSettings.arg(Arrays.stream(json.get("minecraftArguments").getAsString().split(" ")).map(this::processTemplates).collect(Collectors.toList())); + // add missing args + launchSettings.arg("--accessToken", "FML"); }); } @@ -159,9 +161,7 @@ public void provide(DependencyInfo dependency, Consumer postPopulation settings.defaultMainClass("net.minecraft.launchwrapper.Launch"); }); } - } - } else { getProject().getLogger().info("FG3+ Userdev"); @@ -169,23 +169,15 @@ public void provide(DependencyInfo dependency, Consumer postPopulation addDependency(json.get("mcp").getAsString(), Constants.Configurations.SRG); addDependency(json.get("universal").getAsString(), Constants.Configurations.FORGE_UNIVERSAL); - for (JsonElement lib : json.get("libraries").getAsJsonArray()) { Dependency dep = null; if (lib.getAsString().startsWith("org.spongepowered:mixin:")) { if (PropertyUtil.getAndFinalize(getExtension().getForge().getUseCustomMixin())) { if (lib.getAsString().contains("0.8.2")) { - dep = addDependency( - "net.fabricmc:sponge-mixin:0.8.2+build.24", - Constants.Configurations.FORGE_DEPENDENCIES - ); + dep = addDependency("net.fabricmc:sponge-mixin:0.8.2+build.24", Constants.Configurations.FORGE_DEPENDENCIES); } else { - dep = addDependency( - "dev.architectury:mixin-patched" + - lib.getAsString().substring(lib.getAsString().lastIndexOf(":")) + ".+", - Constants.Configurations.FORGE_DEPENDENCIES - ); + dep = addDependency("dev.architectury:mixin-patched" + lib.getAsString().substring(lib.getAsString().lastIndexOf(":")) + ".+", Constants.Configurations.FORGE_DEPENDENCIES); } } } @@ -211,10 +203,7 @@ public void provide(DependencyInfo dependency, Consumer postPopulation if (launchSettings != null) { launchSettings.evaluateLater(() -> { if (value.has("args")) { - launchSettings.arg(StreamSupport.stream(value.getAsJsonArray("args").spliterator(), false) - .map(JsonElement::getAsString) - .map(this::processTemplates) - .collect(Collectors.toList())); + launchSettings.arg(StreamSupport.stream(value.getAsJsonArray("args").spliterator(), false).map(JsonElement::getAsString).map(this::processTemplates).collect(Collectors.toList())); } if (value.has("props")) { @@ -232,10 +221,7 @@ public void provide(DependencyInfo dependency, Consumer postPopulation settings.defaultMainClass(value.getAsJsonPrimitive("main").getAsString()); if (value.has("jvmArgs")) { - settings.vmArgs(StreamSupport.stream(value.getAsJsonArray("jvmArgs").spliterator(), false) - .map(JsonElement::getAsString) - .map(this::processTemplates) - .collect(Collectors.toList())); + settings.vmArgs(StreamSupport.stream(value.getAsJsonArray("jvmArgs").spliterator(), false).map(JsonElement::getAsString).map(this::processTemplates).collect(Collectors.toList())); } if (value.has("env")) { @@ -279,22 +265,15 @@ public String processTemplates(String string) { // TODO: Look into ways to not hardcode if (key.equals("runtime_classpath")) { - string = runtimeClasspath().stream() - .map(File::getAbsolutePath) - .collect(Collectors.joining(File.pathSeparator)); + string = runtimeClasspath().stream().map(File::getAbsolutePath).collect(Collectors.joining(File.pathSeparator)); } else if (key.equals("minecraft_classpath")) { - string = minecraftClasspath().stream() - .map(File::getAbsolutePath) - .collect(Collectors.joining(File.pathSeparator)); + string = minecraftClasspath().stream().map(File::getAbsolutePath).collect(Collectors.joining(File.pathSeparator)); } else if (key.equals("runtime_classpath_file")) { Path path = getDirectories().getProjectPersistentCache().toPath().resolve("forge_runtime_classpath.txt"); postPopulationScheduler.accept(() -> { try { - Files.writeString(path, runtimeClasspath().stream() - .map(File::getAbsolutePath) - .collect(Collectors.joining("\n")), - StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + Files.writeString(path, runtimeClasspath().stream().map(File::getAbsolutePath).collect(Collectors.joining("\n")), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); } catch (IOException e) { throw new RuntimeException(e); } @@ -306,10 +285,7 @@ public String processTemplates(String string) { postPopulationScheduler.accept(() -> { try { - Files.writeString(path, minecraftClasspath().stream() - .map(File::getAbsolutePath) - .collect(Collectors.joining("\n")), - StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + Files.writeString(path, minecraftClasspath().stream().map(File::getAbsolutePath).collect(Collectors.joining("\n")), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); } catch (IOException e) { throw new RuntimeException(e); } @@ -328,10 +304,7 @@ public String processTemplates(String string) { for (ForgeLocalMod localMod : getExtension().getForge().getLocalMods()) { String sourceSetName = localMod.getName(); - localMod.getSourceSets().flatMap(sourceSet -> Stream.concat( - Stream.of(sourceSet.getOutput().getResourcesDir()), - sourceSet.getOutput().getClassesDirs().getFiles().stream()) - ).map(File::getAbsolutePath).distinct().map(s -> sourceSetName + "%%" + s).collect(Collectors.toCollection(() -> modClasses)); + localMod.getSourceSets().flatMap(sourceSet -> Stream.concat(Stream.of(sourceSet.getOutput().getResourcesDir()), sourceSet.getOutput().getClassesDirs().getFiles().stream())).map(File::getAbsolutePath).distinct().map(s -> sourceSetName + "%%" + s).collect(Collectors.toCollection(() -> modClasses)); } string = String.join(File.pathSeparator, modClasses); @@ -341,18 +314,13 @@ public String processTemplates(String string) { JsonElement element = json.get(key); if (element.isJsonArray()) { - string = StreamSupport.stream(element.getAsJsonArray().spliterator(), false) - .map(JsonElement::getAsString) - .flatMap(str -> { - if (str.contains(":")) { - return DependencyDownloader.download(getProject(), str, false, false).getFiles().stream() - .map(File::getAbsolutePath) - .filter(dep -> !dep.contains("bootstraplauncher")); // TODO: Hack - } - - return Stream.of(str); - }) - .collect(Collectors.joining(File.pathSeparator)); + string = StreamSupport.stream(element.getAsJsonArray().spliterator(), false).map(JsonElement::getAsString).flatMap(str -> { + if (str.contains(":")) { + return DependencyDownloader.download(getProject(), str, false, false).getFiles().stream().map(File::getAbsolutePath).filter(dep -> !dep.contains("bootstraplauncher")); // TODO: Hack + } + + return Stream.of(str); + }).collect(Collectors.joining(File.pathSeparator)); } else { string = element.toString(); } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java index cbcca4525..996b9e814 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java @@ -74,15 +74,16 @@ public void provide(DependencyInfo dependency, Consumer postPopulation Path mcpZip = dependency.resolveFile().orElseThrow(() -> new RuntimeException("Could not resolve MCPConfig")).toPath(); - if (getExtension().getForgeProvider().isFG2()) { official = false; mappingsPath = ZipUtils.contains(mcpZip, "joined.srg") ? "joined.srg" : "config/joined.tsrg"; isFG2 = mappingsPath.endsWith(".srg"); remapAction = null; + if (!Files.exists(mcp) || isRefreshDeps()) { Files.copy(mcpZip, mcp, StandardCopyOption.REPLACE_EXISTING); } + return; } @@ -177,11 +178,11 @@ public ConfigDefinedRemapAction(Project project, JsonObject json) { this.project = project; this.name = json.get("version").getAsString(); this.mainClasspath = DependencyDownloader.download(project, this.name, false, true) - .getSingleFile(); + .getSingleFile(); this.classpath = DependencyDownloader.download(project, this.name, true, true); this.args = StreamSupport.stream(json.getAsJsonArray("args").spliterator(), false) - .map(JsonElement::getAsString) - .collect(Collectors.toList()); + .map(JsonElement::getAsString) + .collect(Collectors.toList()); for (int i = 1; i < this.args.size(); i++) { if (this.args.get(i).equals("{libraries}")) { this.args.remove(i); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java index 1486e918d..3608a4101 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java @@ -24,6 +24,37 @@ package net.fabricmc.loom.configuration.providers.forge; +import java.io.ByteArrayInputStream; +import java.io.Closeable; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UncheckedIOException; +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.jar.Attributes; +import java.util.jar.Manifest; +import java.util.regex.Pattern; +import java.util.stream.Stream; + import com.google.common.base.Preconditions; import com.google.common.base.Stopwatch; import com.google.common.collect.ImmutableList; @@ -34,12 +65,6 @@ import dev.architectury.tinyremapper.InputTag; import dev.architectury.tinyremapper.OutputConsumerPath; import dev.architectury.tinyremapper.TinyRemapper; -import net.fabricmc.loom.configuration.DependencyProvider; -import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl; -import net.fabricmc.loom.util.*; -import net.fabricmc.loom.util.function.FsPathConsumer; -import net.fabricmc.loom.util.srg.InnerClassRemapper; -import net.fabricmc.mappingio.tree.MemoryMappingTree; import org.apache.commons.io.output.NullOutputStream; import org.gradle.api.Project; import org.gradle.api.file.ConfigurableFileCollection; @@ -49,559 +74,555 @@ import org.gradle.api.logging.configuration.ShowStacktrace; import org.gradle.api.plugins.JavaPluginConvention; import org.gradle.api.tasks.SourceSet; -import org.objectweb.asm.*; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.ClassNode; -import java.io.*; -import java.net.URI; -import java.nio.file.*; -import java.util.*; -import java.util.concurrent.CompletableFuture; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.jar.Attributes; -import java.util.jar.Manifest; -import java.util.regex.Pattern; -import java.util.stream.Stream; +import net.fabricmc.loom.configuration.DependencyProvider; +import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl; +import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.DependencyDownloader; +import net.fabricmc.loom.util.FileSystemUtil; +import net.fabricmc.loom.util.MappingsProviderVerbose; +import net.fabricmc.loom.util.ThreadingUtils; +import net.fabricmc.loom.util.TinyRemapperHelper; +import net.fabricmc.loom.util.ZipUtils; +import net.fabricmc.loom.util.function.FsPathConsumer; +import net.fabricmc.loom.util.srg.InnerClassRemapper; +import net.fabricmc.mappingio.tree.MemoryMappingTree; public abstract class MinecraftPatchedProvider extends DependencyProvider { - private static final String LOOM_PATCH_VERSION_KEY = "Loom-Patch-Version"; - private static final String CURRENT_LOOM_PATCH_VERSION = "5"; - private static final String NAME_MAPPING_SERVICE_PATH = "/inject/META-INF/services/cpw.mods.modlauncher.api.INameMappingService"; - - // step 3 - fg2: srg transform, fg3: merge - protected File minecraftMergedPatchedSrgJar; - - // step 4 - Access transform - protected File minecraftMergedPatchedSrgAtJar; - - // step 5 - Offical Mapped Patched AT & Forge - protected File minecraftMergedPatchedAtJar; - protected File forgeMergedJar; - protected File minecraftClientExtra; - protected File projectAtHash; - - protected Set projectAts = new HashSet<>(); - protected boolean atDirty = false; - protected boolean filesDirty = false; - protected Path mcpConfigMappings; - - - public MinecraftPatchedProvider(Project project) { - super(project); - } - - @Override - public void provide(DependencyInfo dependency, Consumer postPopulationScheduler) throws Exception { - initFiles(); - testCleanAllCaches(); - beginTransform(); - } - - protected abstract void beginTransform() throws Exception; - - public abstract void endTransform() throws Exception; - - @Override - public String getTargetConfig() { - return Constants.Configurations.MINECRAFT; - } - - - public void initFiles() throws IOException { - filesDirty = false; - projectAtHash = new File(getDirectories().getProjectPersistentCache(), "at.sha256"); - ConfigurableFileCollection accessTransformers = getExtension().getForge().getAccessTransformers(); - accessTransformers.finalizeValue(); - projectAts = accessTransformers.getFiles(); - - if (projectAts.isEmpty()) { - SourceSet main = getProject().getConvention().findPlugin(JavaPluginConvention.class).getSourceSets().getByName("main"); - - for (File srcDir : main.getResources().getSrcDirs()) { - File projectAt = new File(srcDir, Constants.Forge.ACCESS_TRANSFORMER_PATH); - - if (projectAt.exists()) { - this.projectAts.add(projectAt); - break; - } - } - } - - if (isRefreshDeps() || !projectAtHash.exists()) { - writeAtHash(); - atDirty = !projectAts.isEmpty(); - } else { - byte[] expected = com.google.common.io.Files.asByteSource(projectAtHash).read(); - byte[] current = getProjectAtsHash(); - boolean mismatched = !Arrays.equals(current, expected); - - if (mismatched) { - writeAtHash(); - } - - atDirty = mismatched; - } - - MinecraftProviderImpl minecraftProvider = getExtension().getMinecraftProvider(); - PatchProvider patchProvider = getExtension().getPatchProvider(); - String minecraftVersion = minecraftProvider.minecraftVersion(); - String patchId = "forge-" + getExtension().getForgeProvider().getVersion().getCombined() + "-"; - - if (getExtension().isForgeAndOfficial()) { - minecraftProvider.setJarPrefix(patchId); - } - - File globalCache = getExtension().getForgeProvider().getGlobalCache(); - File projectDir = usesProjectCache() ? getExtension().getForgeProvider().getProjectCache() : globalCache; - projectDir.mkdirs(); - - forgeMergedJar = getExtension().isForgeAndOfficial() ? null : new File(globalCache, "forge-official.jar"); - minecraftMergedPatchedAtJar = new File(projectDir, "merged-at-patched.jar"); - minecraftClientExtra = new File(globalCache, "forge-client-extra.jar"); - minecraftMergedPatchedSrgJar = new File(globalCache, "merged-srg-patched.jar"); - minecraftMergedPatchedSrgAtJar = new File(projectDir, "merged-srg-at-patched.jar"); - - } - - public void testCleanAllCaches() throws IOException { - if (isRefreshDeps() || Stream.of(getGlobalCaches()).anyMatch(((Predicate) File::exists).negate()) - || !isPatchedJarUpToDate(minecraftMergedPatchedAtJar)) { - cleanAllCache(); - } else if (atDirty || Stream.of(getProjectCache()).anyMatch(((Predicate) File::exists).negate())) { - cleanProjectCache(); - } - } - - public void applyLoomPatchVersion(Path target) throws IOException { - try (FileSystemUtil.Delegate delegate = FileSystemUtil.getJarFileSystem(target, false)) { - Path manifestPath = delegate.get().getPath("META-INF/MANIFEST.MF"); - - Preconditions.checkArgument(Files.exists(manifestPath), "META-INF/MANIFEST.MF does not exist in patched srg jar!"); - Manifest manifest = new Manifest(); - - if (Files.exists(manifestPath)) { - try (InputStream stream = Files.newInputStream(manifestPath)) { - manifest.read(stream); - manifest.getMainAttributes().putValue(LOOM_PATCH_VERSION_KEY, CURRENT_LOOM_PATCH_VERSION); - } - } - - try (OutputStream stream = Files.newOutputStream(manifestPath, StandardOpenOption.CREATE)) { - manifest.write(stream); - } - } - } - - protected boolean isPatchedJarUpToDate(File jar) throws IOException { - if (!jar.exists()) return false; - - byte[] manifestBytes = ZipUtils.unpackNullable(jar.toPath(), "META-INF/MANIFEST.MF"); - - if (manifestBytes == null) { - return false; - } - - Manifest manifest = new Manifest(new ByteArrayInputStream(manifestBytes)); - Attributes attributes = manifest.getMainAttributes(); - String value = attributes.getValue(LOOM_PATCH_VERSION_KEY); - - if (Objects.equals(value, CURRENT_LOOM_PATCH_VERSION)) { - return true; - } else { - getProject().getLogger().lifecycle(":forge patched jars not up to date. current version: " + value); - return false; - } - } - - public boolean usesProjectCache() { - return !projectAts.isEmpty(); - } - - public File getMergedJar() { - return minecraftMergedPatchedAtJar; - } - - public File getForgeMergedJar() { - return forgeMergedJar; - } - - public boolean isAtDirty() { - return atDirty || filesDirty; - } - - - public void cleanAllCache() { - for (File file : getGlobalCaches()) { - file.delete(); - } - - cleanProjectCache(); - } - - protected File[] getGlobalCaches() { - File[] files = { - minecraftClientExtra - }; - - if (forgeMergedJar != null) { - Arrays.copyOf(files, files.length + 1); - files[files.length - 1] = forgeMergedJar; - } - - return files; - } - - public void cleanProjectCache() { - for (File file : getProjectCache()) { - file.delete(); - } - } - - protected File[] getProjectCache() { - return new File[] { - minecraftMergedPatchedSrgAtJar, - minecraftMergedPatchedAtJar - }; - } - - private void writeAtHash() throws IOException { - try (FileOutputStream out = new FileOutputStream(projectAtHash)) { - out.write(getProjectAtsHash()); - } - } - - private byte[] getProjectAtsHash() throws IOException { - if (projectAts.isEmpty()) return ByteSource.empty().hash(Hashing.sha256()).asBytes(); - List currentBytes = new ArrayList<>(); - - for (File projectAt : projectAts) { - currentBytes.add(com.google.common.io.Files.asByteSource(projectAt)); - } - - return ByteSource.concat(currentBytes).hash(Hashing.sha256()).asBytes(); - } - - private void walkFileSystems(File source, File target, Predicate filter, Function> toWalk, FsPathConsumer action) - throws IOException { - try (FileSystemUtil.Delegate sourceFs = FileSystemUtil.getJarFileSystem(source, false); - FileSystemUtil.Delegate targetFs = FileSystemUtil.getJarFileSystem(target, false)) { - for (Path sourceDir : toWalk.apply(sourceFs.get())) { - Path dir = sourceDir.toAbsolutePath(); - if (!Files.exists(dir)) continue; - Files.walk(dir) - .filter(Files::isRegularFile) - .filter(filter) - .forEach(it -> { - boolean root = dir.getParent() == null; - - try { - Path relativeSource = root ? it : dir.relativize(it); - Path targetPath = targetFs.get().getPath(relativeSource.toString()); - action.accept(sourceFs.get(), targetFs.get(), it, targetPath); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - }); - } - } - } - - protected abstract void patchJars(File clean, File output, Path patches, String side) throws IOException; - - protected abstract void mergeJars(Logger logger) throws Exception; - - protected void copyMissingClasses(File source, File target) throws IOException { - walkFileSystems(source, target, it -> it.toString().endsWith(".class"), (sourceFs, targetFs, sourcePath, targetPath) -> { - if (Files.exists(targetPath)) return; - Path parent = targetPath.getParent(); - - if (parent != null) { - Files.createDirectories(parent); - } - - Files.copy(sourcePath, targetPath); - }); - } - - protected void copyNonClassFiles(File source, File target) throws IOException { - Predicate filter = getExtension().isForgeAndOfficial() ? file -> { - String s = file.toString(); - return !s.endsWith(".class"); - } : file -> { - String s = file.toString(); - return !s.endsWith(".class") || (s.startsWith("META-INF") && !s.startsWith("META-INF/services")); - }; - - walkFileSystems(source, target, filter, this::copyReplacing); - } - - private void walkFileSystems(File source, File target, Predicate filter, FsPathConsumer action) throws IOException { - walkFileSystems(source, target, filter, FileSystem::getRootDirectories, action); - } - - private void copyAll(File source, File target) throws IOException { - walkFileSystems(source, target, it -> true, this::copyReplacing); - } - - private void copyReplacing(FileSystem sourceFs, FileSystem targetFs, Path sourcePath, Path targetPath) throws IOException { - Path parent = targetPath.getParent(); - - if (parent != null) { - Files.createDirectories(parent); - } - - Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING); - } - - protected void copyUserdevFiles(File source, File target) throws IOException { - // Removes the Forge name mapping service definition so that our own is used. - // If there are multiple name mapping services with the same "understanding" pair - // (source -> target namespace pair), modlauncher throws a fit and will crash. - // To use our YarnNamingService instead of MCPNamingService, we have to remove this file. - Predicate filter = file -> !file.toString().endsWith(".class") && !file.toString().equals(NAME_MAPPING_SERVICE_PATH); - - walkFileSystems(source, target, filter, fs -> Collections.singleton(fs.getPath("inject")), (sourceFs, targetFs, sourcePath, targetPath) -> { - Path parent = targetPath.getParent(); - - if (parent != null) { - Files.createDirectories(parent); - } - - Files.copy(sourcePath, targetPath); - }); - } - - protected void deleteParameterNames(File jarFile) throws Exception { - getProject().getLogger().info(":deleting parameter names for " + jarFile.getAbsolutePath()); - Stopwatch stopwatch = Stopwatch.createStarted(); - - try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + jarFile.toURI()), ImmutableMap.of("create", false))) { - ThreadingUtils.TaskCompleter completer = ThreadingUtils.taskCompleter(); - Pattern vignetteParameters = Pattern.compile("p_[0-9a-zA-Z]+_(?:[0-9a-zA-Z]+_)?"); - - for (Path file : (Iterable) Files.walk(fs.getPath("/"))::iterator) { - if (!file.toString().endsWith(".class")) continue; - - completer.add(() -> { - byte[] bytes = Files.readAllBytes(file); - ClassReader reader = new ClassReader(bytes); - ClassWriter writer = new ClassWriter(0); - - reader.accept(new ClassVisitor(Opcodes.ASM9, writer) { - @Override - public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { - return new MethodVisitor(Opcodes.ASM9, super.visitMethod(access, name, descriptor, signature, exceptions)) { - @Override - public void visitParameter(String name, int access) { - if (vignetteParameters.matcher(name).matches()) { - super.visitParameter(null, access); - } else { - super.visitParameter(name, access); - } - } - - @Override - public void visitLocalVariable(String name, String descriptor, String signature, Label start, Label end, int index) { - if (!vignetteParameters.matcher(name).matches()) { - super.visitLocalVariable(name, descriptor, signature, start, end, index); - } - } - }; - } - }, 0); - - byte[] out = writer.toByteArray(); - - if (!Arrays.equals(bytes, out)) { - Files.delete(file); - Files.write(file, out); - } - }); - } - - completer.complete(); - } - - getProject().getLogger().info(":deleting parameter names for " + jarFile.getAbsolutePath() + " in " + stopwatch); - } - - protected void fixParameterAnnotation(File jarFile) throws Exception { - getProject().getLogger().info(":fixing parameter annotations for " + jarFile.getAbsolutePath()); - Stopwatch stopwatch = Stopwatch.createStarted(); - - try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + jarFile.toURI()), ImmutableMap.of("create", false))) { - ThreadingUtils.TaskCompleter completer = ThreadingUtils.taskCompleter(); - - for (Path file : (Iterable) Files.walk(fs.getPath("/"))::iterator) { - if (!file.toString().endsWith(".class")) continue; - - completer.add(() -> { - byte[] bytes = Files.readAllBytes(file); - ClassReader reader = new ClassReader(bytes); - ClassNode node = new ClassNode(); - ClassVisitor visitor = new ParameterAnnotationFixer(node, null); - reader.accept(visitor, 0); - - ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); - node.accept(writer); - byte[] out = writer.toByteArray(); - - if (!Arrays.equals(bytes, out)) { - Files.delete(file); - Files.write(file, out); - } - }); - } - - completer.complete(); - } - - getProject().getLogger().info(":fixing parameter annotations for " + jarFile.getAbsolutePath() + " in " + stopwatch); - } - - - protected void accessTransformForge(Logger logger) throws Exception { - MinecraftProviderImpl minecraftProvider = getExtension().getMinecraftProvider(); - List toDelete = new ArrayList<>(); - String atDependency = Constants.Dependencies.ACCESS_TRANSFORMERS + (minecraftProvider.isNewerThan21w39a() ? Constants.Dependencies.Versions.ACCESS_TRANSFORMERS_NEW : Constants.Dependencies.Versions.ACCESS_TRANSFORMERS); - FileCollection classpath = DependencyDownloader.download(getProject(), atDependency); - Stopwatch stopwatch = Stopwatch.createStarted(); - - logger.lifecycle(":access transforming minecraft"); - - File input = minecraftMergedPatchedSrgJar; - File target = minecraftMergedPatchedSrgAtJar; - Files.deleteIfExists(target.toPath()); - - List args = new ArrayList<>(); - args.add("--inJar"); - args.add(input.getAbsolutePath()); - args.add("--outJar"); - args.add(target.getAbsolutePath()); - - for (File jar : ImmutableList.of(getForgeJar(), getForgeUserdevJar(), minecraftMergedPatchedSrgJar)) { - byte[] atBytes = ZipUtils.unpackNullable(jar.toPath(), Constants.Forge.ACCESS_TRANSFORMER_PATH); - - if (atBytes != null) { - File tmpFile = File.createTempFile("at-conf", ".cfg"); - toDelete.add(tmpFile); - Files.write(tmpFile.toPath(), atBytes, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); - args.add("--atFile"); - args.add(tmpFile.getAbsolutePath()); - } - } - - if (usesProjectCache()) { - for (File projectAt : projectAts) { - args.add("--atFile"); - args.add(projectAt.getAbsolutePath()); - } - } - - getProject().javaexec(spec -> { - spec.getMainClass().set("net.minecraftforge.accesstransformer.TransformerProcessor"); - spec.setArgs(args); - spec.setClasspath(classpath); - - // if running with INFO or DEBUG logging - if (getProject().getGradle().getStartParameter().getShowStacktrace() != ShowStacktrace.INTERNAL_EXCEPTIONS - || getProject().getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) < 0) { - spec.setStandardOutput(System.out); - spec.setErrorOutput(System.err); - } else { - spec.setStandardOutput(NullOutputStream.NULL_OUTPUT_STREAM); - spec.setErrorOutput(NullOutputStream.NULL_OUTPUT_STREAM); - } - }).rethrowFailure().assertNormalExitValue(); - - for (File file : toDelete) { - file.delete(); - } - - logger.lifecycle(":access transformed minecraft in " + stopwatch.stop()); - } - - - protected File getForgeJar() { - return getExtension().getForgeUniversalProvider().getForge(); - } - - protected File getForgeUserdevJar() { - return getExtension().getForgeUserdevProvider().getUserdevJar(); - } - - - - protected TinyRemapper buildRemapper(Path input, String from, String to) throws IOException { - Path[] libraries = TinyRemapperHelper.getMinecraftDependencies(getProject()); - MemoryMappingTree mappingsWithSrg = getExtension().getMappingsProvider().getMappingsWithSrg(); - - TinyRemapper remapper = TinyRemapper.newRemapper() - .logger(getProject().getLogger()::lifecycle) - .logUnknownInvokeDynamic(false) - .withMappings(TinyRemapperHelper.create(mappingsWithSrg, from, to, true)) - .withMappings(InnerClassRemapper.of(InnerClassRemapper.readClassNames(input), mappingsWithSrg, from, to)) - .renameInvalidLocals(true) - .rebuildSourceFilenames(true) - .fixPackageAccess(true) - .build(); - - if (getProject().getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) < 0) { - MappingsProviderVerbose.saveFile(remapper); - } - - remapper.readClassPath(libraries); - remapper.prepareClasses(); - return remapper; - } - - protected void remapPatchedJar(Logger logger) throws Exception { - getProject().getLogger().lifecycle(":remapping minecraft (TinyRemapper, srg -> official)"); - Path mcInput = minecraftMergedPatchedSrgAtJar.toPath(); - Path mcOutput = minecraftMergedPatchedAtJar.toPath(); - Path forgeJar = getForgeJar().toPath(); - Path forgeUserdevJar = getForgeUserdevJar().toPath(); - Path forgeOutput = null; - Files.deleteIfExists(mcOutput); - boolean splitJars = forgeMergedJar != null; - - if (splitJars) { - forgeOutput = forgeMergedJar.toPath(); - Files.deleteIfExists(forgeOutput); - } - - TinyRemapper remapper = buildRemapper(mcInput, "srg", "official"); - - try ( - OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(mcOutput).build(); - Closeable outputConsumerForge = !splitJars ? () -> { - } : new OutputConsumerPath.Builder(forgeOutput).build()) { - outputConsumer.addNonClassFiles(mcInput); - - InputTag mcTag = remapper.createInputTag(); - InputTag forgeTag = remapper.createInputTag(); - List> futures = new ArrayList<>(); - futures.add(remapper.readInputsAsync(mcTag, mcInput)); - futures.add(remapper.readInputsAsync(forgeTag, forgeJar, forgeUserdevJar)); - CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); - remapper.apply(outputConsumer, mcTag); - remapper.apply(splitJars ? (OutputConsumerPath) outputConsumerForge : outputConsumer, forgeTag); - } finally { - remapper.finish(); - } - - copyNonClassFiles(forgeJar.toFile(), splitJars ? forgeMergedJar : minecraftMergedPatchedAtJar); - copyUserdevFiles(forgeUserdevJar.toFile(), splitJars ? forgeMergedJar : minecraftMergedPatchedAtJar); - applyLoomPatchVersion(mcOutput); - } - - protected void fillClientExtraJar() throws IOException { - Files.deleteIfExists(minecraftClientExtra.toPath()); - FileSystemUtil.getJarFileSystem(minecraftClientExtra, true).close(); - - copyNonClassFiles(getExtension().getMinecraftProvider().minecraftClientJar, minecraftClientExtra); - } - + private static final String LOOM_PATCH_VERSION_KEY = "Loom-Patch-Version"; + private static final String CURRENT_LOOM_PATCH_VERSION = "5"; + private static final String NAME_MAPPING_SERVICE_PATH = "/inject/META-INF/services/cpw.mods.modlauncher.api.INameMappingService"; + + // step 3 - fg2: srg transform, fg3: merge + protected File minecraftMergedPatchedSrgJar; + + // step 4 - Access transform + protected File minecraftMergedPatchedSrgAtJar; + + // step 5 - Official Mapped Patched AT & Forge + protected File minecraftMergedPatchedAtJar; + protected File forgeMergedJar; + protected File minecraftClientExtra; + protected File projectAtHash; + + protected Set projectAts = new HashSet<>(); + protected boolean atDirty = false; + protected boolean filesDirty = false; + protected Path mcpConfigMappings; + + protected MinecraftPatchedProvider(Project project) { + super(project); + } + + @Override + public void provide(DependencyInfo dependency, Consumer postPopulationScheduler) throws Exception { + initFiles(); + testCleanAllCaches(); + beginTransform(); + } + + protected abstract void beginTransform() throws Exception; + + public abstract void endTransform() throws Exception; + + @Override + public String getTargetConfig() { + return Constants.Configurations.MINECRAFT; + } + + public void initFiles() throws IOException { + filesDirty = false; + projectAtHash = new File(getDirectories().getProjectPersistentCache(), "at.sha256"); + ConfigurableFileCollection accessTransformers = getExtension().getForge().getAccessTransformers(); + accessTransformers.finalizeValue(); + projectAts = accessTransformers.getFiles(); + + if (projectAts.isEmpty()) { + SourceSet main = getProject().getConvention().findPlugin(JavaPluginConvention.class).getSourceSets().getByName("main"); + + for (File srcDir : main.getResources().getSrcDirs()) { + File projectAt = new File(srcDir, Constants.Forge.ACCESS_TRANSFORMER_PATH); + + if (projectAt.exists()) { + this.projectAts.add(projectAt); + break; + } + } + } + + if (isRefreshDeps() || !projectAtHash.exists()) { + writeAtHash(); + atDirty = !projectAts.isEmpty(); + } else { + byte[] expected = com.google.common.io.Files.asByteSource(projectAtHash).read(); + byte[] current = getProjectAtsHash(); + boolean mismatched = !Arrays.equals(current, expected); + + if (mismatched) { + writeAtHash(); + } + + atDirty = mismatched; + } + + MinecraftProviderImpl minecraftProvider = getExtension().getMinecraftProvider(); + PatchProvider patchProvider = getExtension().getPatchProvider(); + String minecraftVersion = minecraftProvider.minecraftVersion(); + String patchId = "forge-" + getExtension().getForgeProvider().getVersion().getCombined() + "-"; + + if (getExtension().isForgeAndOfficial()) { + minecraftProvider.setJarPrefix(patchId); + } + + File globalCache = getExtension().getForgeProvider().getGlobalCache(); + File projectDir = usesProjectCache() ? getExtension().getForgeProvider().getProjectCache() : globalCache; + projectDir.mkdirs(); + + forgeMergedJar = getExtension().isForgeAndOfficial() ? null : new File(globalCache, "forge-official.jar"); + minecraftMergedPatchedAtJar = new File(projectDir, "merged-at-patched.jar"); + minecraftClientExtra = new File(globalCache, "forge-client-extra.jar"); + minecraftMergedPatchedSrgJar = new File(globalCache, "merged-srg-patched.jar"); + minecraftMergedPatchedSrgAtJar = new File(projectDir, "merged-srg-at-patched.jar"); + } + + public void testCleanAllCaches() throws IOException { + if (isRefreshDeps() || Stream.of(getGlobalCaches()).anyMatch(((Predicate) File::exists).negate()) + || !isPatchedJarUpToDate(minecraftMergedPatchedAtJar)) { + cleanAllCache(); + } else if (atDirty || Stream.of(getProjectCache()).anyMatch(((Predicate) File::exists).negate())) { + cleanProjectCache(); + } + } + + public void applyLoomPatchVersion(Path target) throws IOException { + try (FileSystemUtil.Delegate delegate = FileSystemUtil.getJarFileSystem(target, false)) { + Path manifestPath = delegate.get().getPath("META-INF/MANIFEST.MF"); + + Preconditions.checkArgument(Files.exists(manifestPath), "META-INF/MANIFEST.MF does not exist in patched srg jar!"); + Manifest manifest = new Manifest(); + + if (Files.exists(manifestPath)) { + try (InputStream stream = Files.newInputStream(manifestPath)) { + manifest.read(stream); + manifest.getMainAttributes().putValue(LOOM_PATCH_VERSION_KEY, CURRENT_LOOM_PATCH_VERSION); + } + } + + try (OutputStream stream = Files.newOutputStream(manifestPath, StandardOpenOption.CREATE)) { + manifest.write(stream); + } + } + } + + protected boolean isPatchedJarUpToDate(File jar) throws IOException { + if (!jar.exists()) return false; + + byte[] manifestBytes = ZipUtils.unpackNullable(jar.toPath(), "META-INF/MANIFEST.MF"); + + if (manifestBytes == null) { + return false; + } + + Manifest manifest = new Manifest(new ByteArrayInputStream(manifestBytes)); + Attributes attributes = manifest.getMainAttributes(); + String value = attributes.getValue(LOOM_PATCH_VERSION_KEY); + + if (Objects.equals(value, CURRENT_LOOM_PATCH_VERSION)) { + return true; + } else { + getProject().getLogger().lifecycle(":forge patched jars not up to date. current version: " + value); + return false; + } + } + + public boolean usesProjectCache() { + return !projectAts.isEmpty(); + } + + public File getMergedJar() { + return minecraftMergedPatchedAtJar; + } + + public File getForgeMergedJar() { + return forgeMergedJar; + } + + public boolean isAtDirty() { + return atDirty || filesDirty; + } + + public void cleanAllCache() { + for (File file : getGlobalCaches()) { + file.delete(); + } + + cleanProjectCache(); + } + + protected File[] getGlobalCaches() { + File[] files = { + minecraftClientExtra + }; + + if (forgeMergedJar != null) { + Arrays.copyOf(files, files.length + 1); + files[files.length - 1] = forgeMergedJar; + } + + return files; + } + + public void cleanProjectCache() { + for (File file : getProjectCache()) { + file.delete(); + } + } + + protected File[] getProjectCache() { + return new File[] { + minecraftMergedPatchedSrgAtJar, + minecraftMergedPatchedAtJar + }; + } + + private void writeAtHash() throws IOException { + try (FileOutputStream out = new FileOutputStream(projectAtHash)) { + out.write(getProjectAtsHash()); + } + } + + private byte[] getProjectAtsHash() throws IOException { + if (projectAts.isEmpty()) return ByteSource.empty().hash(Hashing.sha256()).asBytes(); + List currentBytes = new ArrayList<>(); + + for (File projectAt : projectAts) { + currentBytes.add(com.google.common.io.Files.asByteSource(projectAt)); + } + + return ByteSource.concat(currentBytes).hash(Hashing.sha256()).asBytes(); + } + + private void walkFileSystems(File source, File target, Predicate filter, Function> toWalk, FsPathConsumer action) + throws IOException { + try (FileSystemUtil.Delegate sourceFs = FileSystemUtil.getJarFileSystem(source, false); + FileSystemUtil.Delegate targetFs = FileSystemUtil.getJarFileSystem(target, false)) { + for (Path sourceDir : toWalk.apply(sourceFs.get())) { + Path dir = sourceDir.toAbsolutePath(); + if (!Files.exists(dir)) continue; + Files.walk(dir) + .filter(Files::isRegularFile) + .filter(filter) + .forEach(it -> { + boolean root = dir.getParent() == null; + + try { + Path relativeSource = root ? it : dir.relativize(it); + Path targetPath = targetFs.get().getPath(relativeSource.toString()); + action.accept(sourceFs.get(), targetFs.get(), it, targetPath); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + } + } + } + + protected abstract void patchJars(File clean, File output, Path patches, String side) throws IOException; + + protected abstract void mergeJars(Logger logger) throws Exception; + + protected void copyMissingClasses(File source, File target) throws IOException { + walkFileSystems(source, target, it -> it.toString().endsWith(".class"), (sourceFs, targetFs, sourcePath, targetPath) -> { + if (Files.exists(targetPath)) return; + Path parent = targetPath.getParent(); + + if (parent != null) { + Files.createDirectories(parent); + } + + Files.copy(sourcePath, targetPath); + }); + } + + protected void copyNonClassFiles(File source, File target) throws IOException { + Predicate filter = getExtension().isForgeAndOfficial() ? file -> { + String s = file.toString(); + return !s.endsWith(".class"); + } : file -> { + String s = file.toString(); + return !s.endsWith(".class") || (s.startsWith("META-INF") && !s.startsWith("META-INF/services")); + }; + + walkFileSystems(source, target, filter, this::copyReplacing); + } + + private void walkFileSystems(File source, File target, Predicate filter, FsPathConsumer action) throws IOException { + walkFileSystems(source, target, filter, FileSystem::getRootDirectories, action); + } + + private void copyAll(File source, File target) throws IOException { + walkFileSystems(source, target, it -> true, this::copyReplacing); + } + + private void copyReplacing(FileSystem sourceFs, FileSystem targetFs, Path sourcePath, Path targetPath) throws IOException { + Path parent = targetPath.getParent(); + + if (parent != null) { + Files.createDirectories(parent); + } + + Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING); + } + + protected void copyUserdevFiles(File source, File target) throws IOException { + // Removes the Forge name mapping service definition so that our own is used. + // If there are multiple name mapping services with the same "understanding" pair + // (source -> target namespace pair), modlauncher throws a fit and will crash. + // To use our YarnNamingService instead of MCPNamingService, we have to remove this file. + Predicate filter = file -> !file.toString().endsWith(".class") && !file.toString().equals(NAME_MAPPING_SERVICE_PATH); + + walkFileSystems(source, target, filter, fs -> Collections.singleton(fs.getPath("inject")), (sourceFs, targetFs, sourcePath, targetPath) -> { + Path parent = targetPath.getParent(); + + if (parent != null) { + Files.createDirectories(parent); + } + + Files.copy(sourcePath, targetPath); + }); + } + + protected void deleteParameterNames(File jarFile) throws Exception { + getProject().getLogger().info(":deleting parameter names for " + jarFile.getAbsolutePath()); + Stopwatch stopwatch = Stopwatch.createStarted(); + + try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + jarFile.toURI()), ImmutableMap.of("create", false))) { + ThreadingUtils.TaskCompleter completer = ThreadingUtils.taskCompleter(); + Pattern vignetteParameters = Pattern.compile("p_[0-9a-zA-Z]+_(?:[0-9a-zA-Z]+_)?"); + + for (Path file : (Iterable) Files.walk(fs.getPath("/"))::iterator) { + if (!file.toString().endsWith(".class")) continue; + + completer.add(() -> { + byte[] bytes = Files.readAllBytes(file); + ClassReader reader = new ClassReader(bytes); + ClassWriter writer = new ClassWriter(0); + + reader.accept(new ClassVisitor(Opcodes.ASM9, writer) { + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { + return new MethodVisitor(Opcodes.ASM9, super.visitMethod(access, name, descriptor, signature, exceptions)) { + @Override + public void visitParameter(String name, int access) { + if (vignetteParameters.matcher(name).matches()) { + super.visitParameter(null, access); + } else { + super.visitParameter(name, access); + } + } + + @Override + public void visitLocalVariable(String name, String descriptor, String signature, Label start, Label end, int index) { + if (!vignetteParameters.matcher(name).matches()) { + super.visitLocalVariable(name, descriptor, signature, start, end, index); + } + } + }; + } + }, 0); + + byte[] out = writer.toByteArray(); + + if (!Arrays.equals(bytes, out)) { + Files.delete(file); + Files.write(file, out); + } + }); + } + + completer.complete(); + } + + getProject().getLogger().info(":deleting parameter names for " + jarFile.getAbsolutePath() + " in " + stopwatch); + } + + protected void fixParameterAnnotation(File jarFile) throws Exception { + getProject().getLogger().info(":fixing parameter annotations for " + jarFile.getAbsolutePath()); + Stopwatch stopwatch = Stopwatch.createStarted(); + + try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + jarFile.toURI()), ImmutableMap.of("create", false))) { + ThreadingUtils.TaskCompleter completer = ThreadingUtils.taskCompleter(); + + for (Path file : (Iterable) Files.walk(fs.getPath("/"))::iterator) { + if (!file.toString().endsWith(".class")) continue; + + completer.add(() -> { + byte[] bytes = Files.readAllBytes(file); + ClassReader reader = new ClassReader(bytes); + ClassNode node = new ClassNode(); + ClassVisitor visitor = new ParameterAnnotationFixer(node, null); + reader.accept(visitor, 0); + + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); + node.accept(writer); + byte[] out = writer.toByteArray(); + + if (!Arrays.equals(bytes, out)) { + Files.delete(file); + Files.write(file, out); + } + }); + } + + completer.complete(); + } + + getProject().getLogger().info(":fixing parameter annotations for " + jarFile.getAbsolutePath() + " in " + stopwatch); + } + + protected void accessTransformForge(Logger logger) throws Exception { + MinecraftProviderImpl minecraftProvider = getExtension().getMinecraftProvider(); + List toDelete = new ArrayList<>(); + String atDependency = Constants.Dependencies.ACCESS_TRANSFORMERS + (minecraftProvider.isNewerThan21w39a() ? Constants.Dependencies.Versions.ACCESS_TRANSFORMERS_NEW : Constants.Dependencies.Versions.ACCESS_TRANSFORMERS); + FileCollection classpath = DependencyDownloader.download(getProject(), atDependency); + Stopwatch stopwatch = Stopwatch.createStarted(); + + logger.lifecycle(":access transforming minecraft"); + + File input = minecraftMergedPatchedSrgJar; + File target = minecraftMergedPatchedSrgAtJar; + Files.deleteIfExists(target.toPath()); + + List args = new ArrayList<>(); + args.add("--inJar"); + args.add(input.getAbsolutePath()); + args.add("--outJar"); + args.add(target.getAbsolutePath()); + + for (File jar : ImmutableList.of(getForgeJar(), getForgeUserdevJar(), minecraftMergedPatchedSrgJar)) { + byte[] atBytes = ZipUtils.unpackNullable(jar.toPath(), Constants.Forge.ACCESS_TRANSFORMER_PATH); + + if (atBytes != null) { + File tmpFile = File.createTempFile("at-conf", ".cfg"); + toDelete.add(tmpFile); + Files.write(tmpFile.toPath(), atBytes, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + args.add("--atFile"); + args.add(tmpFile.getAbsolutePath()); + } + } + + if (usesProjectCache()) { + for (File projectAt : projectAts) { + args.add("--atFile"); + args.add(projectAt.getAbsolutePath()); + } + } + + getProject().javaexec(spec -> { + spec.getMainClass().set("net.minecraftforge.accesstransformer.TransformerProcessor"); + spec.setArgs(args); + spec.setClasspath(classpath); + + // if running with INFO or DEBUG logging + if (getProject().getGradle().getStartParameter().getShowStacktrace() != ShowStacktrace.INTERNAL_EXCEPTIONS + || getProject().getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) < 0) { + spec.setStandardOutput(System.out); + spec.setErrorOutput(System.err); + } else { + spec.setStandardOutput(NullOutputStream.NULL_OUTPUT_STREAM); + spec.setErrorOutput(NullOutputStream.NULL_OUTPUT_STREAM); + } + }).rethrowFailure().assertNormalExitValue(); + + for (File file : toDelete) { + file.delete(); + } + + logger.lifecycle(":access transformed minecraft in " + stopwatch.stop()); + } + + protected File getForgeJar() { + return getExtension().getForgeUniversalProvider().getForge(); + } + + protected File getForgeUserdevJar() { + return getExtension().getForgeUserdevProvider().getUserdevJar(); + } + + protected TinyRemapper buildRemapper(Path input, String from, String to) throws IOException { + Path[] libraries = TinyRemapperHelper.getMinecraftDependencies(getProject()); + MemoryMappingTree mappingsWithSrg = getExtension().getMappingsProvider().getMappingsWithSrg(); + + TinyRemapper remapper = TinyRemapper.newRemapper() + .logger(getProject().getLogger()::lifecycle) + .logUnknownInvokeDynamic(false) + .withMappings(TinyRemapperHelper.create(mappingsWithSrg, from, to, true)) + .withMappings(InnerClassRemapper.of(InnerClassRemapper.readClassNames(input), mappingsWithSrg, from, to)) + .renameInvalidLocals(true) + .rebuildSourceFilenames(true) + .fixPackageAccess(true) + .build(); + + if (getProject().getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) < 0) { + MappingsProviderVerbose.saveFile(remapper); + } + + remapper.readClassPath(libraries); + remapper.prepareClasses(); + return remapper; + } + + protected void remapPatchedJar(Logger logger) throws Exception { + getProject().getLogger().lifecycle(":remapping minecraft (TinyRemapper, srg -> official)"); + Path mcInput = minecraftMergedPatchedSrgAtJar.toPath(); + Path mcOutput = minecraftMergedPatchedAtJar.toPath(); + Path forgeJar = getForgeJar().toPath(); + Path forgeUserdevJar = getForgeUserdevJar().toPath(); + Path forgeOutput = null; + Files.deleteIfExists(mcOutput); + boolean splitJars = forgeMergedJar != null; + + if (splitJars) { + forgeOutput = forgeMergedJar.toPath(); + Files.deleteIfExists(forgeOutput); + } + + TinyRemapper remapper = buildRemapper(mcInput, "srg", "official"); + + try ( + OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(mcOutput).build(); + Closeable outputConsumerForge = !splitJars ? () -> { + } : new OutputConsumerPath.Builder(forgeOutput).build()) { + outputConsumer.addNonClassFiles(mcInput); + + InputTag mcTag = remapper.createInputTag(); + InputTag forgeTag = remapper.createInputTag(); + List> futures = new ArrayList<>(); + futures.add(remapper.readInputsAsync(mcTag, mcInput)); + futures.add(remapper.readInputsAsync(forgeTag, forgeJar, forgeUserdevJar)); + CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); + remapper.apply(outputConsumer, mcTag); + remapper.apply(splitJars ? (OutputConsumerPath) outputConsumerForge : outputConsumer, forgeTag); + } finally { + remapper.finish(); + } + + copyNonClassFiles(forgeJar.toFile(), splitJars ? forgeMergedJar : minecraftMergedPatchedAtJar); + copyUserdevFiles(forgeUserdevJar.toFile(), splitJars ? forgeMergedJar : minecraftMergedPatchedAtJar); + applyLoomPatchVersion(mcOutput); + } + + protected void fillClientExtraJar() throws IOException { + Files.deleteIfExists(minecraftClientExtra.toPath()); + FileSystemUtil.getJarFileSystem(minecraftClientExtra, true).close(); + + copyNonClassFiles(getExtension().getMinecraftProvider().minecraftClientJar, minecraftClientExtra); + } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/PatchProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/PatchProvider.java index b59a60ced..95a05e6dd 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/PatchProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/PatchProvider.java @@ -28,7 +28,11 @@ import java.io.IOException; import java.io.UncheckedIOException; import java.net.URI; -import java.nio.file.*; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; import java.util.function.Consumer; import com.google.common.collect.ImmutableMap; @@ -60,15 +64,13 @@ public void provide(DependencyInfo dependency, Consumer postPopulation Files.copy(fs.getPath("binpatches.pack.lzma"), clientPatches, StandardCopyOption.REPLACE_EXISTING); Files.copy(fs.getPath("binpatches.pack.lzma"), serverPatches, StandardCopyOption.REPLACE_EXISTING); } - } } else { - if (Files.notExists(clientPatches) || Files.notExists(serverPatches) || isRefreshDeps()) { getProject().getLogger().info(":extracting forge patches"); Path installerJar = dependency.resolveFile().orElseThrow(() -> new RuntimeException( - "Could not resolve Forge installer")).toPath(); + "Could not resolve Forge installer")).toPath(); try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + installerJar.toUri()), ImmutableMap.of("create", false))) { Files.copy(fs.getPath("data", "client.lzma"), clientPatches, StandardCopyOption.REPLACE_EXISTING); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java index 8802be3e2..1713c9ca8 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java @@ -24,10 +24,22 @@ package net.fabricmc.loom.configuration.providers.forge; -import java.io.*; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.io.StringReader; +import java.io.UncheckedIOException; import java.net.URI; import java.nio.charset.StandardCharsets; -import java.nio.file.*; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.StandardOpenOption; import java.util.HashMap; import java.util.Map; import java.util.function.Consumer; @@ -105,17 +117,7 @@ public void provide(DependencyInfo dependency, Consumer postPopulation Files.deleteIfExists(mergedMojangRaw); Files.deleteIfExists(mergedMojang); - net.minecraftforge.installertools.ConsoleTool.main(new String[] { - "--task", - "MERGE_MAPPING", - "--left", - getSrg().toAbsolutePath().toString(), - "--right", - getMojmapTsrg2(getProject(), getExtension()).toAbsolutePath().toString(), - "--classes", - "--output", - mergedMojangRaw.toAbsolutePath().toString() - }); + net.minecraftforge.installertools.ConsoleTool.main(new String[] {"--task", "MERGE_MAPPING", "--left", getSrg().toAbsolutePath().toString(), "--right", getMojmapTsrg2(getProject(), getExtension()).toAbsolutePath().toString(), "--classes", "--output", mergedMojangRaw.toAbsolutePath().toString()}); MemoryMappingTree tree = new MemoryMappingTree(); MappingReader.read(new StringReader(FileUtils.readFileToString(mergedMojangRaw.toFile(), StandardCharsets.UTF_8)), new FieldDescWrappingVisitor(tree)); @@ -175,8 +177,7 @@ public boolean visitField(String srcName, String srcDesc) throws IOException { return super.visitField(srcName, srcDesc); } - private record FieldKey(String owner, String name) { - } + private record FieldKey(String owner, String name) {} } private void init(String version) { @@ -224,8 +225,7 @@ public static Path getMojmapTsrg(Project project, LoomGradleExtension extension) if (Files.notExists(mojmapTsrg) || LoomGradlePlugin.refreshDeps) { try (BufferedWriter writer = Files.newBufferedWriter(mojmapTsrg, StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) { - Tsrg2Utils.writeTsrg(visitor -> visitMojmap(visitor, project), - MappingsNamespace.NAMED.toString(), false, writer); + Tsrg2Utils.writeTsrg(visitor -> visitMojmap(visitor, project), MappingsNamespace.NAMED.toString(), false, writer); } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/FG2TaskApplyBinPatches.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/FG2TaskApplyBinPatches.java index 181208b5c..f6ae1edbc 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/FG2TaskApplyBinPatches.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/FG2TaskApplyBinPatches.java @@ -17,6 +17,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * USA */ + package net.fabricmc.loom.configuration.providers.forge.fg2; import java.io.BufferedOutputStream; @@ -39,206 +40,185 @@ import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; -import net.fabricmc.shade.java.util.jar.Pack200; -import org.gradle.api.Project; import com.google.common.base.Joiner; import com.google.common.base.Throwables; import com.google.common.collect.Maps; import com.google.common.io.ByteArrayDataInput; import com.google.common.io.ByteStreams; import com.nothome.delta.GDiffPatcher; - import lzma.sdk.lzma.Decoder; import lzma.streams.LzmaInputStream; +import org.gradle.api.Project; -public class FG2TaskApplyBinPatches { +import net.fabricmc.shade.java.util.jar.Pack200; - private static final HashMap patchlist = Maps.newHashMap(); - private static final GDiffPatcher patcher = new GDiffPatcher(); - - private static Project project; - - public static void doTask(Project project, File inJar, File patches, File outjar, String side) throws IOException - { - FG2TaskApplyBinPatches.project = project; - setup(patches, side); - - if (outjar.exists()) - { - outjar.delete(); - } - - ZipFile in = new ZipFile(inJar); - ZipInputStream classesIn = new ZipInputStream(new FileInputStream(inJar)); - final ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(outjar))); - final HashSet entries = new HashSet(); - - try - { - // DO PATCHES - log("Patching Class:"); - for (ZipEntry e : Collections.list(in.entries())) - { - if (e.getName().contains("META-INF")) - continue; - - if (e.isDirectory()) - { - out.putNextEntry(e); - } - else - { - ZipEntry n = new ZipEntry(e.getName()); - n.setTime(e.getTime()); - out.putNextEntry(n); - - byte[] data = ByteStreams.toByteArray(in.getInputStream(e)); - ClassPatch patch = patchlist.get(e.getName().replace('\\', '/')); - - if (patch != null) - { - log("\t%s (%s) (input size %d)", patch.targetClassName, patch.sourceClassName, data.length); - int inputChecksum = adlerHash(data); - if (patch.inputChecksum != inputChecksum) - { - throw new RuntimeException(String.format("There is a binary discrepency between the expected input class %s (%s) and the actual class. Checksum on disk is %x, in patch %x. Things are probably about to go very wrong. Did you put something into the jar file?", patch.targetClassName, patch.sourceClassName, inputChecksum, patch.inputChecksum)); - } - synchronized (patcher) - { - data = patcher.patch(data, patch.patch); - } - } - - out.write(data); - } - - // add the names to the hashset - entries.add(e.getName()); - } - - // COPY DATA - ZipEntry entry = null; - while ((entry = classesIn.getNextEntry()) != null) - { - if (entries.contains(entry.getName())) - continue; - - out.putNextEntry(entry); - out.write(ByteStreams.toByteArray(classesIn)); - entries.add(entry.getName()); - } - } - finally - { - classesIn.close(); - in.close(); - out.close(); - } - } - - private static int adlerHash(byte[] input) - { - Adler32 hasher = new Adler32(); - hasher.update(input); - return (int) hasher.getValue(); - } - - public static void setup(File patches, String side) - { - Pattern matcher = Pattern.compile(String.format("binpatch/%s/.*.binpatch", side)); - - JarInputStream jis; - try - { - LzmaInputStream binpatchesDecompressed = new LzmaInputStream(new FileInputStream(patches), new Decoder()); - ByteArrayOutputStream jarBytes = new ByteArrayOutputStream(); - JarOutputStream jos = new JarOutputStream(jarBytes); - Pack200.newUnpacker().unpack(binpatchesDecompressed, jos); - jis = new JarInputStream(new ByteArrayInputStream(jarBytes.toByteArray())); - } - catch (Exception e) - { - throw Throwables.propagate(e); - } - - log("Reading Patches:"); - do - { - try - { - JarEntry entry = jis.getNextJarEntry(); - if (entry == null) - { - break; - } - - if (matcher.matcher(entry.getName()).matches()) - { - ClassPatch cp = readPatch(entry, jis); - patchlist.put(cp.sourceClassName.replace('.', '/') + ".class", cp); - } - else - { - log("skipping entry: %s", entry.getName()); - jis.closeEntry(); - } - } - catch (IOException e) - {} - } while (true); - log("Read %d binary patches", patchlist.size()); - log("Patch list :\n\t%s", Joiner.on("\n\t").join(patchlist.entrySet())); - } - - private static ClassPatch readPatch(JarEntry patchEntry, JarInputStream jis) throws IOException - { - log("\t%s", patchEntry.getName()); - ByteArrayDataInput input = ByteStreams.newDataInput(ByteStreams.toByteArray(jis)); - - String name = input.readUTF(); - String sourceClassName = input.readUTF(); - String targetClassName = input.readUTF(); - boolean exists = input.readBoolean(); - int inputChecksum = 0; - if (exists) - { - inputChecksum = input.readInt(); - } - int patchLength = input.readInt(); - byte[] patchBytes = new byte[patchLength]; - input.readFully(patchBytes); - - return new ClassPatch(name, sourceClassName, targetClassName, exists, inputChecksum, patchBytes); - } - - private static void log(String format, Object... args) - { - project.getLogger().info(String.format(format, args)); - } - - public static class ClassPatch - { - public final String name; - public final String sourceClassName; - public final String targetClassName; - public final boolean existsAtTarget; - public final byte[] patch; - public final int inputChecksum; - - public ClassPatch(String name, String sourceClassName, String targetClassName, boolean existsAtTarget, int inputChecksum, byte[] patch) - { - this.name = name; - this.sourceClassName = sourceClassName; - this.targetClassName = targetClassName; - this.existsAtTarget = existsAtTarget; - this.inputChecksum = inputChecksum; - this.patch = patch; - } - - @Override - public String toString() - { - return String.format("%s : %s => %s (%b) size %d", name, sourceClassName, targetClassName, existsAtTarget, patch.length); - } - } +public class FG2TaskApplyBinPatches { + private static final HashMap patchlist = Maps.newHashMap(); + private static final GDiffPatcher patcher = new GDiffPatcher(); + + private static Project project; + + public static void doTask(Project project, File inJar, File patches, File outjar, String side) throws IOException { + FG2TaskApplyBinPatches.project = project; + setup(patches, side); + + if (outjar.exists()) { + outjar.delete(); + } + + ZipFile in = new ZipFile(inJar); + ZipInputStream classesIn = new ZipInputStream(new FileInputStream(inJar)); + final ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(outjar))); + final HashSet entries = new HashSet(); + + try { + // DO PATCHES + log("Patching Class:"); + + for (ZipEntry e : Collections.list(in.entries())) { + if (e.getName().contains("META-INF")) { + continue; + } + + if (e.isDirectory()) { + out.putNextEntry(e); + } else { + ZipEntry n = new ZipEntry(e.getName()); + n.setTime(e.getTime()); + out.putNextEntry(n); + + byte[] data = ByteStreams.toByteArray(in.getInputStream(e)); + ClassPatch patch = patchlist.get(e.getName().replace('\\', '/')); + + if (patch != null) { + log("\t%s (%s) (input size %d)", patch.targetClassName, patch.sourceClassName, data.length); + int inputChecksum = adlerHash(data); + + if (patch.inputChecksum != inputChecksum) { + throw new RuntimeException(String.format("There is a binary discrepency between the expected input class %s (%s) and the actual class. Checksum on disk is %x, in patch %x. Things are probably about to go very wrong. Did you put something into the jar file?", patch.targetClassName, patch.sourceClassName, inputChecksum, patch.inputChecksum)); + } + + synchronized (patcher) { + data = patcher.patch(data, patch.patch); + } + } + + out.write(data); + } + + // add the names to the hashset + entries.add(e.getName()); + } + + // COPY DATA + ZipEntry entry = null; + + while ((entry = classesIn.getNextEntry()) != null) { + if (entries.contains(entry.getName())) { + continue; + } + + out.putNextEntry(entry); + out.write(ByteStreams.toByteArray(classesIn)); + entries.add(entry.getName()); + } + } finally { + classesIn.close(); + in.close(); + out.close(); + } + } + + private static int adlerHash(byte[] input) { + Adler32 hasher = new Adler32(); + hasher.update(input); + return (int) hasher.getValue(); + } + + public static void setup(File patches, String side) { + Pattern matcher = Pattern.compile(String.format("binpatch/%s/.*.binpatch", side)); + + JarInputStream jis; + + try { + LzmaInputStream binpatchesDecompressed = new LzmaInputStream(new FileInputStream(patches), new Decoder()); + ByteArrayOutputStream jarBytes = new ByteArrayOutputStream(); + JarOutputStream jos = new JarOutputStream(jarBytes); + Pack200.newUnpacker().unpack(binpatchesDecompressed, jos); + jis = new JarInputStream(new ByteArrayInputStream(jarBytes.toByteArray())); + } catch (Exception e) { + throw Throwables.propagate(e); + } + + log("Reading Patches:"); + + do { + try { + JarEntry entry = jis.getNextJarEntry(); + + if (entry == null) { + break; + } + + if (matcher.matcher(entry.getName()).matches()) { + ClassPatch cp = readPatch(entry, jis); + patchlist.put(cp.sourceClassName.replace('.', '/') + ".class", cp); + } else { + log("skipping entry: %s", entry.getName()); + jis.closeEntry(); + } + } catch (IOException e) { + } + } while (true); + log("Read %d binary patches", patchlist.size()); + log("Patch list :\n\t%s", Joiner.on("\n\t").join(patchlist.entrySet())); + } + + private static ClassPatch readPatch(JarEntry patchEntry, JarInputStream jis) throws IOException { + log("\t%s", patchEntry.getName()); + ByteArrayDataInput input = ByteStreams.newDataInput(ByteStreams.toByteArray(jis)); + + String name = input.readUTF(); + String sourceClassName = input.readUTF(); + String targetClassName = input.readUTF(); + boolean exists = input.readBoolean(); + int inputChecksum = 0; + + if (exists) { + inputChecksum = input.readInt(); + } + + int patchLength = input.readInt(); + byte[] patchBytes = new byte[patchLength]; + input.readFully(patchBytes); + + return new ClassPatch(name, sourceClassName, targetClassName, exists, inputChecksum, patchBytes); + } + + private static void log(String format, Object... args) { + project.getLogger().info(String.format(format, args)); + } + + public static class ClassPatch { + public final String name; + public final String sourceClassName; + public final String targetClassName; + public final boolean existsAtTarget; + public final byte[] patch; + public final int inputChecksum; + + public ClassPatch(String name, String sourceClassName, String targetClassName, boolean existsAtTarget, int inputChecksum, byte[] patch) { + this.name = name; + this.sourceClassName = sourceClassName; + this.targetClassName = targetClassName; + this.existsAtTarget = existsAtTarget; + this.inputChecksum = inputChecksum; + this.patch = patch; + } + + @Override + public String toString() { + return String.format("%s : %s => %s (%b) size %d", name, sourceClassName, targetClassName, existsAtTarget, patch.length); + } + } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/MinecraftPatchedProviderFG2.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/MinecraftPatchedProviderFG2.java index cef38329b..4ab981e94 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/MinecraftPatchedProviderFG2.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/MinecraftPatchedProviderFG2.java @@ -24,20 +24,6 @@ package net.fabricmc.loom.configuration.providers.forge.fg2; -import com.google.common.base.Stopwatch; -import dev.architectury.tinyremapper.InputTag; -import dev.architectury.tinyremapper.OutputConsumerPath; -import dev.architectury.tinyremapper.TinyRemapper; -import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl; -import net.fabricmc.loom.configuration.providers.forge.MinecraftPatchedProvider; -import net.fabricmc.loom.configuration.providers.forge.PatchProvider; -import net.fabricmc.loom.util.Constants; -import net.fabricmc.loom.util.ThreadingUtils; -import net.fabricmc.loom.util.srg.SpecialSourceExecutor; -import org.apache.commons.io.output.NullOutputStream; -import org.gradle.api.Project; -import org.gradle.api.logging.Logger; - import java.io.File; import java.io.IOException; import java.io.PrintStream; @@ -45,250 +31,253 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; -import java.util.HashSet; import java.util.Locale; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; -public class MinecraftPatchedProviderFG2 extends MinecraftPatchedProvider { - - // step 0: strip minecraft jars - private File minecraftClientJar; - private File minecraftServerJar; - - // Step 1: Binary Patch (global) - private File minecraftClientPatchedJar; - private File minecraftServerPatchedJar; - - // Step 2: Merge (global) - private File minecraftMergedPatchedJar; - - // Step 3: Srg Transform (global) - // field in super - - // step 4: Access Transform (global or project) - // field in super - - // Step 5: Remap Patched AT & Forge to Official (global or project) - // fields in super - - - public MinecraftPatchedProviderFG2(Project project) { - super(project); - } - - @Override - public void initFiles() throws IOException { - super.initFiles(); - File globalCache = getExtension().getForgeProvider().getGlobalCache(); - File projectDir = usesProjectCache() ? getExtension().getForgeProvider().getProjectCache() : globalCache; - minecraftClientJar = new File(globalCache, "client-stripped.jar"); - minecraftServerJar = new File(globalCache, "server-stripped.jar"); - minecraftClientPatchedJar = new File(globalCache, "client-patched.jar"); - minecraftServerPatchedJar = new File(globalCache, "server-patched.jar"); - minecraftMergedPatchedJar = new File(globalCache, "merged-patched.jar"); - - } - - @Override - protected File[] getGlobalCaches() { - File[] files = { - minecraftClientPatchedJar, - minecraftServerPatchedJar, - minecraftMergedPatchedJar, - minecraftMergedPatchedSrgJar, - minecraftClientExtra, - }; - - if (forgeMergedJar != null) { - Arrays.copyOf(files, files.length + 1); - files[files.length - 1] = forgeMergedJar; - } - - return files; - } - - private boolean dirty; - @Override - protected void beginTransform() throws Exception { - if (atDirty) { - getProject().getLogger().lifecycle(":found dirty access transformers"); - } - - this.dirty = false; - - // Step 0: strip the client/server jars - if (!minecraftClientJar.exists() || !minecraftServerJar.exists()) { - this.dirty = true; - stripJars(getProject().getLogger()); - } - - // Step 1: Binary Patch (global) - if (!minecraftClientPatchedJar.exists() || !minecraftServerPatchedJar.exists()) { - this.dirty = true; - patchJars(getProject().getLogger()); - } - } - - @Override - public void endTransform() throws Exception { - - // Step 2: Merge (global) - if (dirty || !minecraftMergedPatchedJar.exists()) { - mergeJars(getProject().getLogger()); - } - - // Step 3: Srg Transform (global) - if (dirty || !minecraftMergedPatchedSrgJar.exists()) { - remapPatchedJarToSrg(getProject().getLogger()); - } - - // Step 4: Access Transform (global or project) - if (atDirty || !minecraftMergedPatchedSrgAtJar.exists()) { - this.dirty = true; - accessTransformForge(getProject().getLogger()); - } - - if (forgeMergedJar != null && !forgeMergedJar.exists()) { - this.dirty = true; - } - - // Step 5: Remap Patched AT & Forge to Official (global or project) - if (dirty) { - remapPatchedJar(getProject().getLogger()); - - if (getExtension().isForgeAndOfficial()) { - fillClientExtraJar(); - } - } - - this.filesDirty = dirty; - this.dirty = false; - - if (getExtension().isForgeAndOfficial()) { - addDependency(minecraftClientExtra, Constants.Configurations.FORGE_EXTRA); - } - - - } - - - public enum Environment { - CLIENT(provider -> provider.minecraftClientJar, - provider -> provider.minecraftClientPatchedJar - ), - SERVER(provider -> provider.minecraftServerJar, - provider -> provider.minecraftServerPatchedJar - ); - - final Function srgJar; - final Function patchedSrgJar; - - Environment(Function srgJar, - Function patchedSrgJar) { - this.srgJar = srgJar; - this.patchedSrgJar = patchedSrgJar; - } - - public String side() { - return name().toLowerCase(Locale.ROOT); - } - } - - private void stripJars(Logger logger) throws IOException { - logger.lifecycle(":stripping jars"); - Set filter = Files.readAllLines(getExtension().getMcpConfigProvider().getMappings(), StandardCharsets.UTF_8).stream() - .filter(s -> s.startsWith("CL:")) - .map(s -> s.split(" ")[1] + ".class") - .collect(Collectors.toSet()); - MinecraftProviderImpl minecraftProvider = getExtension().getMinecraftProvider(); - SpecialSourceExecutor.stripJar(getProject(), minecraftProvider.minecraftClientJar.toPath(), minecraftClientJar.toPath(), filter); - SpecialSourceExecutor.stripJar(getProject(), minecraftProvider.getMinecraftServerJar().toPath(), minecraftServerJar.toPath(), filter); - } - - private void patchJars(Logger logger) throws IOException { - Stopwatch stopwatch = Stopwatch.createStarted(); - logger.lifecycle(":patching jars"); - - PatchProvider patchProvider = getExtension().getPatchProvider(); - patchJars(minecraftClientJar, minecraftClientPatchedJar, patchProvider.clientPatches, "client"); - patchJars(minecraftServerJar, minecraftServerPatchedJar, patchProvider.serverPatches, "server"); - - - ThreadingUtils.run(Environment.values(), environment -> { - copyMissingClasses(environment.srgJar.apply(this), environment.patchedSrgJar.apply(this)); - deleteParameterNames(environment.patchedSrgJar.apply(this)); - - if (getExtension().isForgeAndNotOfficial()) { - fixParameterAnnotation(environment.patchedSrgJar.apply(this)); - } - }); - - logger.lifecycle(":patched jars in " + stopwatch.stop()); - } - - @Override - protected void patchJars(File clean, File output, Path patches, String side) throws IOException { - PrintStream previous = System.out; - - try { - System.setOut(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM)); - } catch (SecurityException ignored) { - // Failed to replace logger filter, just ignore - } - - FG2TaskApplyBinPatches.doTask(getProject(), clean.getAbsoluteFile(), patches.toFile().getAbsoluteFile(), output.getAbsoluteFile(), side); - - try { - System.setOut(previous); - } catch (SecurityException ignored) { - // Failed to replace logger filter, just ignore - } - } - - @Override - protected void mergeJars(Logger logger) throws Exception { - // FIXME: Hack here: There are no server-only classes so we can just copy the client JAR. - // This will change if upstream Loom adds the possibility for separate projects/source sets per environment. - - - logger.lifecycle(":merging jars"); - Set mcLibs = getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_DEPENDENCIES).resolve(); - Files.copy(minecraftClientPatchedJar.toPath(), minecraftMergedPatchedJar.toPath()); - - - logger.lifecycle(":copying resources"); - - // Copy resources - if (getExtension().isForgeAndNotOfficial()) { - // Copy resources - MinecraftProviderImpl minecraftProvider = getExtension().getMinecraftProvider(); - copyNonClassFiles(minecraftProvider.minecraftClientJar, minecraftMergedPatchedJar); - copyNonClassFiles(minecraftProvider.getMinecraftServerJar(), minecraftMergedPatchedJar); - } - } - - protected void remapPatchedJarToSrg(Logger logger) throws Exception { - getProject().getLogger().lifecycle(":remapping minecraft (TinyRemapper, official -> srg)"); - Path mcInput = minecraftMergedPatchedJar.toPath(); - Path mcOutput = minecraftMergedPatchedSrgJar.toPath(); - - TinyRemapper remapper = buildRemapper(mcInput, "official", "srg"); - - try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(mcOutput).build()) { - InputTag mcTag = remapper.createInputTag(); - remapper.readInputsAsync(mcTag, mcInput).join(); - remapper.apply(outputConsumer, mcTag); - } finally { - remapper.finish(); - } - - logger.lifecycle(":copying resources"); - MinecraftProviderImpl minecraftProvider = getExtension().getMinecraftProvider(); - copyNonClassFiles(minecraftProvider.minecraftClientJar, mcOutput.toFile()); - copyNonClassFiles(minecraftProvider.getMinecraftServerJar(), mcOutput.toFile()); - } +import com.google.common.base.Stopwatch; +import dev.architectury.tinyremapper.InputTag; +import dev.architectury.tinyremapper.OutputConsumerPath; +import dev.architectury.tinyremapper.TinyRemapper; +import org.apache.commons.io.output.NullOutputStream; +import org.gradle.api.Project; +import org.gradle.api.logging.Logger; +import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl; +import net.fabricmc.loom.configuration.providers.forge.MinecraftPatchedProvider; +import net.fabricmc.loom.configuration.providers.forge.PatchProvider; +import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.ThreadingUtils; +import net.fabricmc.loom.util.srg.SpecialSourceExecutor; +public class MinecraftPatchedProviderFG2 extends MinecraftPatchedProvider { + // step 0: strip minecraft jars + private File minecraftClientJar; + private File minecraftServerJar; + + // Step 1: Binary Patch (global) + private File minecraftClientPatchedJar; + private File minecraftServerPatchedJar; + + // Step 2: Merge (global) + private File minecraftMergedPatchedJar; + + // Step 3: Srg Transform (global) + // field in super + + // step 4: Access Transform (global or project) + // field in super + + // Step 5: Remap Patched AT & Forge to Official (global or project) + // fields in super + + public MinecraftPatchedProviderFG2(Project project) { + super(project); + } + + @Override + public void initFiles() throws IOException { + super.initFiles(); + File globalCache = getExtension().getForgeProvider().getGlobalCache(); + File projectDir = usesProjectCache() ? getExtension().getForgeProvider().getProjectCache() : globalCache; + minecraftClientJar = new File(globalCache, "client-stripped.jar"); + minecraftServerJar = new File(globalCache, "server-stripped.jar"); + minecraftClientPatchedJar = new File(globalCache, "client-patched.jar"); + minecraftServerPatchedJar = new File(globalCache, "server-patched.jar"); + minecraftMergedPatchedJar = new File(globalCache, "merged-patched.jar"); + } + + @Override + protected File[] getGlobalCaches() { + File[] files = { + minecraftClientPatchedJar, + minecraftServerPatchedJar, + minecraftMergedPatchedJar, + minecraftMergedPatchedSrgJar, + minecraftClientExtra, + }; + + if (forgeMergedJar != null) { + Arrays.copyOf(files, files.length + 1); + files[files.length - 1] = forgeMergedJar; + } + + return files; + } + + private boolean dirty; + + @Override + protected void beginTransform() throws Exception { + if (atDirty) { + getProject().getLogger().lifecycle(":found dirty access transformers"); + } + + this.dirty = false; + + // Step 0: strip the client/server jars + if (!minecraftClientJar.exists() || !minecraftServerJar.exists()) { + this.dirty = true; + stripJars(getProject().getLogger()); + } + + // Step 1: Binary Patch (global) + if (!minecraftClientPatchedJar.exists() || !minecraftServerPatchedJar.exists()) { + this.dirty = true; + patchJars(getProject().getLogger()); + } + } + + @Override + public void endTransform() throws Exception { + // Step 2: Merge (global) + if (dirty || !minecraftMergedPatchedJar.exists()) { + mergeJars(getProject().getLogger()); + } + + // Step 3: Srg Transform (global) + if (dirty || !minecraftMergedPatchedSrgJar.exists()) { + remapPatchedJarToSrg(getProject().getLogger()); + } + + // Step 4: Access Transform (global or project) + if (atDirty || !minecraftMergedPatchedSrgAtJar.exists()) { + this.dirty = true; + accessTransformForge(getProject().getLogger()); + } + + if (forgeMergedJar != null && !forgeMergedJar.exists()) { + this.dirty = true; + } + + // Step 5: Remap Patched AT & Forge to Official (global or project) + if (dirty) { + remapPatchedJar(getProject().getLogger()); + + if (getExtension().isForgeAndOfficial()) { + fillClientExtraJar(); + } + } + + this.filesDirty = dirty; + this.dirty = false; + + if (getExtension().isForgeAndOfficial()) { + addDependency(minecraftClientExtra, Constants.Configurations.FORGE_EXTRA); + } + } + + public enum Environment { + CLIENT(provider -> provider.minecraftClientJar, + provider -> provider.minecraftClientPatchedJar + ), + SERVER(provider -> provider.minecraftServerJar, + provider -> provider.minecraftServerPatchedJar + ); + + final Function srgJar; + final Function patchedSrgJar; + + Environment(Function srgJar, + Function patchedSrgJar) { + this.srgJar = srgJar; + this.patchedSrgJar = patchedSrgJar; + } + + public String side() { + return name().toLowerCase(Locale.ROOT); + } + } + + private void stripJars(Logger logger) throws IOException { + logger.lifecycle(":stripping jars"); + Set filter = Files.readAllLines(getExtension().getMcpConfigProvider().getMappings(), StandardCharsets.UTF_8).stream() + .filter(s -> s.startsWith("CL:")) + .map(s -> s.split(" ")[1] + ".class") + .collect(Collectors.toSet()); + MinecraftProviderImpl minecraftProvider = getExtension().getMinecraftProvider(); + SpecialSourceExecutor.stripJar(getProject(), minecraftProvider.minecraftClientJar.toPath(), minecraftClientJar.toPath(), filter); + SpecialSourceExecutor.stripJar(getProject(), minecraftProvider.getMinecraftServerJar().toPath(), minecraftServerJar.toPath(), filter); + } + + private void patchJars(Logger logger) throws IOException { + Stopwatch stopwatch = Stopwatch.createStarted(); + logger.lifecycle(":patching jars"); + + PatchProvider patchProvider = getExtension().getPatchProvider(); + patchJars(minecraftClientJar, minecraftClientPatchedJar, patchProvider.clientPatches, "client"); + patchJars(minecraftServerJar, minecraftServerPatchedJar, patchProvider.serverPatches, "server"); + + ThreadingUtils.run(Environment.values(), environment -> { + copyMissingClasses(environment.srgJar.apply(this), environment.patchedSrgJar.apply(this)); + deleteParameterNames(environment.patchedSrgJar.apply(this)); + + if (getExtension().isForgeAndNotOfficial()) { + fixParameterAnnotation(environment.patchedSrgJar.apply(this)); + } + }); + + logger.lifecycle(":patched jars in " + stopwatch.stop()); + } + + @Override + protected void patchJars(File clean, File output, Path patches, String side) throws IOException { + PrintStream previous = System.out; + + try { + System.setOut(new PrintStream(NullOutputStream.NULL_OUTPUT_STREAM)); + } catch (SecurityException ignored) { + // Failed to replace logger filter, just ignore + } + + FG2TaskApplyBinPatches.doTask(getProject(), clean.getAbsoluteFile(), patches.toFile().getAbsoluteFile(), output.getAbsoluteFile(), side); + + try { + System.setOut(previous); + } catch (SecurityException ignored) { + // Failed to replace logger filter, just ignore + } + } + + @Override + protected void mergeJars(Logger logger) throws Exception { + // FIXME: Hack here: There are no server-only classes so we can just copy the client JAR. + // This will change if upstream Loom adds the possibility for separate projects/source sets per environment. + + logger.lifecycle(":merging jars"); + Set mcLibs = getProject().getConfigurations().getByName(Constants.Configurations.MINECRAFT_DEPENDENCIES).resolve(); + Files.copy(minecraftClientPatchedJar.toPath(), minecraftMergedPatchedJar.toPath()); + + logger.lifecycle(":copying resources"); + + // Copy resources + if (getExtension().isForgeAndNotOfficial()) { + // Copy resources + MinecraftProviderImpl minecraftProvider = getExtension().getMinecraftProvider(); + copyNonClassFiles(minecraftProvider.minecraftClientJar, minecraftMergedPatchedJar); + copyNonClassFiles(minecraftProvider.getMinecraftServerJar(), minecraftMergedPatchedJar); + } + } + + protected void remapPatchedJarToSrg(Logger logger) throws Exception { + getProject().getLogger().lifecycle(":remapping minecraft (TinyRemapper, official -> srg)"); + Path mcInput = minecraftMergedPatchedJar.toPath(); + Path mcOutput = minecraftMergedPatchedSrgJar.toPath(); + + TinyRemapper remapper = buildRemapper(mcInput, "official", "srg"); + + try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(mcOutput).build()) { + InputTag mcTag = remapper.createInputTag(); + remapper.readInputsAsync(mcTag, mcInput).join(); + remapper.apply(outputConsumer, mcTag); + } finally { + remapper.finish(); + } + + logger.lifecycle(":copying resources"); + MinecraftProviderImpl minecraftProvider = getExtension().getMinecraftProvider(); + copyNonClassFiles(minecraftProvider.minecraftClientJar, mcOutput.toFile()); + copyNonClassFiles(minecraftProvider.getMinecraftServerJar(), mcOutput.toFile()); + } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg3/MinecraftPatchedProviderFG3.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg3/MinecraftPatchedProviderFG3.java index 702441704..65a0c66a5 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg3/MinecraftPatchedProviderFG3.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg3/MinecraftPatchedProviderFG3.java @@ -24,18 +24,6 @@ package net.fabricmc.loom.configuration.providers.forge.fg3; -import com.google.common.base.Stopwatch; -import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl; -import net.fabricmc.loom.configuration.providers.forge.MinecraftPatchedProvider; -import net.fabricmc.loom.configuration.providers.forge.PatchProvider; -import net.fabricmc.loom.util.Constants; -import net.fabricmc.loom.util.ThreadingUtils; -import net.fabricmc.loom.util.srg.SpecialSourceExecutor; -import net.minecraftforge.binarypatcher.ConsoleTool; -import org.apache.commons.io.output.NullOutputStream; -import org.gradle.api.Project; -import org.gradle.api.logging.Logger; - import java.io.File; import java.io.IOException; import java.io.PrintStream; @@ -46,6 +34,19 @@ import java.util.Set; import java.util.function.Function; +import com.google.common.base.Stopwatch; +import net.minecraftforge.binarypatcher.ConsoleTool; +import org.apache.commons.io.output.NullOutputStream; +import org.gradle.api.Project; +import org.gradle.api.logging.Logger; + +import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl; +import net.fabricmc.loom.configuration.providers.forge.MinecraftPatchedProvider; +import net.fabricmc.loom.configuration.providers.forge.PatchProvider; +import net.fabricmc.loom.util.Constants; +import net.fabricmc.loom.util.ThreadingUtils; +import net.fabricmc.loom.util.srg.SpecialSourceExecutor; + public class MinecraftPatchedProviderFG3 extends MinecraftPatchedProvider { // Step 1: Remap Minecraft to SRG (global) private File minecraftClientSrgJar; @@ -178,17 +179,17 @@ private Path getToSrgMappings() throws IOException { public enum Environment { CLIENT(provider -> provider.minecraftClientSrgJar, - provider -> provider.minecraftClientPatchedSrgJar + provider -> provider.minecraftClientPatchedSrgJar ), SERVER(provider -> provider.minecraftServerSrgJar, - provider -> provider.minecraftServerPatchedSrgJar + provider -> provider.minecraftServerPatchedSrgJar ); final Function srgJar; public final Function patchedSrgJar; Environment(Function srgJar, - Function patchedSrgJar) { + Function patchedSrgJar) { this.srgJar = srgJar; this.patchedSrgJar = patchedSrgJar; } @@ -229,9 +230,9 @@ protected void patchJars(File clean, File output, Path patches, String side) thr } ConsoleTool.main(new String[] { - "--clean", clean.getAbsolutePath(), - "--output", output.getAbsolutePath(), - "--apply", patches.toAbsolutePath().toString() + "--clean", clean.getAbsolutePath(), + "--output", output.getAbsolutePath(), + "--apply", patches.toAbsolutePath().toString() }); try { @@ -246,7 +247,6 @@ protected void mergeJars(Logger logger) throws Exception { // FIXME: Hack here: There are no server-only classes so we can just copy the client JAR. // This will change if upstream Loom adds the possibility for separate projects/source sets per environment. - Files.copy(minecraftClientPatchedSrgJar.toPath(), minecraftMergedPatchedSrgJar.toPath()); logger.lifecycle(":copying resources"); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMappedProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMappedProvider.java index 5a0bf03f6..3dbd79298 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMappedProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMappedProvider.java @@ -342,11 +342,21 @@ public void visit(int version, int access, String name, String signature, String if (forge != null) { OutputRemappingHandler.remap(remapper, forge.assets, outputForge, null, forgeTag); - //FG2 - remove binpatches for the dev environment - try (FileSystem fs = FileSystems.newFileSystem(outputForge, Map.of("create", "false"))) { - Path binpatches = fs.getPath("binpatches.pack.lzma"); - if (Files.exists(binpatches)) { - Files.delete(binpatches); + if (getExtension().getForgeProvider().isFG2()) { + //FG2 - remove binpatches for the dev environment + try (FileSystem fs = FileSystems.newFileSystem(outputForge, Map.of("create", "false"))) { + Path binpatches = fs.getPath("binpatches.pack.lzma"); + + if (Files.exists(binpatches)) { + Files.delete(binpatches); + } + + //TODO: FIXME, hack to remove forge trying to transform class names for fg2 dev launch + Path deobfTransf = fs.getPath("net/minecraftforge/fml/common/asm/transformers/DeobfuscationTransformer.class"); + + if (Files.exists(deobfTransf)) { + Files.delete(deobfTransf); + } } } } diff --git a/src/main/java/net/fabricmc/loom/util/srg/MCPReader.java b/src/main/java/net/fabricmc/loom/util/srg/MCPReader.java index 3e2729813..008a88966 100644 --- a/src/main/java/net/fabricmc/loom/util/srg/MCPReader.java +++ b/src/main/java/net/fabricmc/loom/util/srg/MCPReader.java @@ -67,12 +67,12 @@ public class MCPReader { private final Path intermediaryTinyPath; private final Path srgTsrgPath; - private final boolean isSrg; + private final boolean isLegacySrg; - public MCPReader(Path intermediaryTinyPath, Path srgTsrgPath, boolean isSrg) { + public MCPReader(Path intermediaryTinyPath, Path srgTsrgPath, boolean isLegacySrg) { this.intermediaryTinyPath = intermediaryTinyPath; this.srgTsrgPath = srgTsrgPath; - this.isSrg = isSrg; + this.isLegacySrg = isLegacySrg; } public TinyFile read(Path mcpJar) throws IOException { @@ -196,7 +196,8 @@ private Map readSrg() throws IOException { readTsrg2(tokens, content); } else { MappingSet mappingSet; - if (isSrg) { + + if (isLegacySrg) { mappingSet = new SrgReader(new StringReader(content)).read(); } else { mappingSet = new TSrgReader(new StringReader(content)).read(); @@ -227,13 +228,13 @@ private void readTsrg2(Map tokens, String content) throws I for (MappingTree.MethodMapping methodDef : classDef.getMethods()) { tokens.put(MemberToken.ofMethod(ofClass, methodDef.getName(obfIndex), methodDef.getDesc(obfIndex)), - methodDef.getName(srgIndex)); + methodDef.getName(srgIndex)); } } } private void injectMcp(Path mcpJar, Map intermediaryToSrgMap, Map intermediaryToDocsMap, Map> intermediaryToParamsMap) - throws IOException, CsvValidationException { + throws IOException, CsvValidationException { Map> srgToIntermediary = inverseMap(intermediaryToSrgMap); Map> simpleSrgToIntermediary = new HashMap<>(); Pattern methodPattern = Pattern.compile("(func_\\d*)_.*"); @@ -346,10 +347,10 @@ private void appendClass(Map tokens, ClassMapping cla } private record MemberToken( - TokenType type, - @Nullable MCPReader.MemberToken owner, - String name, - @Nullable String descriptor + TokenType type, + @Nullable MCPReader.MemberToken owner, + String name, + @Nullable String descriptor ) { static MemberToken ofClass(String name) { return new MemberToken(TokenType.CLASS, null, name, null); diff --git a/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java b/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java index de1756cc3..e5026c480 100644 --- a/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java +++ b/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java @@ -24,16 +24,6 @@ package net.fabricmc.loom.util.srg; -import com.google.common.base.Stopwatch; -import net.fabricmc.loom.LoomGradleExtension; -import net.fabricmc.loom.configuration.providers.forge.McpConfigProvider.RemapAction; -import net.fabricmc.loom.util.FileSystemUtil; -import net.fabricmc.loom.util.ThreadingUtils; -import org.apache.commons.io.output.NullOutputStream; -import org.gradle.api.Project; -import org.gradle.api.logging.LogLevel; -import org.gradle.api.logging.configuration.ShowStacktrace; - import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; @@ -44,6 +34,17 @@ import java.util.Set; import java.util.stream.Collectors; +import com.google.common.base.Stopwatch; +import org.apache.commons.io.output.NullOutputStream; +import org.gradle.api.Project; +import org.gradle.api.logging.LogLevel; +import org.gradle.api.logging.configuration.ShowStacktrace; + +import net.fabricmc.loom.LoomGradleExtension; +import net.fabricmc.loom.configuration.providers.forge.McpConfigProvider.RemapAction; +import net.fabricmc.loom.util.FileSystemUtil; +import net.fabricmc.loom.util.ThreadingUtils; + public class SpecialSourceExecutor { private static String trimLeadingSlash(String string) { if (string.startsWith(File.separator)) { @@ -56,7 +57,6 @@ private static String trimLeadingSlash(String string) { } public static void stripJar(Project project, Path inJar, Path outJar, Set filter) throws IOException { - Stopwatch stopwatch = Stopwatch.createStarted(); int count = 0; @@ -95,11 +95,11 @@ public static void stripJar(Project project, Path inJar, Path outJar, Set mcLibs, Path officialJar, Path mappings) - throws Exception { + throws Exception { Set filter = Files.readAllLines(mappings, StandardCharsets.UTF_8).stream() - .filter(s -> !s.startsWith("\t")) - .map(s -> s.split(" ")[0] + ".class") - .collect(Collectors.toSet()); + .filter(s -> !s.startsWith("\t")) + .map(s -> s.split(" ")[0] + ".class") + .collect(Collectors.toSet()); LoomGradleExtension extension = LoomGradleExtension.get(project.getProject()); Path stripped = extension.getFiles().getProjectBuildCache().toPath().resolve(officialJar.getFileName().toString().substring(0, officialJar.getFileName().toString().length() - 4) + "-filtered.jar"); @@ -107,7 +107,6 @@ public static Path produceSrgJar(RemapAction remapAction, Project project, Strin stripJar(project, officialJar, stripped, filter); - Path output = extension.getFiles().getProjectBuildCache().toPath().resolve(officialJar.getFileName().toString().substring(0, officialJar.getFileName().toString().length() - 4) + "-srg-output.jar"); Files.deleteIfExists(output); Stopwatch stopwatch = Stopwatch.createStarted(); @@ -127,7 +126,7 @@ public static Path produceSrgJar(RemapAction remapAction, Project project, Strin // if running with INFO or DEBUG logging if (project.getGradle().getStartParameter().getShowStacktrace() != ShowStacktrace.INTERNAL_EXCEPTIONS - || project.getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) < 0) { + || project.getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) < 0) { spec.setStandardOutput(System.out); spec.setErrorOutput(System.err); } else { diff --git a/src/main/java/net/fabricmc/loom/util/srg/SrgMerger.java b/src/main/java/net/fabricmc/loom/util/srg/SrgMerger.java index 5025e0c98..176ea8f1a 100644 --- a/src/main/java/net/fabricmc/loom/util/srg/SrgMerger.java +++ b/src/main/java/net/fabricmc/loom/util/srg/SrgMerger.java @@ -44,7 +44,6 @@ import com.google.common.base.MoreObjects; import com.google.common.base.Stopwatch; -import net.fabricmc.mappingio.format.SrgReader; import org.apache.commons.io.IOUtils; import org.gradle.api.logging.Logger; import org.jetbrains.annotations.Nullable; @@ -53,6 +52,7 @@ import net.fabricmc.mappingio.FlatMappingVisitor; import net.fabricmc.mappingio.MappingReader; import net.fabricmc.mappingio.adapter.RegularAsFlatMappingVisitor; +import net.fabricmc.mappingio.format.SrgReader; import net.fabricmc.mappingio.format.Tiny2Writer; import net.fabricmc.mappingio.format.TsrgReader; import net.fabricmc.mappingio.tree.MappingTree; @@ -75,11 +75,11 @@ public final class SrgMerger { private final boolean lenient; private final Set methodSrgNames = new HashSet<>(); - private final boolean isSrg; + private final boolean isLegacySrg; - public SrgMerger(Logger logger, Path srg, @Nullable Supplier mojmap, Path tiny, boolean lenient, boolean isSrg) throws IOException { + public SrgMerger(Logger logger, Path srg, @Nullable Supplier mojmap, Path tiny, boolean lenient, boolean isLegacySrg) throws IOException { this.logger = logger; - this.isSrg = isSrg; + this.isLegacySrg = isLegacySrg; this.srg = readSrg(srg, mojmap); this.src = new MemoryMappingTree(); this.output = new MemoryMappingTree(); @@ -123,10 +123,10 @@ public MemoryMappingTree merge() throws IOException { * @throws MappingException if the input tiny tree's default namespace is not 'official' * or if an element mentioned in the SRG file does not have tiny mappings */ - public static void mergeSrg(Logger logger, @Nullable Supplier mojmap, Path srg, Path tiny, Path out, boolean lenient, boolean isSrg) - throws IOException, MappingException { + public static void mergeSrg(Logger logger, @Nullable Supplier mojmap, Path srg, Path tiny, Path out, boolean lenient, boolean isLegacySrg) + throws IOException, MappingException { Stopwatch stopwatch = Stopwatch.createStarted(); - MemoryMappingTree tree = mergeSrg(logger, mojmap, srg, tiny, lenient, isSrg); + MemoryMappingTree tree = mergeSrg(logger, mojmap, srg, tiny, lenient, isLegacySrg); try (Tiny2Writer writer = new Tiny2Writer(Files.newBufferedWriter(out), false)) { tree.accept(writer); @@ -149,9 +149,9 @@ public static void mergeSrg(Logger logger, @Nullable Supplier mojmap, Path * @throws MappingException if the input tiny tree's default namespace is not 'official' * or if an element mentioned in the SRG file does not have tiny mappings */ - public static MemoryMappingTree mergeSrg(Logger logger, @Nullable Supplier mojmap, Path srg, Path tiny, boolean lenient, boolean isSrg) - throws IOException, MappingException { - return new SrgMerger(logger, srg, mojmap, tiny, lenient, isSrg).merge(); + public static MemoryMappingTree mergeSrg(Logger logger, @Nullable Supplier mojmap, Path srg, Path tiny, boolean lenient, boolean isLegacySrg) + throws IOException, MappingException { + return new SrgMerger(logger, srg, mojmap, tiny, lenient, isLegacySrg).merge(); } private MemoryMappingTree readSrg(Path srg, @Nullable Supplier mojmap) throws IOException { @@ -163,11 +163,13 @@ private MemoryMappingTree readSrg(Path srg, @Nullable Supplier mojmap) thr } MemoryMappingTree tsrg = new MemoryMappingTree(); - if (isSrg) { + + if (isLegacySrg) { SrgReader.read(new StringReader(content), tsrg); } else { TsrgReader.read(new StringReader(content), tsrg); } + return tsrg; } } @@ -216,8 +218,8 @@ private void classToTiny(MappingTree.ClassMapping klass) throws IOException { } List classNames = CollectionUtil.map( - output.getDstNamespaces(), - namespace -> "srg".equals(namespace) ? srg : classDef.getName(namespace) + output.getDstNamespaces(), + namespace -> "srg".equals(namespace) ? srg : classDef.getName(namespace) ); flatOutput.visitClass(obf, classNames.toArray(new String[0])); @@ -228,8 +230,8 @@ private void classToTiny(MappingTree.ClassMapping klass) throws IOException { for (MappingTree.MethodMapping method : klass.getMethods()) { MappingTree.MethodMapping def = CollectionUtil.find( - classDef.getMethods(), - m -> m.getName("official").equals(method.getSrcName()) && m.getDesc("official").equals(method.getSrcDesc()) + classDef.getMethods(), + m -> m.getName("official").equals(method.getSrcName()) && m.getDesc("official").equals(method.getSrcDesc()) ).orElse(null); String methodSrgName = method.getDstName(0); @@ -244,8 +246,8 @@ private void classToTiny(MappingTree.ClassMapping klass) throws IOException { postProcesses.add(() -> { if (!methodSrgNames.contains(methodSrgName)) { List methodNames = CollectionUtil.map( - output.getDstNamespaces(), - namespace -> "srg".equals(namespace) ? methodSrgName : method.getSrcName() + output.getDstNamespaces(), + namespace -> "srg".equals(namespace) ? methodSrgName : method.getSrcName() ); try { @@ -280,8 +282,8 @@ private void classToTiny(MappingTree.ClassMapping klass) throws IOException { } MappingTree.MethodMapping method = CollectionUtil.find( - klass.getMethods(), - m -> m.getSrcName().equals(def.getName("official")) && m.getSrcDesc().equals(def.getDesc("official")) + klass.getMethods(), + m -> m.getSrcName().equals(def.getName("official")) && m.getSrcDesc().equals(def.getDesc("official")) ).orElse(null); if (method == null) { @@ -296,15 +298,15 @@ private void classToTiny(MappingTree.ClassMapping klass) throws IOException { for (MappingTree.FieldMapping field : klass.getFields()) { MappingTree.FieldMapping def = CollectionUtil.find( - classDef.getFields(), - f -> f.getName("official").equals(field.getSrcName()) + classDef.getFields(), + f -> f.getName("official").equals(field.getSrcName()) ).orElse(nullOrThrow(() -> new MappingException("Missing field: " + field.getSrcName() + " (srg: " + field.getDstName(0) + ")"))); if (def == null) { if (tryMatchRegardlessSrgsField(obf, field)) { List fieldNames = CollectionUtil.map( - output.getDstNamespaces(), - namespace -> "srg".equals(namespace) ? field.getDstName(0) : field.getSrcName() + output.getDstNamespaces(), + namespace -> "srg".equals(namespace) ? field.getDstName(0) : field.getSrcName() ); flatOutput.visitField(obf, field.getSrcName(), field.getSrcDesc(), fieldNames.toArray(new String[0])); @@ -314,8 +316,8 @@ private void classToTiny(MappingTree.ClassMapping klass) throws IOException { } List fieldNames = CollectionUtil.map( - output.getDstNamespaces(), - namespace -> "srg".equals(namespace) ? field.getDstName(0) : def.getName(namespace) + output.getDstNamespaces(), + namespace -> "srg".equals(namespace) ? field.getDstName(0) : def.getName(namespace) ); flatOutput.visitField(obf, def.getName("official"), def.getDesc("official"), fieldNames.toArray(new String[0])); @@ -327,15 +329,15 @@ private void classToTiny(MappingTree.ClassMapping klass) throws IOException { } private void methodToTiny(String obfClassName, @Nullable MappingTree.MethodMapping srgMethod, @Nullable String srgMethodName, MappingTree.MethodMapping actualMethod) - throws IOException { + throws IOException { if (srgMethod != null && srgMethodName != null) { srgMethodName = srgMethod.getDstName(0); } String finalSrgMethodName = srgMethodName; List methodNames = CollectionUtil.map( - output.getDstNamespaces(), - namespace -> "srg".equals(namespace) ? finalSrgMethodName : actualMethod.getName(namespace) + output.getDstNamespaces(), + namespace -> "srg".equals(namespace) ? finalSrgMethodName : actualMethod.getName(namespace) ); flatOutput.visitMethod(obfClassName, actualMethod.getName("official"), actualMethod.getDesc("official"), methodNames.toArray(new String[0])); @@ -348,8 +350,8 @@ private void methodToTiny(String obfClassName, @Nullable MappingTree.MethodMappi MappingTree.MethodArgMapping srgArg = srgMethod != null ? srgMethod.getArg(arg.getArgPosition(), arg.getLvIndex(), arg.getName("official")) : null; String srgName = srgArg != null ? srgArg.getDstName(0) : null; List argNames = CollectionUtil.map( - output.getDstNamespaces(), - namespace -> "srg".equals(namespace) ? srgName : arg.getName(namespace) + output.getDstNamespaces(), + namespace -> "srg".equals(namespace) ? srgName : arg.getName(namespace) ); flatOutput.visitMethodArg(obfClassName, actualMethod.getName("official"), actualMethod.getDesc("official"), arg.getArgPosition(), arg.getLvIndex(), arg.getName("official"), argNames.toArray(new String[0])); @@ -363,8 +365,8 @@ private void methodToTiny(String obfClassName, @Nullable MappingTree.MethodMappi MappingTree.MethodVarMapping srgVar = srgMethod != null ? srgMethod.getVar(var.getLvtRowIndex(), var.getLvIndex(), var.getStartOpIdx(), var.getName("official")) : null; String srgName = srgVar != null ? srgVar.getDstName(0) : null; List varNames = CollectionUtil.map( - output.getDstNamespaces(), - namespace -> "srg".equals(namespace) ? srgName : var.getName(namespace) + output.getDstNamespaces(), + namespace -> "srg".equals(namespace) ? srgName : var.getName(namespace) ); flatOutput.visitMethodVar(obfClassName, actualMethod.getName("official"), actualMethod.getDesc("official"), var.getLvtRowIndex(), var.getLvIndex(), var.getStartOpIdx(), var.getName("official"), varNames.toArray(new String[0])); From 9e81355adbd21c0fdd8a073c6ceaf667a766f70c Mon Sep 17 00:00:00 2001 From: Wagyourtail Date: Sun, 12 Dec 2021 08:56:45 -0700 Subject: [PATCH 17/28] get fg2 dev launching (fix at's) --- .../forge/MinecraftPatchedProvider.java | 65 +++++++------------ .../fg2/MinecraftPatchedProviderFG2.java | 3 +- .../fg3/MinecraftPatchedProviderFG3.java | 2 +- 3 files changed, 26 insertions(+), 44 deletions(-) diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java index 3608a4101..34973346a 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java @@ -99,6 +99,7 @@ public abstract class MinecraftPatchedProvider extends DependencyProvider { private static final String LOOM_PATCH_VERSION_KEY = "Loom-Patch-Version"; private static final String CURRENT_LOOM_PATCH_VERSION = "5"; private static final String NAME_MAPPING_SERVICE_PATH = "/inject/META-INF/services/cpw.mods.modlauncher.api.INameMappingService"; + protected final String accessTransformerPath; // step 3 - fg2: srg transform, fg3: merge protected File minecraftMergedPatchedSrgJar; @@ -117,8 +118,9 @@ public abstract class MinecraftPatchedProvider extends DependencyProvider { protected boolean filesDirty = false; protected Path mcpConfigMappings; - protected MinecraftPatchedProvider(Project project) { + protected MinecraftPatchedProvider(Project project, String accessTransformerPath) { super(project); + this.accessTransformerPath = accessTransformerPath; } @Override @@ -193,8 +195,7 @@ public void initFiles() throws IOException { } public void testCleanAllCaches() throws IOException { - if (isRefreshDeps() || Stream.of(getGlobalCaches()).anyMatch(((Predicate) File::exists).negate()) - || !isPatchedJarUpToDate(minecraftMergedPatchedAtJar)) { + if (isRefreshDeps() || Stream.of(getGlobalCaches()).anyMatch(((Predicate) File::exists).negate()) || !isPatchedJarUpToDate(minecraftMergedPatchedAtJar)) { cleanAllCache(); } else if (atDirty || Stream.of(getProjectCache()).anyMatch(((Predicate) File::exists).negate())) { cleanProjectCache(); @@ -267,9 +268,7 @@ public void cleanAllCache() { } protected File[] getGlobalCaches() { - File[] files = { - minecraftClientExtra - }; + File[] files = {minecraftClientExtra}; if (forgeMergedJar != null) { Arrays.copyOf(files, files.length + 1); @@ -286,10 +285,7 @@ public void cleanProjectCache() { } protected File[] getProjectCache() { - return new File[] { - minecraftMergedPatchedSrgAtJar, - minecraftMergedPatchedAtJar - }; + return new File[] {minecraftMergedPatchedSrgAtJar, minecraftMergedPatchedAtJar}; } private void writeAtHash() throws IOException { @@ -311,25 +307,21 @@ private byte[] getProjectAtsHash() throws IOException { private void walkFileSystems(File source, File target, Predicate filter, Function> toWalk, FsPathConsumer action) throws IOException { - try (FileSystemUtil.Delegate sourceFs = FileSystemUtil.getJarFileSystem(source, false); - FileSystemUtil.Delegate targetFs = FileSystemUtil.getJarFileSystem(target, false)) { + try (FileSystemUtil.Delegate sourceFs = FileSystemUtil.getJarFileSystem(source, false); FileSystemUtil.Delegate targetFs = FileSystemUtil.getJarFileSystem(target, false)) { for (Path sourceDir : toWalk.apply(sourceFs.get())) { Path dir = sourceDir.toAbsolutePath(); if (!Files.exists(dir)) continue; - Files.walk(dir) - .filter(Files::isRegularFile) - .filter(filter) - .forEach(it -> { - boolean root = dir.getParent() == null; - - try { - Path relativeSource = root ? it : dir.relativize(it); - Path targetPath = targetFs.get().getPath(relativeSource.toString()); - action.accept(sourceFs.get(), targetFs.get(), it, targetPath); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - }); + Files.walk(dir).filter(Files::isRegularFile).filter(filter).forEach(it -> { + boolean root = dir.getParent() == null; + + try { + Path relativeSource = root ? it : dir.relativize(it); + Path targetPath = targetFs.get().getPath(relativeSource.toString()); + action.accept(sourceFs.get(), targetFs.get(), it, targetPath); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); } } } @@ -507,7 +499,7 @@ protected void accessTransformForge(Logger logger) throws Exception { args.add(target.getAbsolutePath()); for (File jar : ImmutableList.of(getForgeJar(), getForgeUserdevJar(), minecraftMergedPatchedSrgJar)) { - byte[] atBytes = ZipUtils.unpackNullable(jar.toPath(), Constants.Forge.ACCESS_TRANSFORMER_PATH); + byte[] atBytes = ZipUtils.unpackNullable(jar.toPath(), accessTransformerPath); if (atBytes != null) { File tmpFile = File.createTempFile("at-conf", ".cfg"); @@ -531,8 +523,7 @@ protected void accessTransformForge(Logger logger) throws Exception { spec.setClasspath(classpath); // if running with INFO or DEBUG logging - if (getProject().getGradle().getStartParameter().getShowStacktrace() != ShowStacktrace.INTERNAL_EXCEPTIONS - || getProject().getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) < 0) { + if (getProject().getGradle().getStartParameter().getShowStacktrace() != ShowStacktrace.INTERNAL_EXCEPTIONS || getProject().getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) < 0) { spec.setStandardOutput(System.out); spec.setErrorOutput(System.err); } else { @@ -560,15 +551,7 @@ protected TinyRemapper buildRemapper(Path input, String from, String to) throws Path[] libraries = TinyRemapperHelper.getMinecraftDependencies(getProject()); MemoryMappingTree mappingsWithSrg = getExtension().getMappingsProvider().getMappingsWithSrg(); - TinyRemapper remapper = TinyRemapper.newRemapper() - .logger(getProject().getLogger()::lifecycle) - .logUnknownInvokeDynamic(false) - .withMappings(TinyRemapperHelper.create(mappingsWithSrg, from, to, true)) - .withMappings(InnerClassRemapper.of(InnerClassRemapper.readClassNames(input), mappingsWithSrg, from, to)) - .renameInvalidLocals(true) - .rebuildSourceFilenames(true) - .fixPackageAccess(true) - .build(); + TinyRemapper remapper = TinyRemapper.newRemapper().logger(getProject().getLogger()::lifecycle).logUnknownInvokeDynamic(false).withMappings(TinyRemapperHelper.create(mappingsWithSrg, from, to, true)).withMappings(InnerClassRemapper.of(InnerClassRemapper.readClassNames(input), mappingsWithSrg, from, to)).renameInvalidLocals(true).rebuildSourceFilenames(true).fixPackageAccess(true).build(); if (getProject().getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) < 0) { MappingsProviderVerbose.saveFile(remapper); @@ -596,10 +579,8 @@ protected void remapPatchedJar(Logger logger) throws Exception { TinyRemapper remapper = buildRemapper(mcInput, "srg", "official"); - try ( - OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(mcOutput).build(); - Closeable outputConsumerForge = !splitJars ? () -> { - } : new OutputConsumerPath.Builder(forgeOutput).build()) { + try (OutputConsumerPath outputConsumer = new OutputConsumerPath.Builder(mcOutput).build(); Closeable outputConsumerForge = !splitJars ? () -> { + } : new OutputConsumerPath.Builder(forgeOutput).build()) { outputConsumer.addNonClassFiles(mcInput); InputTag mcTag = remapper.createInputTag(); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/MinecraftPatchedProviderFG2.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/MinecraftPatchedProviderFG2.java index 4ab981e94..7afe4c65b 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/MinecraftPatchedProviderFG2.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/MinecraftPatchedProviderFG2.java @@ -73,7 +73,8 @@ public class MinecraftPatchedProviderFG2 extends MinecraftPatchedProvider { // fields in super public MinecraftPatchedProviderFG2(Project project) { - super(project); + // use AT from forge universal (btw userdev at's are named `forge_at.cfg` if this should be changed) + super(project, "forge_at.cfg"); } @Override diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg3/MinecraftPatchedProviderFG3.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg3/MinecraftPatchedProviderFG3.java index 65a0c66a5..9fb6768a8 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg3/MinecraftPatchedProviderFG3.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg3/MinecraftPatchedProviderFG3.java @@ -68,7 +68,7 @@ public class MinecraftPatchedProviderFG3 extends MinecraftPatchedProvider { private Path[] mergedMojangTsrg2Files; public MinecraftPatchedProviderFG3(Project project) { - super(project); + super(project, Constants.Forge.ACCESS_TRANSFORMER_PATH); } @Override From e6261e822d7e941eaaec5100220a90cb76799e15 Mon Sep 17 00:00:00 2001 From: Wagyourtail Date: Sun, 12 Dec 2021 09:03:13 -0700 Subject: [PATCH 18/28] deleteIfExists --- .../minecraft/MinecraftMappedProvider.java | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMappedProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMappedProvider.java index 3dbd79298..8c069f9bd 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMappedProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/minecraft/MinecraftMappedProvider.java @@ -343,20 +343,12 @@ public void visit(int version, int access, String name, String signature, String OutputRemappingHandler.remap(remapper, forge.assets, outputForge, null, forgeTag); if (getExtension().getForgeProvider().isFG2()) { - //FG2 - remove binpatches for the dev environment + //FG2 - remove binpatches for the dev environment to work try (FileSystem fs = FileSystems.newFileSystem(outputForge, Map.of("create", "false"))) { - Path binpatches = fs.getPath("binpatches.pack.lzma"); + Files.deleteIfExists(fs.getPath("binpatches.pack.lzma")); - if (Files.exists(binpatches)) { - Files.delete(binpatches); - } - - //TODO: FIXME, hack to remove forge trying to transform class names for fg2 dev launch - Path deobfTransf = fs.getPath("net/minecraftforge/fml/common/asm/transformers/DeobfuscationTransformer.class"); - - if (Files.exists(deobfTransf)) { - Files.delete(deobfTransf); - } + //TODO: FIXME, hack. remove forge trying to transform class names for fg2 dev launch + Files.deleteIfExists(fs.getPath("net/minecraftforge/fml/common/asm/transformers/DeobfuscationTransformer.class")); } } } From 9140b5a4a438fd55989a86fa4852ccaaccd8ccaa Mon Sep 17 00:00:00 2001 From: Wagyourtail Date: Sun, 12 Dec 2021 09:20:48 -0700 Subject: [PATCH 19/28] fix formatting 2 --- .../forge/FieldMigratedMappingsProvider.java | 2 +- .../providers/forge/ForgeUserdevProvider.java | 42 +++++++++++++------ .../providers/forge/McpConfigProvider.java | 4 +- .../providers/forge/SrgProvider.java | 3 +- .../net/fabricmc/loom/util/srg/MCPReader.java | 12 +++--- .../loom/util/srg/SpecialSourceExecutor.java | 9 ++-- .../net/fabricmc/loom/util/srg/SrgMerger.java | 38 ++++++++--------- 7 files changed, 64 insertions(+), 46 deletions(-) diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java index 192a3d35a..f61f21ecb 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java @@ -251,7 +251,7 @@ public FieldVisitor visitField(int access, String name, String descriptor, Strin String fieldIntermediary = fieldDef.getName("intermediary"); String descriptorIntermediary = fieldDef.getDesc("intermediary"); String newDescriptorRemapped = DescriptorRemapper.remapDescriptor(newDescriptor, - clazz -> srgToIntermediary.getOrDefault(clazz, clazz)); + clazz -> srgToIntermediary.getOrDefault(clazz, clazz)); migratedFields.put(new FieldMember(ownerIntermediary, fieldIntermediary), newDescriptorRemapped); getProject().getLogger().info(ownerIntermediary + "#" + fieldIntermediary + ": " + descriptorIntermediary + " -> " + newDescriptorRemapped); } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java index e692d99c8..b6fcbbbf6 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java @@ -265,15 +265,22 @@ public String processTemplates(String string) { // TODO: Look into ways to not hardcode if (key.equals("runtime_classpath")) { - string = runtimeClasspath().stream().map(File::getAbsolutePath).collect(Collectors.joining(File.pathSeparator)); + string = runtimeClasspath().stream() + .map(File::getAbsolutePath) + .collect(Collectors.joining(File.pathSeparator)); } else if (key.equals("minecraft_classpath")) { - string = minecraftClasspath().stream().map(File::getAbsolutePath).collect(Collectors.joining(File.pathSeparator)); + string = minecraftClasspath().stream() + .map(File::getAbsolutePath) + .collect(Collectors.joining(File.pathSeparator)); } else if (key.equals("runtime_classpath_file")) { Path path = getDirectories().getProjectPersistentCache().toPath().resolve("forge_runtime_classpath.txt"); postPopulationScheduler.accept(() -> { try { - Files.writeString(path, runtimeClasspath().stream().map(File::getAbsolutePath).collect(Collectors.joining("\n")), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + Files.writeString(path, runtimeClasspath().stream() + .map(File::getAbsolutePath) + .collect(Collectors.joining("\n")), + StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); } catch (IOException e) { throw new RuntimeException(e); } @@ -285,7 +292,10 @@ public String processTemplates(String string) { postPopulationScheduler.accept(() -> { try { - Files.writeString(path, minecraftClasspath().stream().map(File::getAbsolutePath).collect(Collectors.joining("\n")), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + Files.writeString(path, minecraftClasspath().stream() + .map(File::getAbsolutePath) + .collect(Collectors.joining("\n")), + StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); } catch (IOException e) { throw new RuntimeException(e); } @@ -304,7 +314,10 @@ public String processTemplates(String string) { for (ForgeLocalMod localMod : getExtension().getForge().getLocalMods()) { String sourceSetName = localMod.getName(); - localMod.getSourceSets().flatMap(sourceSet -> Stream.concat(Stream.of(sourceSet.getOutput().getResourcesDir()), sourceSet.getOutput().getClassesDirs().getFiles().stream())).map(File::getAbsolutePath).distinct().map(s -> sourceSetName + "%%" + s).collect(Collectors.toCollection(() -> modClasses)); + localMod.getSourceSets().flatMap(sourceSet -> Stream.concat( + Stream.of(sourceSet.getOutput().getResourcesDir()), + sourceSet.getOutput().getClassesDirs().getFiles().stream()) + ).map(File::getAbsolutePath).distinct().map(s -> sourceSetName + "%%" + s).collect(Collectors.toCollection(() -> modClasses)); } string = String.join(File.pathSeparator, modClasses); @@ -314,13 +327,18 @@ public String processTemplates(String string) { JsonElement element = json.get(key); if (element.isJsonArray()) { - string = StreamSupport.stream(element.getAsJsonArray().spliterator(), false).map(JsonElement::getAsString).flatMap(str -> { - if (str.contains(":")) { - return DependencyDownloader.download(getProject(), str, false, false).getFiles().stream().map(File::getAbsolutePath).filter(dep -> !dep.contains("bootstraplauncher")); // TODO: Hack - } - - return Stream.of(str); - }).collect(Collectors.joining(File.pathSeparator)); + string = StreamSupport.stream(element.getAsJsonArray().spliterator(), false) + .map(JsonElement::getAsString) + .flatMap(str -> { + if (str.contains(":")) { + return DependencyDownloader.download(getProject(), str, false, false).getFiles().stream() + .map(File::getAbsolutePath) + .filter(dep -> !dep.contains("bootstraplauncher")); // TODO: Hack + } + + return Stream.of(str); + }) + .collect(Collectors.joining(File.pathSeparator)); } else { string = element.toString(); } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java index 996b9e814..2b3aa2e2f 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java @@ -181,8 +181,8 @@ public ConfigDefinedRemapAction(Project project, JsonObject json) { .getSingleFile(); this.classpath = DependencyDownloader.download(project, this.name, true, true); this.args = StreamSupport.stream(json.getAsJsonArray("args").spliterator(), false) - .map(JsonElement::getAsString) - .collect(Collectors.toList()); + .map(JsonElement::getAsString) + .collect(Collectors.toList()); for (int i = 1; i < this.args.size(); i++) { if (this.args.get(i).equals("{libraries}")) { this.args.remove(i); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java index 1713c9ca8..01c149cb6 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java @@ -225,7 +225,8 @@ public static Path getMojmapTsrg(Project project, LoomGradleExtension extension) if (Files.notExists(mojmapTsrg) || LoomGradlePlugin.refreshDeps) { try (BufferedWriter writer = Files.newBufferedWriter(mojmapTsrg, StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING)) { - Tsrg2Utils.writeTsrg(visitor -> visitMojmap(visitor, project), MappingsNamespace.NAMED.toString(), false, writer); + Tsrg2Utils.writeTsrg(visitor -> visitMojmap(visitor, project), + MappingsNamespace.NAMED.toString(), false, writer); } } diff --git a/src/main/java/net/fabricmc/loom/util/srg/MCPReader.java b/src/main/java/net/fabricmc/loom/util/srg/MCPReader.java index 008a88966..fd6f5d291 100644 --- a/src/main/java/net/fabricmc/loom/util/srg/MCPReader.java +++ b/src/main/java/net/fabricmc/loom/util/srg/MCPReader.java @@ -228,13 +228,13 @@ private void readTsrg2(Map tokens, String content) throws I for (MappingTree.MethodMapping methodDef : classDef.getMethods()) { tokens.put(MemberToken.ofMethod(ofClass, methodDef.getName(obfIndex), methodDef.getDesc(obfIndex)), - methodDef.getName(srgIndex)); + methodDef.getName(srgIndex)); } } } private void injectMcp(Path mcpJar, Map intermediaryToSrgMap, Map intermediaryToDocsMap, Map> intermediaryToParamsMap) - throws IOException, CsvValidationException { + throws IOException, CsvValidationException { Map> srgToIntermediary = inverseMap(intermediaryToSrgMap); Map> simpleSrgToIntermediary = new HashMap<>(); Pattern methodPattern = Pattern.compile("(func_\\d*)_.*"); @@ -347,10 +347,10 @@ private void appendClass(Map tokens, ClassMapping cla } private record MemberToken( - TokenType type, - @Nullable MCPReader.MemberToken owner, - String name, - @Nullable String descriptor + TokenType type, + @Nullable MCPReader.MemberToken owner, + String name, + @Nullable String descriptor ) { static MemberToken ofClass(String name) { return new MemberToken(TokenType.CLASS, null, name, null); diff --git a/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java b/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java index e5026c480..3cc097beb 100644 --- a/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java +++ b/src/main/java/net/fabricmc/loom/util/srg/SpecialSourceExecutor.java @@ -97,9 +97,9 @@ public static void stripJar(Project project, Path inJar, Path outJar, Set mcLibs, Path officialJar, Path mappings) throws Exception { Set filter = Files.readAllLines(mappings, StandardCharsets.UTF_8).stream() - .filter(s -> !s.startsWith("\t")) - .map(s -> s.split(" ")[0] + ".class") - .collect(Collectors.toSet()); + .filter(s -> !s.startsWith("\t")) + .map(s -> s.split(" ")[0] + ".class") + .collect(Collectors.toSet()); LoomGradleExtension extension = LoomGradleExtension.get(project.getProject()); Path stripped = extension.getFiles().getProjectBuildCache().toPath().resolve(officialJar.getFileName().toString().substring(0, officialJar.getFileName().toString().length() - 4) + "-filtered.jar"); @@ -111,7 +111,6 @@ public static Path produceSrgJar(RemapAction remapAction, Project project, Strin Files.deleteIfExists(output); Stopwatch stopwatch = Stopwatch.createStarted(); - project.getLogger().info(stripped.toString()); List args = remapAction.getArgs(stripped, output, mappings, project.files(mcLibs)); project.getLogger().lifecycle(":remapping minecraft (" + remapAction + ", " + side + ", official -> mojang)"); @@ -126,7 +125,7 @@ public static Path produceSrgJar(RemapAction remapAction, Project project, Strin // if running with INFO or DEBUG logging if (project.getGradle().getStartParameter().getShowStacktrace() != ShowStacktrace.INTERNAL_EXCEPTIONS - || project.getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) < 0) { + || project.getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) < 0) { spec.setStandardOutput(System.out); spec.setErrorOutput(System.err); } else { diff --git a/src/main/java/net/fabricmc/loom/util/srg/SrgMerger.java b/src/main/java/net/fabricmc/loom/util/srg/SrgMerger.java index 176ea8f1a..98d5fab66 100644 --- a/src/main/java/net/fabricmc/loom/util/srg/SrgMerger.java +++ b/src/main/java/net/fabricmc/loom/util/srg/SrgMerger.java @@ -124,7 +124,7 @@ public MemoryMappingTree merge() throws IOException { * or if an element mentioned in the SRG file does not have tiny mappings */ public static void mergeSrg(Logger logger, @Nullable Supplier mojmap, Path srg, Path tiny, Path out, boolean lenient, boolean isLegacySrg) - throws IOException, MappingException { + throws IOException, MappingException { Stopwatch stopwatch = Stopwatch.createStarted(); MemoryMappingTree tree = mergeSrg(logger, mojmap, srg, tiny, lenient, isLegacySrg); @@ -150,7 +150,7 @@ public static void mergeSrg(Logger logger, @Nullable Supplier mojmap, Path * or if an element mentioned in the SRG file does not have tiny mappings */ public static MemoryMappingTree mergeSrg(Logger logger, @Nullable Supplier mojmap, Path srg, Path tiny, boolean lenient, boolean isLegacySrg) - throws IOException, MappingException { + throws IOException, MappingException { return new SrgMerger(logger, srg, mojmap, tiny, lenient, isLegacySrg).merge(); } @@ -218,8 +218,8 @@ private void classToTiny(MappingTree.ClassMapping klass) throws IOException { } List classNames = CollectionUtil.map( - output.getDstNamespaces(), - namespace -> "srg".equals(namespace) ? srg : classDef.getName(namespace) + output.getDstNamespaces(), + namespace -> "srg".equals(namespace) ? srg : classDef.getName(namespace) ); flatOutput.visitClass(obf, classNames.toArray(new String[0])); @@ -230,8 +230,8 @@ private void classToTiny(MappingTree.ClassMapping klass) throws IOException { for (MappingTree.MethodMapping method : klass.getMethods()) { MappingTree.MethodMapping def = CollectionUtil.find( - classDef.getMethods(), - m -> m.getName("official").equals(method.getSrcName()) && m.getDesc("official").equals(method.getSrcDesc()) + classDef.getMethods(), + m -> m.getName("official").equals(method.getSrcName()) && m.getDesc("official").equals(method.getSrcDesc()) ).orElse(null); String methodSrgName = method.getDstName(0); @@ -246,8 +246,8 @@ private void classToTiny(MappingTree.ClassMapping klass) throws IOException { postProcesses.add(() -> { if (!methodSrgNames.contains(methodSrgName)) { List methodNames = CollectionUtil.map( - output.getDstNamespaces(), - namespace -> "srg".equals(namespace) ? methodSrgName : method.getSrcName() + output.getDstNamespaces(), + namespace -> "srg".equals(namespace) ? methodSrgName : method.getSrcName() ); try { @@ -282,8 +282,8 @@ private void classToTiny(MappingTree.ClassMapping klass) throws IOException { } MappingTree.MethodMapping method = CollectionUtil.find( - klass.getMethods(), - m -> m.getSrcName().equals(def.getName("official")) && m.getSrcDesc().equals(def.getDesc("official")) + klass.getMethods(), + m -> m.getSrcName().equals(def.getName("official")) && m.getSrcDesc().equals(def.getDesc("official")) ).orElse(null); if (method == null) { @@ -298,15 +298,15 @@ private void classToTiny(MappingTree.ClassMapping klass) throws IOException { for (MappingTree.FieldMapping field : klass.getFields()) { MappingTree.FieldMapping def = CollectionUtil.find( - classDef.getFields(), - f -> f.getName("official").equals(field.getSrcName()) + classDef.getFields(), + f -> f.getName("official").equals(field.getSrcName()) ).orElse(nullOrThrow(() -> new MappingException("Missing field: " + field.getSrcName() + " (srg: " + field.getDstName(0) + ")"))); if (def == null) { if (tryMatchRegardlessSrgsField(obf, field)) { List fieldNames = CollectionUtil.map( - output.getDstNamespaces(), - namespace -> "srg".equals(namespace) ? field.getDstName(0) : field.getSrcName() + output.getDstNamespaces(), + namespace -> "srg".equals(namespace) ? field.getDstName(0) : field.getSrcName() ); flatOutput.visitField(obf, field.getSrcName(), field.getSrcDesc(), fieldNames.toArray(new String[0])); @@ -316,8 +316,8 @@ private void classToTiny(MappingTree.ClassMapping klass) throws IOException { } List fieldNames = CollectionUtil.map( - output.getDstNamespaces(), - namespace -> "srg".equals(namespace) ? field.getDstName(0) : def.getName(namespace) + output.getDstNamespaces(), + namespace -> "srg".equals(namespace) ? field.getDstName(0) : def.getName(namespace) ); flatOutput.visitField(obf, def.getName("official"), def.getDesc("official"), fieldNames.toArray(new String[0])); @@ -329,15 +329,15 @@ private void classToTiny(MappingTree.ClassMapping klass) throws IOException { } private void methodToTiny(String obfClassName, @Nullable MappingTree.MethodMapping srgMethod, @Nullable String srgMethodName, MappingTree.MethodMapping actualMethod) - throws IOException { + throws IOException { if (srgMethod != null && srgMethodName != null) { srgMethodName = srgMethod.getDstName(0); } String finalSrgMethodName = srgMethodName; List methodNames = CollectionUtil.map( - output.getDstNamespaces(), - namespace -> "srg".equals(namespace) ? finalSrgMethodName : actualMethod.getName(namespace) + output.getDstNamespaces(), + namespace -> "srg".equals(namespace) ? finalSrgMethodName : actualMethod.getName(namespace) ); flatOutput.visitMethod(obfClassName, actualMethod.getName("official"), actualMethod.getDesc("official"), methodNames.toArray(new String[0])); From fed6b7d9b1cb46b18af73aba57a202c7198e2df8 Mon Sep 17 00:00:00 2001 From: Wagyourtail Date: Sun, 12 Dec 2021 09:28:28 -0700 Subject: [PATCH 20/28] fix formatting minimizing diffs 3rd pass --- .../forge/FieldMigratedMappingsProvider.java | 4 ++-- .../providers/forge/ForgeUserdevProvider.java | 3 +-- .../providers/forge/McpConfigProvider.java | 2 +- .../providers/forge/SrgProvider.java | 15 +++++++++++++-- .../net/fabricmc/loom/util/srg/SrgMerger.java | 8 ++++---- 5 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java index f61f21ecb..9651769af 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/FieldMigratedMappingsProvider.java @@ -215,8 +215,8 @@ public FieldVisitor visitField(int access, String name, String descriptor, Strin completer.add(() -> { byte[] bytes = Files.readAllBytes(fsPath); new ClassReader(bytes).accept( - visitor, - ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES + visitor, + ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES ); }); } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java index b6fcbbbf6..9771a91aa 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java @@ -60,10 +60,10 @@ import org.gradle.api.file.FileSystemLocation; import org.gradle.api.provider.Provider; -import net.fabricmc.loom.api.ForgeLocalMod; import net.fabricmc.loom.configuration.DependencyProvider; import net.fabricmc.loom.configuration.ide.RunConfigSettings; import net.fabricmc.loom.configuration.launch.LaunchProviderSettings; +import net.fabricmc.loom.api.ForgeLocalMod; import net.fabricmc.loom.util.Constants; import net.fabricmc.loom.util.DependencyDownloader; import net.fabricmc.loom.util.FileSystemUtil; @@ -72,7 +72,6 @@ public class ForgeUserdevProvider extends DependencyProvider { private File userdevJar; private JsonObject json; - private Consumer postPopulationScheduler; public ForgeUserdevProvider(Project project) { diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java index 2b3aa2e2f..f79149180 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java @@ -178,7 +178,7 @@ public ConfigDefinedRemapAction(Project project, JsonObject json) { this.project = project; this.name = json.get("version").getAsString(); this.mainClasspath = DependencyDownloader.download(project, this.name, false, true) - .getSingleFile(); + .getSingleFile(); this.classpath = DependencyDownloader.download(project, this.name, true, true); this.args = StreamSupport.stream(json.getAsJsonArray("args").spliterator(), false) .map(JsonElement::getAsString) diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java index 01c149cb6..9257add9e 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/SrgProvider.java @@ -117,7 +117,17 @@ public void provide(DependencyInfo dependency, Consumer postPopulation Files.deleteIfExists(mergedMojangRaw); Files.deleteIfExists(mergedMojang); - net.minecraftforge.installertools.ConsoleTool.main(new String[] {"--task", "MERGE_MAPPING", "--left", getSrg().toAbsolutePath().toString(), "--right", getMojmapTsrg2(getProject(), getExtension()).toAbsolutePath().toString(), "--classes", "--output", mergedMojangRaw.toAbsolutePath().toString()}); + net.minecraftforge.installertools.ConsoleTool.main(new String[] { + "--task", + "MERGE_MAPPING", + "--left", + getSrg().toAbsolutePath().toString(), + "--right", + getMojmapTsrg2(getProject(), getExtension()).toAbsolutePath().toString(), + "--classes", + "--output", + mergedMojangRaw.toAbsolutePath().toString() + }); MemoryMappingTree tree = new MemoryMappingTree(); MappingReader.read(new StringReader(FileUtils.readFileToString(mergedMojangRaw.toFile(), StandardCharsets.UTF_8)), new FieldDescWrappingVisitor(tree)); @@ -177,7 +187,8 @@ public boolean visitField(String srcName, String srcDesc) throws IOException { return super.visitField(srcName, srcDesc); } - private record FieldKey(String owner, String name) {} + private record FieldKey(String owner, String name) { + } } private void init(String version) { diff --git a/src/main/java/net/fabricmc/loom/util/srg/SrgMerger.java b/src/main/java/net/fabricmc/loom/util/srg/SrgMerger.java index 98d5fab66..d0f02428c 100644 --- a/src/main/java/net/fabricmc/loom/util/srg/SrgMerger.java +++ b/src/main/java/net/fabricmc/loom/util/srg/SrgMerger.java @@ -350,8 +350,8 @@ private void methodToTiny(String obfClassName, @Nullable MappingTree.MethodMappi MappingTree.MethodArgMapping srgArg = srgMethod != null ? srgMethod.getArg(arg.getArgPosition(), arg.getLvIndex(), arg.getName("official")) : null; String srgName = srgArg != null ? srgArg.getDstName(0) : null; List argNames = CollectionUtil.map( - output.getDstNamespaces(), - namespace -> "srg".equals(namespace) ? srgName : arg.getName(namespace) + output.getDstNamespaces(), + namespace -> "srg".equals(namespace) ? srgName : arg.getName(namespace) ); flatOutput.visitMethodArg(obfClassName, actualMethod.getName("official"), actualMethod.getDesc("official"), arg.getArgPosition(), arg.getLvIndex(), arg.getName("official"), argNames.toArray(new String[0])); @@ -365,8 +365,8 @@ private void methodToTiny(String obfClassName, @Nullable MappingTree.MethodMappi MappingTree.MethodVarMapping srgVar = srgMethod != null ? srgMethod.getVar(var.getLvtRowIndex(), var.getLvIndex(), var.getStartOpIdx(), var.getName("official")) : null; String srgName = srgVar != null ? srgVar.getDstName(0) : null; List varNames = CollectionUtil.map( - output.getDstNamespaces(), - namespace -> "srg".equals(namespace) ? srgName : var.getName(namespace) + output.getDstNamespaces(), + namespace -> "srg".equals(namespace) ? srgName : var.getName(namespace) ); flatOutput.visitMethodVar(obfClassName, actualMethod.getName("official"), actualMethod.getDesc("official"), var.getLvtRowIndex(), var.getLvIndex(), var.getStartOpIdx(), var.getName("official"), varNames.toArray(new String[0])); From 7554cf801535d2feb39d6021ef08032083ec77f9 Mon Sep 17 00:00:00 2001 From: Wagyourtail Date: Sun, 12 Dec 2021 09:30:18 -0700 Subject: [PATCH 21/28] instead of else, return early for smaller diff --- .../providers/forge/ForgeUserdevProvider.java | 116 +++++++++--------- 1 file changed, 59 insertions(+), 57 deletions(-) diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java index 9771a91aa..730094586 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java @@ -161,77 +161,79 @@ public void provide(DependencyInfo dependency, Consumer postPopulation }); } } - } else { - getProject().getLogger().info("FG3+ Userdev"); - - addDependency(json.get("mcp").getAsString(), Constants.Configurations.MCP_CONFIG); - addDependency(json.get("mcp").getAsString(), Constants.Configurations.SRG); - addDependency(json.get("universal").getAsString(), Constants.Configurations.FORGE_UNIVERSAL); - - for (JsonElement lib : json.get("libraries").getAsJsonArray()) { - Dependency dep = null; - - if (lib.getAsString().startsWith("org.spongepowered:mixin:")) { - if (PropertyUtil.getAndFinalize(getExtension().getForge().getUseCustomMixin())) { - if (lib.getAsString().contains("0.8.2")) { - dep = addDependency("net.fabricmc:sponge-mixin:0.8.2+build.24", Constants.Configurations.FORGE_DEPENDENCIES); - } else { - dep = addDependency("dev.architectury:mixin-patched" + lib.getAsString().substring(lib.getAsString().lastIndexOf(":")) + ".+", Constants.Configurations.FORGE_DEPENDENCIES); - } - } - } - if (dep == null) { - dep = addDependency(lib.getAsString(), Constants.Configurations.FORGE_DEPENDENCIES); - } + return; + } - if (lib.getAsString().split(":").length < 4) { - ((ModuleDependency) dep).attributes(attributes -> { - attributes.attribute(transformed, true); - }); + getProject().getLogger().info("FG3+ Userdev"); + + addDependency(json.get("mcp").getAsString(), Constants.Configurations.MCP_CONFIG); + addDependency(json.get("mcp").getAsString(), Constants.Configurations.SRG); + addDependency(json.get("universal").getAsString(), Constants.Configurations.FORGE_UNIVERSAL); + + for (JsonElement lib : json.get("libraries").getAsJsonArray()) { + Dependency dep = null; + + if (lib.getAsString().startsWith("org.spongepowered:mixin:")) { + if (PropertyUtil.getAndFinalize(getExtension().getForge().getUseCustomMixin())) { + if (lib.getAsString().contains("0.8.2")) { + dep = addDependency("net.fabricmc:sponge-mixin:0.8.2+build.24", Constants.Configurations.FORGE_DEPENDENCIES); + } else { + dep = addDependency("dev.architectury:mixin-patched" + lib.getAsString().substring(lib.getAsString().lastIndexOf(":")) + ".+", Constants.Configurations.FORGE_DEPENDENCIES); + } } } - // TODO: Should I copy the patches from here as well? - // That'd require me to run the "MCP environment" fully up to merging. - for (Map.Entry entry : json.getAsJsonObject("runs").entrySet()) { - LaunchProviderSettings launchSettings = getExtension().getLaunchConfigs().findByName(entry.getKey()); - RunConfigSettings settings = getExtension().getRunConfigs().findByName(entry.getKey()); - JsonObject value = entry.getValue().getAsJsonObject(); + if (dep == null) { + dep = addDependency(lib.getAsString(), Constants.Configurations.FORGE_DEPENDENCIES); + } - if (launchSettings != null) { - launchSettings.evaluateLater(() -> { - if (value.has("args")) { - launchSettings.arg(StreamSupport.stream(value.getAsJsonArray("args").spliterator(), false).map(JsonElement::getAsString).map(this::processTemplates).collect(Collectors.toList())); - } + if (lib.getAsString().split(":").length < 4) { + ((ModuleDependency) dep).attributes(attributes -> { + attributes.attribute(transformed, true); + }); + } + } - if (value.has("props")) { - for (Map.Entry props : value.getAsJsonObject("props").entrySet()) { - String string = processTemplates(props.getValue().getAsString()); + // TODO: Should I copy the patches from here as well? + // That'd require me to run the "MCP environment" fully up to merging. + for (Map.Entry entry : json.getAsJsonObject("runs").entrySet()) { + LaunchProviderSettings launchSettings = getExtension().getLaunchConfigs().findByName(entry.getKey()); + RunConfigSettings settings = getExtension().getRunConfigs().findByName(entry.getKey()); + JsonObject value = entry.getValue().getAsJsonObject(); + + if (launchSettings != null) { + launchSettings.evaluateLater(() -> { + if (value.has("args")) { + launchSettings.arg(StreamSupport.stream(value.getAsJsonArray("args").spliterator(), false).map(JsonElement::getAsString).map(this::processTemplates).collect(Collectors.toList())); + } - launchSettings.property(props.getKey(), string); - } + if (value.has("props")) { + for (Map.Entry props : value.getAsJsonObject("props").entrySet()) { + String string = processTemplates(props.getValue().getAsString()); + + launchSettings.property(props.getKey(), string); } - }); - } + } + }); + } - if (settings != null) { - settings.evaluateLater(() -> { - settings.defaultMainClass(value.getAsJsonPrimitive("main").getAsString()); + if (settings != null) { + settings.evaluateLater(() -> { + settings.defaultMainClass(value.getAsJsonPrimitive("main").getAsString()); - if (value.has("jvmArgs")) { - settings.vmArgs(StreamSupport.stream(value.getAsJsonArray("jvmArgs").spliterator(), false).map(JsonElement::getAsString).map(this::processTemplates).collect(Collectors.toList())); - } + if (value.has("jvmArgs")) { + settings.vmArgs(StreamSupport.stream(value.getAsJsonArray("jvmArgs").spliterator(), false).map(JsonElement::getAsString).map(this::processTemplates).collect(Collectors.toList())); + } - if (value.has("env")) { - for (Map.Entry env : value.getAsJsonObject("env").entrySet()) { - String string = processTemplates(env.getValue().getAsString()); + if (value.has("env")) { + for (Map.Entry env : value.getAsJsonObject("env").entrySet()) { + String string = processTemplates(env.getValue().getAsString()); - settings.envVariables.put(env.getKey(), string); - } + settings.envVariables.put(env.getKey(), string); } - }); - } + } + }); } } } From b69e85bf0d355693c74381009c343a04eb83c7de Mon Sep 17 00:00:00 2001 From: Wagyourtail Date: Sun, 12 Dec 2021 09:34:38 -0700 Subject: [PATCH 22/28] cleanup pass 4 --- .../providers/forge/ForgeUserdevProvider.java | 10 ++++++++-- .../providers/forge/PatchProvider.java | 20 ++++++++++--------- .../mappings/MappingsProviderImpl.java | 4 ++-- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java index 730094586..38fd7603b 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/ForgeUserdevProvider.java @@ -205,7 +205,10 @@ public void provide(DependencyInfo dependency, Consumer postPopulation if (launchSettings != null) { launchSettings.evaluateLater(() -> { if (value.has("args")) { - launchSettings.arg(StreamSupport.stream(value.getAsJsonArray("args").spliterator(), false).map(JsonElement::getAsString).map(this::processTemplates).collect(Collectors.toList())); + launchSettings.arg(StreamSupport.stream(value.getAsJsonArray("args").spliterator(), false) + .map(JsonElement::getAsString) + .map(this::processTemplates) + .collect(Collectors.toList())); } if (value.has("props")) { @@ -223,7 +226,10 @@ public void provide(DependencyInfo dependency, Consumer postPopulation settings.defaultMainClass(value.getAsJsonPrimitive("main").getAsString()); if (value.has("jvmArgs")) { - settings.vmArgs(StreamSupport.stream(value.getAsJsonArray("jvmArgs").spliterator(), false).map(JsonElement::getAsString).map(this::processTemplates).collect(Collectors.toList())); + settings.vmArgs(StreamSupport.stream(value.getAsJsonArray("jvmArgs").spliterator(), false) + .map(JsonElement::getAsString) + .map(this::processTemplates) + .collect(Collectors.toList())); } if (value.has("env")) { diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/PatchProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/PatchProvider.java index 95a05e6dd..3029a7826 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/PatchProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/PatchProvider.java @@ -65,17 +65,19 @@ public void provide(DependencyInfo dependency, Consumer postPopulation Files.copy(fs.getPath("binpatches.pack.lzma"), serverPatches, StandardCopyOption.REPLACE_EXISTING); } } - } else { - if (Files.notExists(clientPatches) || Files.notExists(serverPatches) || isRefreshDeps()) { - getProject().getLogger().info(":extracting forge patches"); - Path installerJar = dependency.resolveFile().orElseThrow(() -> new RuntimeException( - "Could not resolve Forge installer")).toPath(); + return; + } - try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + installerJar.toUri()), ImmutableMap.of("create", false))) { - Files.copy(fs.getPath("data", "client.lzma"), clientPatches, StandardCopyOption.REPLACE_EXISTING); - Files.copy(fs.getPath("data", "server.lzma"), serverPatches, StandardCopyOption.REPLACE_EXISTING); - } + if (Files.notExists(clientPatches) || Files.notExists(serverPatches) || isRefreshDeps()) { + getProject().getLogger().info(":extracting forge patches"); + + Path installerJar = dependency.resolveFile().orElseThrow(() -> new RuntimeException( + "Could not resolve Forge installer")).toPath(); + + try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + installerJar.toUri()), ImmutableMap.of("create", false))) { + Files.copy(fs.getPath("data", "client.lzma"), clientPatches, StandardCopyOption.REPLACE_EXISTING); + Files.copy(fs.getPath("data", "server.lzma"), serverPatches, StandardCopyOption.REPLACE_EXISTING); } } } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java index a9dec9e82..60baf8891 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java @@ -49,8 +49,6 @@ import com.google.common.base.Stopwatch; import com.google.common.net.UrlEscapers; import com.google.gson.JsonObject; -import net.fabricmc.loom.configuration.providers.forge.MinecraftPatchedProvider; -import net.fabricmc.loom.configuration.providers.forge.fg2.MinecraftPatchedProviderFG2; import org.apache.tools.ant.util.StringUtils; import org.gradle.api.Project; import org.gradle.api.artifacts.Configuration; @@ -66,6 +64,8 @@ import net.fabricmc.loom.configuration.processors.JarProcessorManager; import net.fabricmc.loom.configuration.processors.MinecraftProcessedProvider; import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl; +import net.fabricmc.loom.configuration.providers.forge.MinecraftPatchedProvider; +import net.fabricmc.loom.configuration.providers.forge.fg2.MinecraftPatchedProviderFG2; import net.fabricmc.loom.configuration.providers.forge.fg3.MinecraftPatchedProviderFG3; import net.fabricmc.loom.configuration.providers.forge.SrgProvider; import net.fabricmc.loom.configuration.providers.minecraft.MinecraftMappedProvider; From 9b0cacb26c5221f5220624640ecbb35ad67c0850 Mon Sep 17 00:00:00 2001 From: Wagyourtail Date: Sun, 12 Dec 2021 09:49:40 -0700 Subject: [PATCH 23/28] rearrange to minimize diff in refactored class --- .../forge/MinecraftPatchedProvider.java | 412 +++++++++--------- 1 file changed, 206 insertions(+), 206 deletions(-) diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java index 34973346a..d1755e843 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java @@ -123,22 +123,6 @@ protected MinecraftPatchedProvider(Project project, String accessTransformerPath this.accessTransformerPath = accessTransformerPath; } - @Override - public void provide(DependencyInfo dependency, Consumer postPopulationScheduler) throws Exception { - initFiles(); - testCleanAllCaches(); - beginTransform(); - } - - protected abstract void beginTransform() throws Exception; - - public abstract void endTransform() throws Exception; - - @Override - public String getTargetConfig() { - return Constants.Configurations.MINECRAFT; - } - public void initFiles() throws IOException { filesDirty = false; projectAtHash = new File(getDirectories().getProjectPersistentCache(), "at.sha256"); @@ -194,69 +178,15 @@ public void initFiles() throws IOException { minecraftMergedPatchedSrgAtJar = new File(projectDir, "merged-srg-at-patched.jar"); } - public void testCleanAllCaches() throws IOException { - if (isRefreshDeps() || Stream.of(getGlobalCaches()).anyMatch(((Predicate) File::exists).negate()) || !isPatchedJarUpToDate(minecraftMergedPatchedAtJar)) { - cleanAllCache(); - } else if (atDirty || Stream.of(getProjectCache()).anyMatch(((Predicate) File::exists).negate())) { - cleanProjectCache(); - } - } - - public void applyLoomPatchVersion(Path target) throws IOException { - try (FileSystemUtil.Delegate delegate = FileSystemUtil.getJarFileSystem(target, false)) { - Path manifestPath = delegate.get().getPath("META-INF/MANIFEST.MF"); - - Preconditions.checkArgument(Files.exists(manifestPath), "META-INF/MANIFEST.MF does not exist in patched srg jar!"); - Manifest manifest = new Manifest(); - - if (Files.exists(manifestPath)) { - try (InputStream stream = Files.newInputStream(manifestPath)) { - manifest.read(stream); - manifest.getMainAttributes().putValue(LOOM_PATCH_VERSION_KEY, CURRENT_LOOM_PATCH_VERSION); - } - } - - try (OutputStream stream = Files.newOutputStream(manifestPath, StandardOpenOption.CREATE)) { - manifest.write(stream); - } - } - } - - protected boolean isPatchedJarUpToDate(File jar) throws IOException { - if (!jar.exists()) return false; - - byte[] manifestBytes = ZipUtils.unpackNullable(jar.toPath(), "META-INF/MANIFEST.MF"); - - if (manifestBytes == null) { - return false; - } - - Manifest manifest = new Manifest(new ByteArrayInputStream(manifestBytes)); - Attributes attributes = manifest.getMainAttributes(); - String value = attributes.getValue(LOOM_PATCH_VERSION_KEY); + private byte[] getProjectAtsHash() throws IOException { + if (projectAts.isEmpty()) return ByteSource.empty().hash(Hashing.sha256()).asBytes(); + List currentBytes = new ArrayList<>(); - if (Objects.equals(value, CURRENT_LOOM_PATCH_VERSION)) { - return true; - } else { - getProject().getLogger().lifecycle(":forge patched jars not up to date. current version: " + value); - return false; + for (File projectAt : projectAts) { + currentBytes.add(com.google.common.io.Files.asByteSource(projectAt)); } - } - public boolean usesProjectCache() { - return !projectAts.isEmpty(); - } - - public File getMergedJar() { - return minecraftMergedPatchedAtJar; - } - - public File getForgeMergedJar() { - return forgeMergedJar; - } - - public boolean isAtDirty() { - return atDirty || filesDirty; + return ByteSource.concat(currentBytes).hash(Hashing.sha256()).asBytes(); } public void cleanAllCache() { @@ -288,107 +218,85 @@ protected File[] getProjectCache() { return new File[] {minecraftMergedPatchedSrgAtJar, minecraftMergedPatchedAtJar}; } - private void writeAtHash() throws IOException { - try (FileOutputStream out = new FileOutputStream(projectAtHash)) { - out.write(getProjectAtsHash()); - } - } - - private byte[] getProjectAtsHash() throws IOException { - if (projectAts.isEmpty()) return ByteSource.empty().hash(Hashing.sha256()).asBytes(); - List currentBytes = new ArrayList<>(); - - for (File projectAt : projectAts) { - currentBytes.add(com.google.common.io.Files.asByteSource(projectAt)); - } - - return ByteSource.concat(currentBytes).hash(Hashing.sha256()).asBytes(); + @Override + public void provide(DependencyInfo dependency, Consumer postPopulationScheduler) throws Exception { + initFiles(); + testCleanAllCaches(); + beginTransform(); } - private void walkFileSystems(File source, File target, Predicate filter, Function> toWalk, FsPathConsumer action) - throws IOException { - try (FileSystemUtil.Delegate sourceFs = FileSystemUtil.getJarFileSystem(source, false); FileSystemUtil.Delegate targetFs = FileSystemUtil.getJarFileSystem(target, false)) { - for (Path sourceDir : toWalk.apply(sourceFs.get())) { - Path dir = sourceDir.toAbsolutePath(); - if (!Files.exists(dir)) continue; - Files.walk(dir).filter(Files::isRegularFile).filter(filter).forEach(it -> { - boolean root = dir.getParent() == null; + protected abstract void beginTransform() throws Exception; - try { - Path relativeSource = root ? it : dir.relativize(it); - Path targetPath = targetFs.get().getPath(relativeSource.toString()); - action.accept(sourceFs.get(), targetFs.get(), it, targetPath); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - }); - } + public void testCleanAllCaches() throws IOException { + if (isRefreshDeps() || Stream.of(getGlobalCaches()).anyMatch(((Predicate) File::exists).negate()) || !isPatchedJarUpToDate(minecraftMergedPatchedAtJar)) { + cleanAllCache(); + } else if (atDirty || Stream.of(getProjectCache()).anyMatch(((Predicate) File::exists).negate())) { + cleanProjectCache(); } } - protected abstract void patchJars(File clean, File output, Path patches, String side) throws IOException; - - protected abstract void mergeJars(Logger logger) throws Exception; + public abstract void endTransform() throws Exception; - protected void copyMissingClasses(File source, File target) throws IOException { - walkFileSystems(source, target, it -> it.toString().endsWith(".class"), (sourceFs, targetFs, sourcePath, targetPath) -> { - if (Files.exists(targetPath)) return; - Path parent = targetPath.getParent(); + protected void fillClientExtraJar() throws IOException { + Files.deleteIfExists(minecraftClientExtra.toPath()); + FileSystemUtil.getJarFileSystem(minecraftClientExtra, true).close(); - if (parent != null) { - Files.createDirectories(parent); - } + copyNonClassFiles(getExtension().getMinecraftProvider().minecraftClientJar, minecraftClientExtra); + } - Files.copy(sourcePath, targetPath); - }); + private void writeAtHash() throws IOException { + try (FileOutputStream out = new FileOutputStream(projectAtHash)) { + out.write(getProjectAtsHash()); + } } - protected void copyNonClassFiles(File source, File target) throws IOException { - Predicate filter = getExtension().isForgeAndOfficial() ? file -> { - String s = file.toString(); - return !s.endsWith(".class"); - } : file -> { - String s = file.toString(); - return !s.endsWith(".class") || (s.startsWith("META-INF") && !s.startsWith("META-INF/services")); - }; + protected TinyRemapper buildRemapper(Path input, String from, String to) throws IOException { + Path[] libraries = TinyRemapperHelper.getMinecraftDependencies(getProject()); + MemoryMappingTree mappingsWithSrg = getExtension().getMappingsProvider().getMappingsWithSrg(); - walkFileSystems(source, target, filter, this::copyReplacing); - } + TinyRemapper remapper = TinyRemapper.newRemapper().logger(getProject().getLogger()::lifecycle).logUnknownInvokeDynamic(false).withMappings(TinyRemapperHelper.create(mappingsWithSrg, from, to, true)).withMappings(InnerClassRemapper.of(InnerClassRemapper.readClassNames(input), mappingsWithSrg, from, to)).renameInvalidLocals(true).rebuildSourceFilenames(true).fixPackageAccess(true).build(); - private void walkFileSystems(File source, File target, Predicate filter, FsPathConsumer action) throws IOException { - walkFileSystems(source, target, filter, FileSystem::getRootDirectories, action); - } + if (getProject().getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) < 0) { + MappingsProviderVerbose.saveFile(remapper); + } - private void copyAll(File source, File target) throws IOException { - walkFileSystems(source, target, it -> true, this::copyReplacing); + remapper.readClassPath(libraries); + remapper.prepareClasses(); + return remapper; } - private void copyReplacing(FileSystem sourceFs, FileSystem targetFs, Path sourcePath, Path targetPath) throws IOException { - Path parent = targetPath.getParent(); + protected void fixParameterAnnotation(File jarFile) throws Exception { + getProject().getLogger().info(":fixing parameter annotations for " + jarFile.getAbsolutePath()); + Stopwatch stopwatch = Stopwatch.createStarted(); - if (parent != null) { - Files.createDirectories(parent); - } + try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + jarFile.toURI()), ImmutableMap.of("create", false))) { + ThreadingUtils.TaskCompleter completer = ThreadingUtils.taskCompleter(); - Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING); - } + for (Path file : (Iterable) Files.walk(fs.getPath("/"))::iterator) { + if (!file.toString().endsWith(".class")) continue; - protected void copyUserdevFiles(File source, File target) throws IOException { - // Removes the Forge name mapping service definition so that our own is used. - // If there are multiple name mapping services with the same "understanding" pair - // (source -> target namespace pair), modlauncher throws a fit and will crash. - // To use our YarnNamingService instead of MCPNamingService, we have to remove this file. - Predicate filter = file -> !file.toString().endsWith(".class") && !file.toString().equals(NAME_MAPPING_SERVICE_PATH); + completer.add(() -> { + byte[] bytes = Files.readAllBytes(file); + ClassReader reader = new ClassReader(bytes); + ClassNode node = new ClassNode(); + ClassVisitor visitor = new ParameterAnnotationFixer(node, null); + reader.accept(visitor, 0); - walkFileSystems(source, target, filter, fs -> Collections.singleton(fs.getPath("inject")), (sourceFs, targetFs, sourcePath, targetPath) -> { - Path parent = targetPath.getParent(); + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); + node.accept(writer); + byte[] out = writer.toByteArray(); - if (parent != null) { - Files.createDirectories(parent); + if (!Arrays.equals(bytes, out)) { + Files.delete(file); + Files.write(file, out); + } + }); } - Files.copy(sourcePath, targetPath); - }); + completer.complete(); + } + + getProject().getLogger().info(":fixing parameter annotations for " + jarFile.getAbsolutePath() + " in " + stopwatch); } protected void deleteParameterNames(File jarFile) throws Exception { @@ -445,38 +353,33 @@ public void visitLocalVariable(String name, String descriptor, String signature, getProject().getLogger().info(":deleting parameter names for " + jarFile.getAbsolutePath() + " in " + stopwatch); } - protected void fixParameterAnnotation(File jarFile) throws Exception { - getProject().getLogger().info(":fixing parameter annotations for " + jarFile.getAbsolutePath()); - Stopwatch stopwatch = Stopwatch.createStarted(); + protected File getForgeJar() { + return getExtension().getForgeUniversalProvider().getForge(); + } - try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:" + jarFile.toURI()), ImmutableMap.of("create", false))) { - ThreadingUtils.TaskCompleter completer = ThreadingUtils.taskCompleter(); + protected File getForgeUserdevJar() { + return getExtension().getForgeUserdevProvider().getUserdevJar(); + } - for (Path file : (Iterable) Files.walk(fs.getPath("/"))::iterator) { - if (!file.toString().endsWith(".class")) continue; + protected boolean isPatchedJarUpToDate(File jar) throws IOException { + if (!jar.exists()) return false; - completer.add(() -> { - byte[] bytes = Files.readAllBytes(file); - ClassReader reader = new ClassReader(bytes); - ClassNode node = new ClassNode(); - ClassVisitor visitor = new ParameterAnnotationFixer(node, null); - reader.accept(visitor, 0); + byte[] manifestBytes = ZipUtils.unpackNullable(jar.toPath(), "META-INF/MANIFEST.MF"); - ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); - node.accept(writer); - byte[] out = writer.toByteArray(); + if (manifestBytes == null) { + return false; + } - if (!Arrays.equals(bytes, out)) { - Files.delete(file); - Files.write(file, out); - } - }); - } + Manifest manifest = new Manifest(new ByteArrayInputStream(manifestBytes)); + Attributes attributes = manifest.getMainAttributes(); + String value = attributes.getValue(LOOM_PATCH_VERSION_KEY); - completer.complete(); + if (Objects.equals(value, CURRENT_LOOM_PATCH_VERSION)) { + return true; + } else { + getProject().getLogger().lifecycle(":forge patched jars not up to date. current version: " + value); + return false; } - - getProject().getLogger().info(":fixing parameter annotations for " + jarFile.getAbsolutePath() + " in " + stopwatch); } protected void accessTransformForge(Logger logger) throws Exception { @@ -539,29 +442,6 @@ protected void accessTransformForge(Logger logger) throws Exception { logger.lifecycle(":access transformed minecraft in " + stopwatch.stop()); } - protected File getForgeJar() { - return getExtension().getForgeUniversalProvider().getForge(); - } - - protected File getForgeUserdevJar() { - return getExtension().getForgeUserdevProvider().getUserdevJar(); - } - - protected TinyRemapper buildRemapper(Path input, String from, String to) throws IOException { - Path[] libraries = TinyRemapperHelper.getMinecraftDependencies(getProject()); - MemoryMappingTree mappingsWithSrg = getExtension().getMappingsProvider().getMappingsWithSrg(); - - TinyRemapper remapper = TinyRemapper.newRemapper().logger(getProject().getLogger()::lifecycle).logUnknownInvokeDynamic(false).withMappings(TinyRemapperHelper.create(mappingsWithSrg, from, to, true)).withMappings(InnerClassRemapper.of(InnerClassRemapper.readClassNames(input), mappingsWithSrg, from, to)).renameInvalidLocals(true).rebuildSourceFilenames(true).fixPackageAccess(true).build(); - - if (getProject().getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) < 0) { - MappingsProviderVerbose.saveFile(remapper); - } - - remapper.readClassPath(libraries); - remapper.prepareClasses(); - return remapper; - } - protected void remapPatchedJar(Logger logger) throws Exception { getProject().getLogger().lifecycle(":remapping minecraft (TinyRemapper, srg -> official)"); Path mcInput = minecraftMergedPatchedSrgAtJar.toPath(); @@ -600,10 +480,130 @@ protected void remapPatchedJar(Logger logger) throws Exception { applyLoomPatchVersion(mcOutput); } - protected void fillClientExtraJar() throws IOException { - Files.deleteIfExists(minecraftClientExtra.toPath()); - FileSystemUtil.getJarFileSystem(minecraftClientExtra, true).close(); + protected abstract void patchJars(File clean, File output, Path patches, String side) throws IOException; - copyNonClassFiles(getExtension().getMinecraftProvider().minecraftClientJar, minecraftClientExtra); + protected abstract void mergeJars(Logger logger) throws Exception; + + private void walkFileSystems(File source, File target, Predicate filter, Function> toWalk, FsPathConsumer action) + throws IOException { + try (FileSystemUtil.Delegate sourceFs = FileSystemUtil.getJarFileSystem(source, false); FileSystemUtil.Delegate targetFs = FileSystemUtil.getJarFileSystem(target, false)) { + for (Path sourceDir : toWalk.apply(sourceFs.get())) { + Path dir = sourceDir.toAbsolutePath(); + if (!Files.exists(dir)) continue; + Files.walk(dir).filter(Files::isRegularFile).filter(filter).forEach(it -> { + boolean root = dir.getParent() == null; + + try { + Path relativeSource = root ? it : dir.relativize(it); + Path targetPath = targetFs.get().getPath(relativeSource.toString()); + action.accept(sourceFs.get(), targetFs.get(), it, targetPath); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }); + } + } + } + + private void walkFileSystems(File source, File target, Predicate filter, FsPathConsumer action) throws IOException { + walkFileSystems(source, target, filter, FileSystem::getRootDirectories, action); + } + + private void copyAll(File source, File target) throws IOException { + walkFileSystems(source, target, it -> true, this::copyReplacing); + } + + protected void copyMissingClasses(File source, File target) throws IOException { + walkFileSystems(source, target, it -> it.toString().endsWith(".class"), (sourceFs, targetFs, sourcePath, targetPath) -> { + if (Files.exists(targetPath)) return; + Path parent = targetPath.getParent(); + + if (parent != null) { + Files.createDirectories(parent); + } + + Files.copy(sourcePath, targetPath); + }); + } + + protected void copyNonClassFiles(File source, File target) throws IOException { + Predicate filter = getExtension().isForgeAndOfficial() ? file -> { + String s = file.toString(); + return !s.endsWith(".class"); + } : file -> { + String s = file.toString(); + return !s.endsWith(".class") || (s.startsWith("META-INF") && !s.startsWith("META-INF/services")); + }; + + walkFileSystems(source, target, filter, this::copyReplacing); + } + + private void copyReplacing(FileSystem sourceFs, FileSystem targetFs, Path sourcePath, Path targetPath) throws IOException { + Path parent = targetPath.getParent(); + + if (parent != null) { + Files.createDirectories(parent); + } + + Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING); + } + + protected void copyUserdevFiles(File source, File target) throws IOException { + // Removes the Forge name mapping service definition so that our own is used. + // If there are multiple name mapping services with the same "understanding" pair + // (source -> target namespace pair), modlauncher throws a fit and will crash. + // To use our YarnNamingService instead of MCPNamingService, we have to remove this file. + Predicate filter = file -> !file.toString().endsWith(".class") && !file.toString().equals(NAME_MAPPING_SERVICE_PATH); + + walkFileSystems(source, target, filter, fs -> Collections.singleton(fs.getPath("inject")), (sourceFs, targetFs, sourcePath, targetPath) -> { + Path parent = targetPath.getParent(); + + if (parent != null) { + Files.createDirectories(parent); + } + + Files.copy(sourcePath, targetPath); + }); + } + + public void applyLoomPatchVersion(Path target) throws IOException { + try (FileSystemUtil.Delegate delegate = FileSystemUtil.getJarFileSystem(target, false)) { + Path manifestPath = delegate.get().getPath("META-INF/MANIFEST.MF"); + + Preconditions.checkArgument(Files.exists(manifestPath), "META-INF/MANIFEST.MF does not exist in patched srg jar!"); + Manifest manifest = new Manifest(); + + if (Files.exists(manifestPath)) { + try (InputStream stream = Files.newInputStream(manifestPath)) { + manifest.read(stream); + manifest.getMainAttributes().putValue(LOOM_PATCH_VERSION_KEY, CURRENT_LOOM_PATCH_VERSION); + } + } + + try (OutputStream stream = Files.newOutputStream(manifestPath, StandardOpenOption.CREATE)) { + manifest.write(stream); + } + } + } + + public File getMergedJar() { + return minecraftMergedPatchedAtJar; + } + + public File getForgeMergedJar() { + return forgeMergedJar; + } + + public boolean usesProjectCache() { + return !projectAts.isEmpty(); + } + + public boolean isAtDirty() { + return atDirty || filesDirty; + } + + @Override + public String getTargetConfig() { + return Constants.Configurations.MINECRAFT; } } From fa9d626a294c21655a71199e54b1b1288d77f606 Mon Sep 17 00:00:00 2001 From: Wagyourtail Date: Sun, 12 Dec 2021 09:54:48 -0700 Subject: [PATCH 24/28] fixes --- .../forge/MinecraftPatchedProvider.java | 21 +++++++++++++++---- .../fg2/MinecraftPatchedProviderFG2.java | 2 +- .../fg3/MinecraftPatchedProviderFG3.java | 2 +- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java index d1755e843..8f1144be3 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/MinecraftPatchedProvider.java @@ -198,10 +198,12 @@ public void cleanAllCache() { } protected File[] getGlobalCaches() { - File[] files = {minecraftClientExtra}; + File[] files = { + minecraftClientExtra + }; if (forgeMergedJar != null) { - Arrays.copyOf(files, files.length + 1); + files = Arrays.copyOf(files, files.length + 1); files[files.length - 1] = forgeMergedJar; } @@ -215,7 +217,10 @@ public void cleanProjectCache() { } protected File[] getProjectCache() { - return new File[] {minecraftMergedPatchedSrgAtJar, minecraftMergedPatchedAtJar}; + return new File[] { + minecraftMergedPatchedSrgAtJar, + minecraftMergedPatchedAtJar + }; } @Override @@ -254,7 +259,15 @@ protected TinyRemapper buildRemapper(Path input, String from, String to) throws Path[] libraries = TinyRemapperHelper.getMinecraftDependencies(getProject()); MemoryMappingTree mappingsWithSrg = getExtension().getMappingsProvider().getMappingsWithSrg(); - TinyRemapper remapper = TinyRemapper.newRemapper().logger(getProject().getLogger()::lifecycle).logUnknownInvokeDynamic(false).withMappings(TinyRemapperHelper.create(mappingsWithSrg, from, to, true)).withMappings(InnerClassRemapper.of(InnerClassRemapper.readClassNames(input), mappingsWithSrg, from, to)).renameInvalidLocals(true).rebuildSourceFilenames(true).fixPackageAccess(true).build(); + TinyRemapper remapper = TinyRemapper.newRemapper() + .logger(getProject().getLogger()::lifecycle) + .logUnknownInvokeDynamic(false) + .withMappings(TinyRemapperHelper.create(mappingsWithSrg, from, to, true)) + .withMappings(InnerClassRemapper.of(InnerClassRemapper.readClassNames(input), mappingsWithSrg, from, to)) + .renameInvalidLocals(true) + .rebuildSourceFilenames(true) + .fixPackageAccess(true) + .build(); if (getProject().getGradle().getStartParameter().getLogLevel().compareTo(LogLevel.LIFECYCLE) < 0) { MappingsProviderVerbose.saveFile(remapper); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/MinecraftPatchedProviderFG2.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/MinecraftPatchedProviderFG2.java index 7afe4c65b..22ec9835b 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/MinecraftPatchedProviderFG2.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/MinecraftPatchedProviderFG2.java @@ -100,7 +100,7 @@ protected File[] getGlobalCaches() { }; if (forgeMergedJar != null) { - Arrays.copyOf(files, files.length + 1); + files = Arrays.copyOf(files, files.length + 1); files[files.length - 1] = forgeMergedJar; } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg3/MinecraftPatchedProviderFG3.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg3/MinecraftPatchedProviderFG3.java index 9fb6768a8..d8caba924 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg3/MinecraftPatchedProviderFG3.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg3/MinecraftPatchedProviderFG3.java @@ -93,7 +93,7 @@ protected File[] getGlobalCaches() { }; if (forgeMergedJar != null) { - Arrays.copyOf(files, files.length + 1); + files = Arrays.copyOf(files, files.length + 1); files[files.length - 1] = forgeMergedJar; } From c8326f684cc8ed45ad09f34b683036d86ac9844f Mon Sep 17 00:00:00 2001 From: shedaniel Date: Tue, 14 Dec 2021 00:02:44 +0800 Subject: [PATCH 25/28] WIP Remove pack200 Signed-off-by: shedaniel --- .../forge/fg2/FG2TaskApplyBinPatches.java | 86 +- .../fg2/MinecraftPatchedProviderFG2.java | 2 +- .../providers/forge/fg2/Pack200Provider.java | 8 + .../java/util/jar/pack/AdaptiveCoding.java | 297 -- .../com/sun/java/util/jar/pack/Attribute.java | 1695 ---------- .../sun/java/util/jar/pack/BandStructure.java | 2759 ----------------- .../sun/java/util/jar/pack/ClassReader.java | 636 ---- .../sun/java/util/jar/pack/ClassWriter.java | 310 -- .../com/sun/java/util/jar/pack/Code.java | 396 --- .../com/sun/java/util/jar/pack/Coding.java | 906 ------ .../sun/java/util/jar/pack/CodingChooser.java | 1489 --------- .../sun/java/util/jar/pack/CodingMethod.java | 43 - .../sun/java/util/jar/pack/ConstantPool.java | 1657 ---------- .../com/sun/java/util/jar/pack/Constants.java | 503 --- .../com/sun/java/util/jar/pack/Driver.java | 749 ----- .../java/util/jar/pack/DriverResource.java | 136 - .../java/util/jar/pack/DriverResource_ja.java | 136 - .../util/jar/pack/DriverResource_zh_CN.java | 136 - .../com/sun/java/util/jar/pack/FixedList.java | 174 -- .../com/sun/java/util/jar/pack/Fixups.java | 570 ---- .../com/sun/java/util/jar/pack/Histogram.java | 818 ----- .../sun/java/util/jar/pack/Instruction.java | 687 ---- .../sun/java/util/jar/pack/NativeUnpack.java | 331 -- .../com/sun/java/util/jar/pack/Package.java | 1375 -------- .../sun/java/util/jar/pack/PackageReader.java | 2371 -------------- .../sun/java/util/jar/pack/PackageWriter.java | 1738 ----------- .../sun/java/util/jar/pack/PackerImpl.java | 607 ---- .../java/util/jar/pack/PopulationCoding.java | 499 --- .../com/sun/java/util/jar/pack/PropMap.java | 315 -- .../com/sun/java/util/jar/pack/TLGlobals.java | 115 - .../sun/java/util/jar/pack/UnpackerImpl.java | 264 -- .../com/sun/java/util/jar/pack/Utils.java | 321 -- .../fabricmc/shade/java/util/jar/Pack200.java | 738 ----- 33 files changed, 47 insertions(+), 22820 deletions(-) create mode 100644 src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/Pack200Provider.java delete mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/AdaptiveCoding.java delete mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Attribute.java delete mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/BandStructure.java delete mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/ClassReader.java delete mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/ClassWriter.java delete mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Code.java delete mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Coding.java delete mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/CodingChooser.java delete mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/CodingMethod.java delete mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/ConstantPool.java delete mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Constants.java delete mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Driver.java delete mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/DriverResource.java delete mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/DriverResource_ja.java delete mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/DriverResource_zh_CN.java delete mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/FixedList.java delete mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Fixups.java delete mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Histogram.java delete mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Instruction.java delete mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/NativeUnpack.java delete mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Package.java delete mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/PackageReader.java delete mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/PackageWriter.java delete mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/PackerImpl.java delete mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/PopulationCoding.java delete mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/PropMap.java delete mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/TLGlobals.java delete mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/UnpackerImpl.java delete mode 100644 src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Utils.java delete mode 100644 src/main/java/net/fabricmc/shade/java/util/jar/Pack200.java diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/FG2TaskApplyBinPatches.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/FG2TaskApplyBinPatches.java index f6ae1edbc..e615f48bc 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/FG2TaskApplyBinPatches.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/FG2TaskApplyBinPatches.java @@ -30,6 +30,8 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.List; +import java.util.ServiceLoader; import java.util.jar.JarEntry; import java.util.jar.JarInputStream; import java.util.jar.JarOutputStream; @@ -41,7 +43,6 @@ import java.util.zip.ZipOutputStream; import com.google.common.base.Joiner; -import com.google.common.base.Throwables; import com.google.common.collect.Maps; import com.google.common.io.ByteArrayDataInput; import com.google.common.io.ByteStreams; @@ -50,28 +51,25 @@ import lzma.streams.LzmaInputStream; import org.gradle.api.Project; -import net.fabricmc.shade.java.util.jar.Pack200; - public class FG2TaskApplyBinPatches { - private static final HashMap patchlist = Maps.newHashMap(); - private static final GDiffPatcher patcher = new GDiffPatcher(); + private final HashMap patches = Maps.newHashMap(); + private final GDiffPatcher patcher = new GDiffPatcher(); - private static Project project; + private Project project; - public static void doTask(Project project, File inJar, File patches, File outjar, String side) throws IOException { - FG2TaskApplyBinPatches.project = project; - setup(patches, side); + public FG2TaskApplyBinPatches(Project project) { + this.project = project; + } - if (outjar.exists()) { - outjar.delete(); - } + public void doTask(File input, File patches, File output, String side) throws IOException { + setup(patches, side); + output.delete(); - ZipFile in = new ZipFile(inJar); - ZipInputStream classesIn = new ZipInputStream(new FileInputStream(inJar)); - final ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(outjar))); - final HashSet entries = new HashSet(); + final HashSet entries = new HashSet<>(); - try { + try (ZipFile in = new ZipFile(input); + ZipInputStream classesIn = new ZipInputStream(new FileInputStream(input)); + ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(new FileOutputStream(output)))) { // DO PATCHES log("Patching Class:"); @@ -88,14 +86,14 @@ public static void doTask(Project project, File inJar, File patches, File outjar out.putNextEntry(n); byte[] data = ByteStreams.toByteArray(in.getInputStream(e)); - ClassPatch patch = patchlist.get(e.getName().replace('\\', '/')); + ClassPatch patch = this.patches.get(e.getName().replace('\\', '/')); if (patch != null) { log("\t%s (%s) (input size %d)", patch.targetClassName, patch.sourceClassName, data.length); int inputChecksum = adlerHash(data); if (patch.inputChecksum != inputChecksum) { - throw new RuntimeException(String.format("There is a binary discrepency between the expected input class %s (%s) and the actual class. Checksum on disk is %x, in patch %x. Things are probably about to go very wrong. Did you put something into the jar file?", patch.targetClassName, patch.sourceClassName, inputChecksum, patch.inputChecksum)); + throw new RuntimeException(String.format("There is a binary discrepancy between the expected input class %s (%s) and the actual class. Checksum on disk is %x, in patch %x. Things are probably about to go very wrong. Did you put something into the jar file?", patch.targetClassName, patch.sourceClassName, inputChecksum, patch.inputChecksum)); } synchronized (patcher) { @@ -111,7 +109,7 @@ public static void doTask(Project project, File inJar, File patches, File outjar } // COPY DATA - ZipEntry entry = null; + ZipEntry entry; while ((entry = classesIn.getNextEntry()) != null) { if (entries.contains(entry.getName())) { @@ -122,10 +120,6 @@ public static void doTask(Project project, File inJar, File patches, File outjar out.write(ByteStreams.toByteArray(classesIn)); entries.add(entry.getName()); } - } finally { - classesIn.close(); - in.close(); - out.close(); } } @@ -135,7 +129,7 @@ private static int adlerHash(byte[] input) { return (int) hasher.getValue(); } - public static void setup(File patches, String side) { + public void setup(File patches, String side) { Pattern matcher = Pattern.compile(String.format("binpatch/%s/.*.binpatch", side)); JarInputStream jis; @@ -144,10 +138,19 @@ public static void setup(File patches, String side) { LzmaInputStream binpatchesDecompressed = new LzmaInputStream(new FileInputStream(patches), new Decoder()); ByteArrayOutputStream jarBytes = new ByteArrayOutputStream(); JarOutputStream jos = new JarOutputStream(jarBytes); - Pack200.newUnpacker().unpack(binpatchesDecompressed, jos); + List> loader = ServiceLoader.load(Pack200Provider.class) + .stream().toList(); + + if (loader.isEmpty()) { + throw new IllegalStateException("No provider for Pack200 has been found. Did you declare a provider?"); + } else if (loader.size() > 1) { + throw new IllegalStateException("Multiple providers for Pack200 have been found, this is not supported. Did you properly declare a provider?"); + } + + loader.get(0).get().unpack(binpatchesDecompressed, jos); jis = new JarInputStream(new ByteArrayInputStream(jarBytes.toByteArray())); } catch (Exception e) { - throw Throwables.propagate(e); + throw new RuntimeException(e); } log("Reading Patches:"); @@ -162,19 +165,20 @@ public static void setup(File patches, String side) { if (matcher.matcher(entry.getName()).matches()) { ClassPatch cp = readPatch(entry, jis); - patchlist.put(cp.sourceClassName.replace('.', '/') + ".class", cp); + this.patches.put(cp.sourceClassName.replace('.', '/') + ".class", cp); } else { log("skipping entry: %s", entry.getName()); jis.closeEntry(); } } catch (IOException e) { + throw new RuntimeException(e); } } while (true); - log("Read %d binary patches", patchlist.size()); - log("Patch list :\n\t%s", Joiner.on("\n\t").join(patchlist.entrySet())); + log("Read %d binary patches", this.patches.size()); + log("Patch list :\n\t%s", Joiner.on("\n\t").join(this.patches.entrySet())); } - private static ClassPatch readPatch(JarEntry patchEntry, JarInputStream jis) throws IOException { + private ClassPatch readPatch(JarEntry patchEntry, JarInputStream jis) throws IOException { log("\t%s", patchEntry.getName()); ByteArrayDataInput input = ByteStreams.newDataInput(ByteStreams.toByteArray(jis)); @@ -195,26 +199,12 @@ private static ClassPatch readPatch(JarEntry patchEntry, JarInputStream jis) thr return new ClassPatch(name, sourceClassName, targetClassName, exists, inputChecksum, patchBytes); } - private static void log(String format, Object... args) { + private void log(String format, Object... args) { project.getLogger().info(String.format(format, args)); } - public static class ClassPatch { - public final String name; - public final String sourceClassName; - public final String targetClassName; - public final boolean existsAtTarget; - public final byte[] patch; - public final int inputChecksum; - - public ClassPatch(String name, String sourceClassName, String targetClassName, boolean existsAtTarget, int inputChecksum, byte[] patch) { - this.name = name; - this.sourceClassName = sourceClassName; - this.targetClassName = targetClassName; - this.existsAtTarget = existsAtTarget; - this.inputChecksum = inputChecksum; - this.patch = patch; - } + public record ClassPatch(String name, String sourceClassName, String targetClassName, boolean existsAtTarget, int inputChecksum, + byte[] patch) { @Override public String toString() { diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/MinecraftPatchedProviderFG2.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/MinecraftPatchedProviderFG2.java index 22ec9835b..a6ebfce9a 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/MinecraftPatchedProviderFG2.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/MinecraftPatchedProviderFG2.java @@ -232,7 +232,7 @@ protected void patchJars(File clean, File output, Path patches, String side) thr // Failed to replace logger filter, just ignore } - FG2TaskApplyBinPatches.doTask(getProject(), clean.getAbsoluteFile(), patches.toFile().getAbsoluteFile(), output.getAbsoluteFile(), side); + new FG2TaskApplyBinPatches(getProject()).doTask(clean.getAbsoluteFile(), patches.toFile().getAbsoluteFile(), output.getAbsoluteFile(), side); try { System.setOut(previous); diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/Pack200Provider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/Pack200Provider.java new file mode 100644 index 000000000..8912c42cf --- /dev/null +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/Pack200Provider.java @@ -0,0 +1,8 @@ +package net.fabricmc.loom.configuration.providers.forge.fg2; + +import java.io.InputStream; +import java.util.jar.JarOutputStream; + +public interface Pack200Provider { + void unpack(InputStream inputStream, JarOutputStream outputStream); +} diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/AdaptiveCoding.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/AdaptiveCoding.java deleted file mode 100644 index d1aff1307..000000000 --- a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/AdaptiveCoding.java +++ /dev/null @@ -1,297 +0,0 @@ -/* - * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package net.fabricmc.shade.com.sun.java.util.jar.pack; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -/** - * Adaptive coding. - * See the section "Adaptive Encodings" in the Pack200 spec. - * @author John Rose - */ -class AdaptiveCoding implements CodingMethod { - CodingMethod headCoding; - int headLength; - CodingMethod tailCoding; - - public AdaptiveCoding(int headLength, CodingMethod headCoding, CodingMethod tailCoding) { - assert(isCodableLength(headLength)); - this.headLength = headLength; - this.headCoding = headCoding; - this.tailCoding = tailCoding; - } - - public void setHeadCoding(CodingMethod headCoding) { - this.headCoding = headCoding; - } - public void setHeadLength(int headLength) { - assert(isCodableLength(headLength)); - this.headLength = headLength; - } - public void setTailCoding(CodingMethod tailCoding) { - this.tailCoding = tailCoding; - } - - public boolean isTrivial() { - return headCoding == tailCoding; - } - - // CodingMethod methods. - public void writeArrayTo(OutputStream out, int[] a, int start, int end) throws IOException { - writeArray(this, out, a, start, end); - } - // writeArrayTo must be coded iteratively, not recursively: - private static void writeArray(AdaptiveCoding run, OutputStream out, int[] a, int start, int end) throws IOException { - for (;;) { - int mid = start+run.headLength; - assert(mid <= end); - run.headCoding.writeArrayTo(out, a, start, mid); - start = mid; - if (run.tailCoding instanceof AdaptiveCoding) { - run = (AdaptiveCoding) run.tailCoding; - continue; - } - break; - } - run.tailCoding.writeArrayTo(out, a, start, end); - } - - public void readArrayFrom(InputStream in, int[] a, int start, int end) throws IOException { - readArray(this, in, a, start, end); - } - private static void readArray(AdaptiveCoding run, InputStream in, int[] a, int start, int end) throws IOException { - for (;;) { - int mid = start+run.headLength; - assert(mid <= end); - run.headCoding.readArrayFrom(in, a, start, mid); - start = mid; - if (run.tailCoding instanceof AdaptiveCoding) { - run = (AdaptiveCoding) run.tailCoding; - continue; - } - break; - } - run.tailCoding.readArrayFrom(in, a, start, end); - } - - public static final int KX_MIN = 0; - public static final int KX_MAX = 3; - public static final int KX_LG2BASE = 4; - public static final int KX_BASE = 16; - - public static final int KB_MIN = 0x00; - public static final int KB_MAX = 0xFF; - public static final int KB_OFFSET = 1; - public static final int KB_DEFAULT = 3; - - static int getKXOf(int K) { - for (int KX = KX_MIN; KX <= KX_MAX; KX++) { - if (((K - KB_OFFSET) & ~KB_MAX) == 0) - return KX; - K >>>= KX_LG2BASE; - } - return -1; - } - - static int getKBOf(int K) { - int KX = getKXOf(K); - if (KX < 0) return -1; - K >>>= (KX * KX_LG2BASE); - return K-1; - } - - static int decodeK(int KX, int KB) { - assert(KX_MIN <= KX && KX <= KX_MAX); - assert(KB_MIN <= KB && KB <= KB_MAX); - return (KB+KB_OFFSET) << (KX * KX_LG2BASE); - } - - static int getNextK(int K) { - if (K <= 0) return 1; // 1st K value - int KX = getKXOf(K); - if (KX < 0) return Integer.MAX_VALUE; - // This is the increment we expect to apply: - int unit = 1 << (KX * KX_LG2BASE); - int mask = KB_MAX << (KX * KX_LG2BASE); - int K1 = K + unit; - K1 &= ~(unit-1); // cut off stray low-order bits - if (((K1 - unit) & ~mask) == 0) { - assert(getKXOf(K1) == KX); - return K1; - } - if (KX == KX_MAX) return Integer.MAX_VALUE; - KX += 1; - int mask2 = KB_MAX << (KX * KX_LG2BASE); - K1 |= (mask & ~mask2); - K1 += unit; - assert(getKXOf(K1) == KX); - return K1; - } - - // Is K of the form ((KB:[0..255])+1) * 16^(KX:{0..3])? - public static boolean isCodableLength(int K) { - int KX = getKXOf(K); - if (KX < 0) return false; - int unit = 1 << (KX * KX_LG2BASE); - int mask = KB_MAX << (KX * KX_LG2BASE); - return ((K - unit) & ~mask) == 0; - } - - public byte[] getMetaCoding(Coding dflt) { - //assert(!isTrivial()); // can happen - // See the isCodableLength restriction in CodingChooser. - ByteArrayOutputStream bytes = new ByteArrayOutputStream(10); - try { - makeMetaCoding(this, dflt, bytes); - } catch (IOException ee) { - throw new RuntimeException(ee); - } - return bytes.toByteArray(); - } - private static void makeMetaCoding(AdaptiveCoding run, Coding dflt, - ByteArrayOutputStream bytes) - throws IOException { - for (;;) { - CodingMethod headCoding = run.headCoding; - int headLength = run.headLength; - CodingMethod tailCoding = run.tailCoding; - int K = headLength; - assert(isCodableLength(K)); - int ADef = (headCoding == dflt)?1:0; - int BDef = (tailCoding == dflt)?1:0; - if (ADef+BDef > 1) BDef = 0; // arbitrary choice - int ABDef = 1*ADef + 2*BDef; - assert(ABDef < 3); - int KX = getKXOf(K); - int KB = getKBOf(K); - assert(decodeK(KX, KB) == K); - int KBFlag = (KB != KB_DEFAULT)?1:0; - bytes.write(Constants._meta_run + KX + 4*KBFlag + 8*ABDef); - if (KBFlag != 0) bytes.write(KB); - if (ADef == 0) bytes.write(headCoding.getMetaCoding(dflt)); - if (tailCoding instanceof AdaptiveCoding) { - run = (AdaptiveCoding) tailCoding; - continue; // tail call, to avoid deep stack recursion - } - if (BDef == 0) bytes.write(tailCoding.getMetaCoding(dflt)); - break; - } - } - public static int parseMetaCoding(byte[] bytes, int pos, Coding dflt, CodingMethod res[]) { - int op = bytes[pos++] & 0xFF; - if (op < Constants._meta_run || op >= Constants._meta_pop) return pos-1; // backup - AdaptiveCoding prevc = null; - for (boolean keepGoing = true; keepGoing; ) { - keepGoing = false; - assert(op >= Constants._meta_run); - op -= Constants._meta_run; - int KX = op % 4; - int KBFlag = (op / 4) % 2; - int ABDef = (op / 8); - assert(ABDef < 3); - int ADef = (ABDef & 1); - int BDef = (ABDef & 2); - CodingMethod[] ACode = {dflt}, BCode = {dflt}; - int KB = KB_DEFAULT; - if (KBFlag != 0) - KB = bytes[pos++] & 0xFF; - if (ADef == 0) { - pos = BandStructure.parseMetaCoding(bytes, pos, dflt, ACode); - } - if (BDef == 0 && - ((op = bytes[pos] & 0xFF) >= Constants._meta_run) && op < Constants._meta_pop) { - pos++; - keepGoing = true; - } else if (BDef == 0) { - pos = BandStructure.parseMetaCoding(bytes, pos, dflt, BCode); - } - AdaptiveCoding newc = new AdaptiveCoding(decodeK(KX, KB), - ACode[0], BCode[0]); - if (prevc == null) { - res[0] = newc; - } else { - prevc.tailCoding = newc; - } - prevc = newc; - } - return pos; - } - - private String keyString(CodingMethod m) { - if (m instanceof Coding) - return ((Coding)m).keyString(); - return m.toString(); - } - public String toString() { - StringBuilder res = new StringBuilder(20); - AdaptiveCoding run = this; - res.append("run("); - for (;;) { - res.append(run.headLength).append("*"); - res.append(keyString(run.headCoding)); - if (run.tailCoding instanceof AdaptiveCoding) { - run = (AdaptiveCoding) run.tailCoding; - res.append(" "); - continue; - } - break; - } - res.append(" **").append(keyString(run.tailCoding)); - res.append(")"); - return res.toString(); - } - -/* - public static void main(String av[]) { - int[][] samples = { - {1,2,3,4,5}, - {254,255,256,256+1*16,256+2*16}, - {0xfd,0xfe,0xff,0x100,0x110,0x120,0x130}, - {0xfd0,0xfe0,0xff0,0x1000,0x1100,0x1200,0x1300}, - {0xfd00,0xfe00,0xff00,0x10000,0x11000,0x12000,0x13000}, - {0xfd000,0xfe000,0xff000,0x100000} - }; - for (int i = 0; i < samples.length; i++) { - for (int j = 0; j < samples[i].length; j++) { - int K = samples[i][j]; - int KX = getKXOf(K); - int KB = getKBOf(K); - System.out.println("K="+Integer.toHexString(K)+ - " KX="+KX+" KB="+KB); - assert(isCodableLength(K)); - assert(K == decodeK(KX, KB)); - if (j == 0) continue; - int K1 = samples[i][j-1]; - assert(K == getNextK(K1)); - } - } - } -//*/ - -} diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Attribute.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Attribute.java deleted file mode 100644 index 48b0c328e..000000000 --- a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Attribute.java +++ /dev/null @@ -1,1695 +0,0 @@ -/* - * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package net.fabricmc.shade.com.sun.java.util.jar.pack; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Represents an attribute in a class-file. - * Takes care to remember where constant pool indexes occur. - * Implements the "little language" of Pack200 for describing - * attribute layouts. - * @author John Rose - */ -class Attribute implements Comparable { - // Attribute instance fields. - - Layout def; // the name and format of this attr - byte[] bytes; // the actual bytes - Object fixups; // reference relocations, if any are required - - public String name() { return def.name(); } - public Layout layout() { return def; } - public byte[] bytes() { return bytes; } - public int size() { return bytes.length; } - public ConstantPool.Entry getNameRef() { return def.getNameRef(); } - - private Attribute(Attribute old) { - this.def = old.def; - this.bytes = old.bytes; - this.fixups = old.fixups; - } - - public Attribute(Layout def, byte[] bytes, Object fixups) { - this.def = def; - this.bytes = bytes; - this.fixups = fixups; - Fixups.setBytes(fixups, bytes); - } - public Attribute(Layout def, byte[] bytes) { - this(def, bytes, null); - } - - public Attribute addContent(byte[] bytes, Object fixups) { - assert(isCanonical()); - if (bytes.length == 0 && fixups == null) - return this; - Attribute res = new Attribute(this); - res.bytes = bytes; - res.fixups = fixups; - Fixups.setBytes(fixups, bytes); - return res; - } - public Attribute addContent(byte[] bytes) { - return addContent(bytes, null); - } - - public void finishRefs(ConstantPool.Index ix) { - if (fixups != null) { - Fixups.finishRefs(fixups, bytes, ix); - fixups = null; - } - } - - public boolean isCanonical() { - return this == def.canon; - } - - @Override - public int compareTo(Attribute that) { - return this.def.compareTo(that.def); - } - - private static final Map, List> canonLists = new HashMap<>(); - private static final Map attributes = new HashMap<>(); - private static final Map standardDefs = new HashMap<>(); - - // Canonicalized lists of trivial attrs (Deprecated, etc.) - // are used by trimToSize, in order to reduce footprint - // of some common cases. (Note that Code attributes are - // always zero size.) - public static List getCanonList(List al) { - synchronized (canonLists) { - List cl = canonLists.get(al); - if (cl == null) { - cl = new ArrayList<>(al.size()); - cl.addAll(al); - cl = Collections.unmodifiableList(cl); - canonLists.put(al, cl); - } - return cl; - } - } - - // Find the canonical empty attribute with the given ctype, name, layout. - public static Attribute find(int ctype, String name, String layout) { - Layout key = Layout.makeKey(ctype, name, layout); - synchronized (attributes) { - Attribute a = attributes.get(key); - if (a == null) { - a = new Layout(ctype, name, layout).canonicalInstance(); - attributes.put(key, a); - } - return a; - } - } - - public static Layout keyForLookup(int ctype, String name) { - return Layout.makeKey(ctype, name); - } - - // Find canonical empty attribute with given ctype and name, - // and with the standard layout. - public static Attribute lookup(Map defs, int ctype, - String name) { - if (defs == null) { - defs = standardDefs; - } - return defs.get(Layout.makeKey(ctype, name)); - } - - public static Attribute define(Map defs, int ctype, - String name, String layout) { - Attribute a = find(ctype, name, layout); - defs.put(Layout.makeKey(ctype, name), a); - return a; - } - - static { - Map sd = standardDefs; - define(sd, Constants.ATTR_CONTEXT_CLASS, "Signature", "RSH"); - define(sd, Constants.ATTR_CONTEXT_CLASS, "Synthetic", ""); - define(sd, Constants.ATTR_CONTEXT_CLASS, "Deprecated", ""); - define(sd, Constants.ATTR_CONTEXT_CLASS, "SourceFile", "RUH"); - define(sd, Constants.ATTR_CONTEXT_CLASS, "EnclosingMethod", "RCHRDNH"); - define(sd, Constants.ATTR_CONTEXT_CLASS, "InnerClasses", "NH[RCHRCNHRUNHFH]"); - define(sd, Constants.ATTR_CONTEXT_CLASS, "BootstrapMethods", "NH[RMHNH[KLH]]"); - - define(sd, Constants.ATTR_CONTEXT_FIELD, "Signature", "RSH"); - define(sd, Constants.ATTR_CONTEXT_FIELD, "Synthetic", ""); - define(sd, Constants.ATTR_CONTEXT_FIELD, "Deprecated", ""); - define(sd, Constants.ATTR_CONTEXT_FIELD, "ConstantValue", "KQH"); - - define(sd, Constants.ATTR_CONTEXT_METHOD, "Signature", "RSH"); - define(sd, Constants.ATTR_CONTEXT_METHOD, "Synthetic", ""); - define(sd, Constants.ATTR_CONTEXT_METHOD, "Deprecated", ""); - define(sd, Constants.ATTR_CONTEXT_METHOD, "Exceptions", "NH[RCH]"); - define(sd, Constants.ATTR_CONTEXT_METHOD, "MethodParameters", "NB[RUNHFH]"); - //define(sd, ATTR_CONTEXT_METHOD, "Code", "HHNI[B]NH[PHPOHPOHRCNH]NH[RUHNI[B]]"); - - define(sd, Constants.ATTR_CONTEXT_CODE, "StackMapTable", - ("[NH[(1)]]" + - "[TB" + - "(64-127)[(2)]" + - "(247)[(1)(2)]" + - "(248-251)[(1)]" + - "(252)[(1)(2)]" + - "(253)[(1)(2)(2)]" + - "(254)[(1)(2)(2)(2)]" + - "(255)[(1)NH[(2)]NH[(2)]]" + - "()[]" + - "]" + - "[H]" + - "[TB(7)[RCH](8)[PH]()[]]")); - - define(sd, Constants.ATTR_CONTEXT_CODE, "LineNumberTable", "NH[PHH]"); - define(sd, Constants.ATTR_CONTEXT_CODE, "LocalVariableTable", "NH[PHOHRUHRSHH]"); - define(sd, Constants.ATTR_CONTEXT_CODE, "LocalVariableTypeTable", "NH[PHOHRUHRSHH]"); - //define(sd, ATTR_CONTEXT_CODE, "CharacterRangeTable", "NH[PHPOHIIH]"); - //define(sd, ATTR_CONTEXT_CODE, "CoverageTable", "NH[PHHII]"); - - // Note: Code and InnerClasses are special-cased elsewhere. - // Their layout specs. are given here for completeness. - // The Code spec is incomplete, in that it does not distinguish - // bytecode bytes or locate CP references. - // The BootstrapMethods attribute is also special-cased - // elsewhere as an appendix to the local constant pool. - } - - // Metadata. - // - // We define metadata using similar layouts - // for all five kinds of metadata attributes and 2 type metadata attributes - // - // Regular annotations are a counted list of [RSHNH[RUH(1)]][...] - // pack.method.attribute.RuntimeVisibleAnnotations=[NH[(1)]][RSHNH[RUH(1)]][TB...] - // - // Parameter annotations are a counted list of regular annotations. - // pack.method.attribute.RuntimeVisibleParameterAnnotations=[NB[(1)]][NH[(1)]][RSHNH[RUH(1)]][TB...] - // - // RuntimeInvisible annotations are defined similarly... - // Non-method annotations are defined similarly... - // - // Annotation are a simple tagged value [TB...] - // pack.attribute.method.AnnotationDefault=[TB...] - - static { - String mdLayouts[] = { - Attribute.normalizeLayoutString - ("" - +"\n # parameter_annotations :=" - +"\n [ NB[(1)] ] # forward call to annotations" - ), - Attribute.normalizeLayoutString - ("" - +"\n # annotations :=" - +"\n [ NH[(1)] ] # forward call to annotation" - +"\n " - ), - Attribute.normalizeLayoutString - ("" - +"\n # annotation :=" - +"\n [RSH" - +"\n NH[RUH (1)] # forward call to value" - +"\n ]" - ), - Attribute.normalizeLayoutString - ("" - +"\n # value :=" - +"\n [TB # Callable 2 encodes one tagged value." - +"\n (\\B,\\C,\\I,\\S,\\Z)[KIH]" - +"\n (\\D)[KDH]" - +"\n (\\F)[KFH]" - +"\n (\\J)[KJH]" - +"\n (\\c)[RSH]" - +"\n (\\e)[RSH RUH]" - +"\n (\\s)[RUH]" - +"\n (\\[)[NH[(0)]] # backward self-call to value" - +"\n (\\@)[RSH NH[RUH (0)]] # backward self-call to value" - +"\n ()[] ]" - ) - }; - /* - * RuntimeVisibleTypeAnnotation and RuntimeInvisibleTypeAnnotatation are - * similar to RuntimeVisibleAnnotation and RuntimeInvisibleAnnotation, - * a type-annotation union and a type-path structure precedes the - * annotation structure - */ - String typeLayouts[] = { - Attribute.normalizeLayoutString - ("" - +"\n # type-annotations :=" - +"\n [ NH[(1)(2)(3)] ] # forward call to type-annotations" - ), - Attribute.normalizeLayoutString - ( "" - +"\n # type-annotation :=" - +"\n [TB" - +"\n (0-1) [B] # {CLASS, METHOD}_TYPE_PARAMETER" - +"\n (16) [FH] # CLASS_EXTENDS" - +"\n (17-18) [BB] # {CLASS, METHOD}_TYPE_PARAMETER_BOUND" - +"\n (19-21) [] # FIELD, METHOD_RETURN, METHOD_RECEIVER" - +"\n (22) [B] # METHOD_FORMAL_PARAMETER" - +"\n (23) [H] # THROWS" - +"\n (64-65) [NH[PHOHH]] # LOCAL_VARIABLE, RESOURCE_VARIABLE" - +"\n (66) [H] # EXCEPTION_PARAMETER" - +"\n (67-70) [PH] # INSTANCEOF, NEW, {CONSTRUCTOR, METHOD}_REFERENCE_RECEIVER" - +"\n (71-75) [PHB] # CAST, {CONSTRUCTOR,METHOD}_INVOCATION_TYPE_ARGUMENT, {CONSTRUCTOR, METHOD}_REFERENCE_TYPE_ARGUMENT" - +"\n ()[] ]" - ), - Attribute.normalizeLayoutString - ("" - +"\n # type-path" - +"\n [ NB[BB] ]" - ) - }; - Map sd = standardDefs; - String defaultLayout = mdLayouts[3]; - String annotationsLayout = mdLayouts[1] + mdLayouts[2] + mdLayouts[3]; - String paramsLayout = mdLayouts[0] + annotationsLayout; - String typesLayout = typeLayouts[0] + typeLayouts[1] + - typeLayouts[2] + mdLayouts[2] + mdLayouts[3]; - - for (int ctype = 0; ctype < Constants.ATTR_CONTEXT_LIMIT; ctype++) { - if (ctype != Constants.ATTR_CONTEXT_CODE) { - define(sd, ctype, - "RuntimeVisibleAnnotations", annotationsLayout); - define(sd, ctype, - "RuntimeInvisibleAnnotations", annotationsLayout); - - if (ctype == Constants.ATTR_CONTEXT_METHOD) { - define(sd, ctype, - "RuntimeVisibleParameterAnnotations", paramsLayout); - define(sd, ctype, - "RuntimeInvisibleParameterAnnotations", paramsLayout); - define(sd, ctype, - "AnnotationDefault", defaultLayout); - } - } - define(sd, ctype, - "RuntimeVisibleTypeAnnotations", typesLayout); - define(sd, ctype, - "RuntimeInvisibleTypeAnnotations", typesLayout); - } - } - - public static String contextName(int ctype) { - switch (ctype) { - case Constants.ATTR_CONTEXT_CLASS: return "class"; - case Constants.ATTR_CONTEXT_FIELD: return "field"; - case Constants.ATTR_CONTEXT_METHOD: return "method"; - case Constants.ATTR_CONTEXT_CODE: return "code"; - } - return null; - } - - /** Base class for any attributed object (Class, Field, Method, Code). - * Flags are included because they are used to help transmit the - * presence of attributes. That is, flags are a mix of modifier - * bits and attribute indicators. - */ - public abstract static - class Holder { - - // We need this abstract method to interpret embedded CP refs. - protected abstract ConstantPool.Entry[] getCPMap(); - - protected int flags; // defined here for convenience - protected List attributes; - - public int attributeSize() { - return (attributes == null) ? 0 : attributes.size(); - } - - public void trimToSize() { - if (attributes == null) { - return; - } - if (attributes.isEmpty()) { - attributes = null; - return; - } - if (attributes instanceof ArrayList) { - ArrayList al = (ArrayList)attributes; - al.trimToSize(); - boolean allCanon = true; - for (Attribute a : al) { - if (!a.isCanonical()) { - allCanon = false; - } - if (a.fixups != null) { - assert(!a.isCanonical()); - a.fixups = Fixups.trimToSize(a.fixups); - } - } - if (allCanon) { - // Replace private writable attribute list - // with only trivial entries by public unique - // immutable attribute list with the same entries. - attributes = getCanonList(al); - } - } - } - - public void addAttribute(Attribute a) { - if (attributes == null) - attributes = new ArrayList<>(3); - else if (!(attributes instanceof ArrayList)) - attributes = new ArrayList<>(attributes); // unfreeze it - attributes.add(a); - } - - public Attribute removeAttribute(Attribute a) { - if (attributes == null) return null; - if (!attributes.contains(a)) return null; - if (!(attributes instanceof ArrayList)) - attributes = new ArrayList<>(attributes); // unfreeze it - attributes.remove(a); - return a; - } - - public Attribute getAttribute(int n) { - return attributes.get(n); - } - - protected void visitRefs(int mode, Collection refs) { - if (attributes == null) return; - for (Attribute a : attributes) { - a.visitRefs(this, mode, refs); - } - } - - static final List noAttributes = Arrays.asList(new Attribute[0]); - - public List getAttributes() { - if (attributes == null) - return noAttributes; - return attributes; - } - - public void setAttributes(List attrList) { - if (attrList.isEmpty()) - attributes = null; - else - attributes = attrList; - } - - public Attribute getAttribute(String attrName) { - if (attributes == null) return null; - for (Attribute a : attributes) { - if (a.name().equals(attrName)) - return a; - } - return null; - } - - public Attribute getAttribute(Layout attrDef) { - if (attributes == null) return null; - for (Attribute a : attributes) { - if (a.layout() == attrDef) - return a; - } - return null; - } - - public Attribute removeAttribute(String attrName) { - return removeAttribute(getAttribute(attrName)); - } - - public Attribute removeAttribute(Layout attrDef) { - return removeAttribute(getAttribute(attrDef)); - } - - public void strip(String attrName) { - removeAttribute(getAttribute(attrName)); - } - } - - // Lightweight interface to hide details of band structure. - // Also used for testing. - public abstract static - class ValueStream { - public int getInt(int bandIndex) { throw undef(); } - public void putInt(int bandIndex, int value) { throw undef(); } - public ConstantPool.Entry getRef(int bandIndex) { throw undef(); } - public void putRef(int bandIndex, ConstantPool.Entry ref) { throw undef(); } - // Note: decodeBCI goes w/ getInt/Ref; encodeBCI goes w/ putInt/Ref - public int decodeBCI(int bciCode) { throw undef(); } - public int encodeBCI(int bci) { throw undef(); } - public void noteBackCall(int whichCallable) { /* ignore by default */ } - private RuntimeException undef() { - return new UnsupportedOperationException("ValueStream method"); - } - } - - // Element kinds: - static final byte EK_INT = 1; // B H I SH etc. - static final byte EK_BCI = 2; // PH POH etc. - static final byte EK_BCO = 3; // OH etc. - static final byte EK_FLAG = 4; // FH etc. - static final byte EK_REPL = 5; // NH[...] etc. - static final byte EK_REF = 6; // RUH, RUNH, KQH, etc. - static final byte EK_UN = 7; // TB(...)[...] etc. - static final byte EK_CASE = 8; // (...)[...] etc. - static final byte EK_CALL = 9; // (0), (1), etc. - static final byte EK_CBLE = 10; // [...][...] etc. - static final byte EF_SIGN = 1<<0; // INT is signed - static final byte EF_DELTA = 1<<1; // BCI/BCI value is diff'ed w/ previous - static final byte EF_NULL = 1<<2; // null REF is expected/allowed - static final byte EF_BACK = 1<<3; // call, callable, case is backward - static final int NO_BAND_INDEX = -1; - - /** A "class" of attributes, characterized by a context-type, name - * and format. The formats are specified in a "little language". - */ - public static - class Layout implements Comparable { - int ctype; // attribute context type, e.g., ATTR_CONTEXT_CODE - String name; // name of attribute - boolean hasRefs; // this kind of attr contains CP refs? - String layout; // layout specification - int bandCount; // total number of elems - Element[] elems; // tokenization of layout - Attribute canon; // canonical instance of this layout - - public int ctype() { return ctype; } - public String name() { return name; } - public String layout() { return layout; } - public Attribute canonicalInstance() { return canon; } - - public ConstantPool.Entry getNameRef() { - return ConstantPool.getUtf8Entry(name()); - } - - public boolean isEmpty() { - return layout.isEmpty(); - } - - public Layout(int ctype, String name, String layout) { - this.ctype = ctype; - this.name = name.intern(); - this.layout = layout.intern(); - assert(ctype < Constants.ATTR_CONTEXT_LIMIT); - boolean hasCallables = layout.startsWith("["); - try { - if (!hasCallables) { - this.elems = tokenizeLayout(this, -1, layout); - } else { - String[] bodies = splitBodies(layout); - // Make the callables now, so they can be linked immediately. - Element[] lelems = new Element[bodies.length]; - this.elems = lelems; - for (int i = 0; i < lelems.length; i++) { - Element ce = this.new Element(); - ce.kind = EK_CBLE; - ce.removeBand(); - ce.bandIndex = NO_BAND_INDEX; - ce.layout = bodies[i]; - lelems[i] = ce; - } - // Next fill them in. - for (int i = 0; i < lelems.length; i++) { - Element ce = lelems[i]; - ce.body = tokenizeLayout(this, i, bodies[i]); - } - //System.out.println(Arrays.asList(elems)); - } - } catch (StringIndexOutOfBoundsException ee) { - // simplest way to catch syntax errors... - throw new RuntimeException("Bad attribute layout: "+layout, ee); - } - // Some uses do not make a fresh one for each occurrence. - // For example, if layout == "", we only need one attr to share. - canon = new Attribute(this, Constants.noBytes); - } - private Layout() {} - static Layout makeKey(int ctype, String name, String layout) { - Layout def = new Layout(); - def.ctype = ctype; - def.name = name.intern(); - def.layout = layout.intern(); - assert(ctype < Constants.ATTR_CONTEXT_LIMIT); - return def; - } - static Layout makeKey(int ctype, String name) { - return makeKey(ctype, name, ""); - } - - public Attribute addContent(byte[] bytes, Object fixups) { - return canon.addContent(bytes, fixups); - } - public Attribute addContent(byte[] bytes) { - return canon.addContent(bytes, null); - } - - @Override - public boolean equals(Object x) { - return ( x != null) && ( x.getClass() == Layout.class ) && - equals((Layout)x); - } - public boolean equals(Layout that) { - return this.name.equals(that.name) - && this.layout.equals(that.layout) - && this.ctype == that.ctype; - } - @Override - public int hashCode() { - return (((17 + name.hashCode()) - * 37 + layout.hashCode()) - * 37 + ctype); - } - @Override - public int compareTo(Layout that) { - int r; - r = this.name.compareTo(that.name); - if (r != 0) return r; - r = this.layout.compareTo(that.layout); - if (r != 0) return r; - return this.ctype - that.ctype; - } - @Override - public String toString() { - String str = contextName(ctype)+"."+name+"["+layout+"]"; - // If -ea, print out more informative strings! - assert((str = stringForDebug()) != null); - return str; - } - private String stringForDebug() { - return contextName(ctype)+"."+name+Arrays.asList(elems); - } - - public - class Element { - String layout; // spelling in the little language - byte flags; // EF_SIGN, etc. - byte kind; // EK_UINT, etc. - byte len; // scalar length of element - byte refKind; // CONSTANT_String, etc. - int bandIndex; // which band does this element govern? - int value; // extra parameter - Element[] body; // extra data (for replications, unions, calls) - - boolean flagTest(byte mask) { return (flags & mask) != 0; } - - Element() { - bandIndex = bandCount++; - } - - void removeBand() { - --bandCount; - assert(bandIndex == bandCount); - bandIndex = NO_BAND_INDEX; - } - - public boolean hasBand() { - return bandIndex >= 0; - } - public String toString() { - String str = layout; - // If -ea, print out more informative strings! - assert((str = stringForDebug()) != null); - return str; - } - private String stringForDebug() { - Element[] lbody = this.body; - switch (kind) { - case EK_CALL: - lbody = null; - break; - case EK_CASE: - if (flagTest(EF_BACK)) - lbody = null; - break; - } - return layout - + (!hasBand()?"":"#"+bandIndex) - + "<"+ (flags==0?"":""+flags)+kind+len - + (refKind==0?"":""+refKind) + ">" - + (value==0?"":"("+value+")") - + (lbody==null?"": ""+Arrays.asList(lbody)); - } - } - - public boolean hasCallables() { - return (elems.length > 0 && elems[0].kind == EK_CBLE); - } - private static final Element[] noElems = {}; - public Element[] getCallables() { - if (hasCallables()) { - Element[] nelems = Arrays.copyOf(elems, elems.length); - return nelems; - } else - return noElems; // no callables at all - } - public Element[] getEntryPoint() { - if (hasCallables()) - return elems[0].body; // body of first callable - else { - Element[] nelems = Arrays.copyOf(elems, elems.length); - return nelems; // no callables; whole body - } - } - - /** Return a sequence of tokens from the given attribute bytes. - * Sequence elements will be 1-1 correspondent with my layout tokens. - */ - public void parse(Holder holder, - byte[] bytes, int pos, int len, ValueStream out) { - int end = parseUsing(getEntryPoint(), - holder, bytes, pos, len, out); - if (end != pos + len) - throw new InternalError("layout parsed "+(end-pos)+" out of "+len+" bytes"); - } - /** Given a sequence of tokens, return the attribute bytes. - * Sequence elements must be 1-1 correspondent with my layout tokens. - * The returned object is a cookie for Fixups.finishRefs, which - * must be used to harden any references into integer indexes. - */ - public Object unparse(ValueStream in, ByteArrayOutputStream out) { - Object[] fixups = { null }; - unparseUsing(getEntryPoint(), fixups, in, out); - return fixups[0]; // return ref-bearing cookie, if any - } - - public String layoutForClassVersion(Package.Version vers) { - if (vers.lessThan(Constants.JAVA6_MAX_CLASS_VERSION)) { - // Disallow layout syntax in the oldest protocol version. - return expandCaseDashNotation(layout); - } - return layout; - } - } - - public static - class FormatException extends IOException { - private static final long serialVersionUID = -2542243830788066513L; - - private int ctype; - private String name; - String layout; - public FormatException(String message, - int ctype, String name, String layout) { - super(Constants.ATTR_CONTEXT_NAME[ctype]+ " attribute \"" + name + "\"" + - (message == null? "" : (": " + message))); - this.ctype = ctype; - this.name = name; - this.layout = layout; - } - public FormatException(String message, - int ctype, String name) { - this(message, ctype, name, null); - } - } - - void visitRefs(Holder holder, int mode, final Collection refs) { - if (mode == Constants.VRM_CLASSIC) { - refs.add(getNameRef()); - } - // else the name is owned by the layout, and is processed elsewhere - if (bytes.length == 0) return; // quick exit - if (!def.hasRefs) return; // quick exit - if (fixups != null) { - Fixups.visitRefs(fixups, refs); - return; - } - // References (to a local cpMap) are embedded in the bytes. - def.parse(holder, bytes, 0, bytes.length, - new ValueStream() { - @Override - public void putInt(int bandIndex, int value) { - } - @Override - public void putRef(int bandIndex, ConstantPool.Entry ref) { - refs.add(ref); - } - @Override - public int encodeBCI(int bci) { - return bci; - } - }); - } - - public void parse(Holder holder, byte[] bytes, int pos, int len, ValueStream out) { - def.parse(holder, bytes, pos, len, out); - } - public Object unparse(ValueStream in, ByteArrayOutputStream out) { - return def.unparse(in, out); - } - - @Override - public String toString() { - return def - +"{"+(bytes == null ? -1 : size())+"}" - +(fixups == null? "": fixups.toString()); - } - - /** Remove any informal "pretty printing" from the layout string. - * Removes blanks and control chars. - * Removes '#' comments (to end of line). - * Replaces '\c' by the decimal code of the character c. - * Replaces '0xNNN' by the decimal code of the hex number NNN. - */ - public static - String normalizeLayoutString(String layout) { - StringBuilder buf = new StringBuilder(); - for (int i = 0, len = layout.length(); i < len; ) { - char ch = layout.charAt(i++); - if (ch <= ' ') { - // Skip whitespace and control chars - continue; - } else if (ch == '#') { - // Skip to end of line. - int end1 = layout.indexOf('\n', i); - int end2 = layout.indexOf('\r', i); - if (end1 < 0) end1 = len; - if (end2 < 0) end2 = len; - i = Math.min(end1, end2); - } else if (ch == '\\') { - // Map a character reference to its decimal code. - buf.append((int) layout.charAt(i++)); - } else if (ch == '0' && layout.startsWith("0x", i-1)) { - // Map a hex numeral to its decimal code. - int start = i-1; - int end = start+2; - while (end < len) { - int dig = layout.charAt(end); - if ((dig >= '0' && dig <= '9') || - (dig >= 'a' && dig <= 'f')) - ++end; - else - break; - } - if (end > start) { - String num = layout.substring(start, end); - buf.append(Integer.decode(num)); - i = end; - } else { - buf.append(ch); - } - } else { - buf.append(ch); - } - } - String result = buf.toString(); - if (false && !result.equals(layout)) { - Utils.log.info("Normalizing layout string"); - Utils.log.info(" From: "+layout); - Utils.log.info(" To: "+result); - } - return result; - } - - /// Subroutines for parsing and unparsing: - - /** Parse the attribute layout language. -

-  attribute_layout:
-        ( layout_element )* | ( callable )+
-  layout_element:
-        ( integral | replication | union | call | reference )
-
-  callable:
-        '[' body ']'
-  body:
-        ( layout_element )+
-
-  integral:
-        ( unsigned_int | signed_int | bc_index | bc_offset | flag )
-  unsigned_int:
-        uint_type
-  signed_int:
-        'S' uint_type
-  any_int:
-        ( unsigned_int | signed_int )
-  bc_index:
-        ( 'P' uint_type | 'PO' uint_type )
-  bc_offset:
-        'O' any_int
-  flag:
-        'F' uint_type
-  uint_type:
-        ( 'B' | 'H' | 'I' | 'V' )
-
-  replication:
-        'N' uint_type '[' body ']'
-
-  union:
-        'T' any_int (union_case)* '(' ')' '[' (body)? ']'
-  union_case:
-        '(' union_case_tag (',' union_case_tag)* ')' '[' (body)? ']'
-  union_case_tag:
-        ( numeral | numeral '-' numeral )
-  call:
-        '(' numeral ')'
-
-  reference:
-        reference_type ( 'N' )? uint_type
-  reference_type:
-        ( constant_ref | schema_ref | utf8_ref | untyped_ref )
-  constant_ref:
-        ( 'KI' | 'KJ' | 'KF' | 'KD' | 'KS' | 'KQ' | 'KM' | 'KT' | 'KL' )
-  schema_ref:
-        ( 'RC' | 'RS' | 'RD' | 'RF' | 'RM' | 'RI' | 'RY' | 'RB' | 'RN' )
-  utf8_ref:
-        'RU'
-  untyped_ref:
-        'RQ'
-
-  numeral:
-        '(' ('-')? (digit)+ ')'
-  digit:
-        ( '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' )
- 
- */ - static //private - Layout.Element[] tokenizeLayout(Layout self, int curCble, String layout) { - List col = new ArrayList<>(layout.length()); - tokenizeLayout(self, curCble, layout, col); - Layout.Element[] res = new Layout.Element[col.size()]; - col.toArray(res); - return res; - } - static //private - void tokenizeLayout(Layout self, int curCble, String layout, List col) { - boolean prevBCI = false; - for (int len = layout.length(), i = 0; i < len; ) { - int start = i; - int body; - Layout.Element e = self.new Element(); - byte kind; - //System.out.println("at "+i+": ..."+layout.substring(i)); - // strip a prefix - switch (layout.charAt(i++)) { - /// layout_element: integral - case 'B': case 'H': case 'I': case 'V': // unsigned_int - kind = EK_INT; - --i; // reparse - i = tokenizeUInt(e, layout, i); - break; - case 'S': // signed_int - kind = EK_INT; - --i; // reparse - i = tokenizeSInt(e, layout, i); - break; - case 'P': // bc_index - kind = EK_BCI; - if (layout.charAt(i++) == 'O') { - // bc_index: 'PO' tokenizeUInt - e.flags |= EF_DELTA; - // must follow P or PO: - if (!prevBCI) - { i = -i; continue; } // fail - i++; // move forward - } - --i; // reparse - i = tokenizeUInt(e, layout, i); - break; - case 'O': // bc_offset - kind = EK_BCO; - e.flags |= EF_DELTA; - // must follow P or PO: - if (!prevBCI) - { i = -i; continue; } // fail - i = tokenizeSInt(e, layout, i); - break; - case 'F': // flag - kind = EK_FLAG; - i = tokenizeUInt(e, layout, i); - break; - case 'N': // replication: 'N' uint '[' elem ... ']' - kind = EK_REPL; - i = tokenizeUInt(e, layout, i); - if (layout.charAt(i++) != '[') - { i = -i; continue; } // fail - i = skipBody(layout, body = i); - e.body = tokenizeLayout(self, curCble, - layout.substring(body, i++)); - break; - case 'T': // union: 'T' any_int union_case* '(' ')' '[' body ']' - kind = EK_UN; - i = tokenizeSInt(e, layout, i); - List cases = new ArrayList<>(); - for (;;) { - // Keep parsing cases until we hit the default case. - if (layout.charAt(i++) != '(') - { i = -i; break; } // fail - int beg = i; - i = layout.indexOf(')', i); - String cstr = layout.substring(beg, i++); - int cstrlen = cstr.length(); - if (layout.charAt(i++) != '[') - { i = -i; break; } // fail - // Check for duplication. - if (layout.charAt(i) == ']') - body = i; // missing body, which is legal here - else - i = skipBody(layout, body = i); - Layout.Element[] cbody - = tokenizeLayout(self, curCble, - layout.substring(body, i++)); - if (cstrlen == 0) { - Layout.Element ce = self.new Element(); - ce.body = cbody; - ce.kind = EK_CASE; - ce.removeBand(); - cases.add(ce); - break; // done with the whole union - } else { - // Parse a case string. - boolean firstCaseNum = true; - for (int cp = 0, endp;; cp = endp+1) { - // Look for multiple case tags: - endp = cstr.indexOf(',', cp); - if (endp < 0) endp = cstrlen; - String cstr1 = cstr.substring(cp, endp); - if (cstr1.isEmpty()) - cstr1 = "empty"; // will fail parse - int value0, value1; - // Check for a case range (new in 1.6). - int dash = findCaseDash(cstr1, 0); - if (dash >= 0) { - value0 = parseIntBefore(cstr1, dash); - value1 = parseIntAfter(cstr1, dash); - if (value0 >= value1) - { i = -i; break; } // fail - } else { - value0 = value1 = Integer.parseInt(cstr1); - } - // Add a case for each value in value0..value1 - for (;; value0++) { - Layout.Element ce = self.new Element(); - ce.body = cbody; // all cases share one body - ce.kind = EK_CASE; - ce.removeBand(); - if (!firstCaseNum) - // "backward case" repeats a body - ce.flags |= EF_BACK; - firstCaseNum = false; - ce.value = value0; - cases.add(ce); - if (value0 == value1) break; - } - if (endp == cstrlen) { - break; // done with this case - } - } - } - } - e.body = new Layout.Element[cases.size()]; - cases.toArray(e.body); - e.kind = kind; - for (int j = 0; j < e.body.length-1; j++) { - Layout.Element ce = e.body[j]; - if (matchCase(e, ce.value) != ce) { - // Duplicate tag. - { i = -i; break; } // fail - } - } - break; - case '(': // call: '(' '-'? digit+ ')' - kind = EK_CALL; - e.removeBand(); - i = layout.indexOf(')', i); - String cstr = layout.substring(start+1, i++); - int offset = Integer.parseInt(cstr); - int target = curCble + offset; - if (!(offset+"").equals(cstr) || - self.elems == null || - target < 0 || - target >= self.elems.length) - { i = -i; continue; } // fail - Layout.Element ce = self.elems[target]; - assert(ce.kind == EK_CBLE); - e.value = target; - e.body = new Layout.Element[]{ ce }; - // Is it a (recursive) backward call? - if (offset <= 0) { - // Yes. Mark both caller and callee backward. - e.flags |= EF_BACK; - ce.flags |= EF_BACK; - } - break; - case 'K': // reference_type: constant_ref - kind = EK_REF; - switch (layout.charAt(i++)) { - case 'I': e.refKind = Constants.CONSTANT_Integer; break; - case 'J': e.refKind = Constants.CONSTANT_Long; break; - case 'F': e.refKind = Constants.CONSTANT_Float; break; - case 'D': e.refKind = Constants.CONSTANT_Double; break; - case 'S': e.refKind = Constants.CONSTANT_String; break; - case 'Q': e.refKind = Constants.CONSTANT_FieldSpecific; break; - - // new in 1.7: - case 'M': e.refKind = Constants.CONSTANT_MethodHandle; break; - case 'T': e.refKind = Constants.CONSTANT_MethodType; break; - case 'L': e.refKind = Constants.CONSTANT_LoadableValue; break; - default: { i = -i; continue; } // fail - } - break; - case 'R': // schema_ref - kind = EK_REF; - switch (layout.charAt(i++)) { - case 'C': e.refKind = Constants.CONSTANT_Class; break; - case 'S': e.refKind = Constants.CONSTANT_Signature; break; - case 'D': e.refKind = Constants.CONSTANT_NameandType; break; - case 'F': e.refKind = Constants.CONSTANT_Fieldref; break; - case 'M': e.refKind = Constants.CONSTANT_Methodref; break; - case 'I': e.refKind = Constants.CONSTANT_InterfaceMethodref; break; - - case 'U': e.refKind = Constants.CONSTANT_Utf8; break; //utf8_ref - case 'Q': e.refKind = Constants.CONSTANT_All; break; //untyped_ref - - // new in 1.7: - case 'Y': e.refKind = Constants.CONSTANT_InvokeDynamic; break; - case 'B': e.refKind = Constants.CONSTANT_BootstrapMethod; break; - case 'N': e.refKind = Constants.CONSTANT_AnyMember; break; - - default: { i = -i; continue; } // fail - } - break; - default: { i = -i; continue; } // fail - } - - // further parsing of refs - if (kind == EK_REF) { - // reference: reference_type -><- ( 'N' )? tokenizeUInt - if (layout.charAt(i++) == 'N') { - e.flags |= EF_NULL; - i++; // move forward - } - --i; // reparse - i = tokenizeUInt(e, layout, i); - self.hasRefs = true; - } - - prevBCI = (kind == EK_BCI); - - // store the new element - e.kind = kind; - e.layout = layout.substring(start, i); - col.add(e); - } - } - static //private - String[] splitBodies(String layout) { - List bodies = new ArrayList<>(); - // Parse several independent layout bodies: "[foo][bar]...[baz]" - for (int i = 0; i < layout.length(); i++) { - if (layout.charAt(i++) != '[') - layout.charAt(-i); // throw error - int body; - i = skipBody(layout, body = i); - bodies.add(layout.substring(body, i)); - } - String[] res = new String[bodies.size()]; - bodies.toArray(res); - return res; - } - private static - int skipBody(String layout, int i) { - assert(layout.charAt(i-1) == '['); - if (layout.charAt(i) == ']') - // No empty bodies, please. - return -i; - // skip balanced [...[...]...] - for (int depth = 1; depth > 0; ) { - switch (layout.charAt(i++)) { - case '[': depth++; break; - case ']': depth--; break; - } - } - --i; // get before bracket - assert(layout.charAt(i) == ']'); - return i; // return closing bracket - } - private static - int tokenizeUInt(Layout.Element e, String layout, int i) { - switch (layout.charAt(i++)) { - case 'V': e.len = 0; break; - case 'B': e.len = 1; break; - case 'H': e.len = 2; break; - case 'I': e.len = 4; break; - default: return -i; - } - return i; - } - private static - int tokenizeSInt(Layout.Element e, String layout, int i) { - if (layout.charAt(i) == 'S') { - e.flags |= EF_SIGN; - ++i; - } - return tokenizeUInt(e, layout, i); - } - - private static - boolean isDigit(char c) { - return c >= '0' && c <= '9'; - } - - /** Find an occurrence of hyphen '-' between two numerals. */ - static //private - int findCaseDash(String layout, int fromIndex) { - if (fromIndex <= 0) fromIndex = 1; // minimum dash pos - int lastDash = layout.length() - 2; // maximum dash pos - for (;;) { - int dash = layout.indexOf('-', fromIndex); - if (dash < 0 || dash > lastDash) return -1; - if (isDigit(layout.charAt(dash-1))) { - char afterDash = layout.charAt(dash+1); - if (afterDash == '-' && dash+2 < layout.length()) - afterDash = layout.charAt(dash+2); - if (isDigit(afterDash)) { - // matched /[0-9]--?[0-9]/; return position of dash - return dash; - } - } - fromIndex = dash+1; - } - } - static - int parseIntBefore(String layout, int dash) { - int end = dash; - int beg = end; - while (beg > 0 && isDigit(layout.charAt(beg-1))) { - --beg; - } - if (beg == end) return Integer.parseInt("empty"); - // skip backward over a sign - if (beg >= 1 && layout.charAt(beg-1) == '-') --beg; - assert(beg == 0 || !isDigit(layout.charAt(beg-1))); - return Integer.parseInt(layout.substring(beg, end)); - } - static - int parseIntAfter(String layout, int dash) { - int beg = dash+1; - int end = beg; - int limit = layout.length(); - if (end < limit && layout.charAt(end) == '-') ++end; - while (end < limit && isDigit(layout.charAt(end))) { - ++end; - } - if (beg == end) return Integer.parseInt("empty"); - return Integer.parseInt(layout.substring(beg, end)); - } - /** For compatibility with 1.5 pack, expand 1-5 into 1,2,3,4,5. */ - static - String expandCaseDashNotation(String layout) { - int dash = findCaseDash(layout, 0); - if (dash < 0) return layout; // no dashes (the common case) - StringBuilder result = new StringBuilder(layout.length() * 3); - int sofar = 0; // how far have we processed the layout? - for (;;) { - // for each dash, collect everything up to the dash - result.append(layout, sofar, dash); - sofar = dash+1; // skip the dash - // then collect intermediate values - int value0 = parseIntBefore(layout, dash); - int value1 = parseIntAfter(layout, dash); - assert(value0 < value1); - result.append(","); // close off value0 numeral - for (int i = value0+1; i < value1; i++) { - result.append(i); - result.append(","); // close off i numeral - } - dash = findCaseDash(layout, sofar); - if (dash < 0) break; - } - result.append(layout, sofar, layout.length()); // collect the rest - return result.toString(); - } - static { - assert(expandCaseDashNotation("1-5").equals("1,2,3,4,5")); - assert(expandCaseDashNotation("-2--1").equals("-2,-1")); - assert(expandCaseDashNotation("-2-1").equals("-2,-1,0,1")); - assert(expandCaseDashNotation("-1-0").equals("-1,0")); - } - - // Parse attribute bytes, putting values into bands. Returns new pos. - // Used when reading a class file (local refs resolved with local cpMap). - // Also used for ad hoc scanning. - static - int parseUsing(Layout.Element[] elems, Holder holder, - byte[] bytes, int pos, int len, ValueStream out) { - int prevBCI = 0; - int prevRBCI = 0; - int end = pos + len; - int[] buf = { 0 }; // for calls to parseInt, holds 2nd result - for (int i = 0; i < elems.length; i++) { - Layout.Element e = elems[i]; - int bandIndex = e.bandIndex; - int value; - int BCI, RBCI; - switch (e.kind) { - case EK_INT: - pos = parseInt(e, bytes, pos, buf); - value = buf[0]; - out.putInt(bandIndex, value); - break; - case EK_BCI: // PH, POH - pos = parseInt(e, bytes, pos, buf); - BCI = buf[0]; - RBCI = out.encodeBCI(BCI); - if (!e.flagTest(EF_DELTA)) { - // PH: transmit R(bci), store bci - value = RBCI; - } else { - // POH: transmit D(R(bci)), store bci - value = RBCI - prevRBCI; - } - prevBCI = BCI; - prevRBCI = RBCI; - out.putInt(bandIndex, value); - break; - case EK_BCO: // OH - assert(e.flagTest(EF_DELTA)); - // OH: transmit D(R(bci)), store D(bci) - pos = parseInt(e, bytes, pos, buf); - BCI = prevBCI + buf[0]; - RBCI = out.encodeBCI(BCI); - value = RBCI - prevRBCI; - prevBCI = BCI; - prevRBCI = RBCI; - out.putInt(bandIndex, value); - break; - case EK_FLAG: - pos = parseInt(e, bytes, pos, buf); - value = buf[0]; - out.putInt(bandIndex, value); - break; - case EK_REPL: - pos = parseInt(e, bytes, pos, buf); - value = buf[0]; - out.putInt(bandIndex, value); - for (int j = 0; j < value; j++) { - pos = parseUsing(e.body, holder, bytes, pos, end-pos, out); - } - break; // already transmitted the scalar value - case EK_UN: - pos = parseInt(e, bytes, pos, buf); - value = buf[0]; - out.putInt(bandIndex, value); - Layout.Element ce = matchCase(e, value); - pos = parseUsing(ce.body, holder, bytes, pos, end-pos, out); - - break; // already transmitted the scalar value - case EK_CALL: - // Adjust band offset if it is a backward call. - assert(e.body.length == 1); - assert(e.body[0].kind == EK_CBLE); - if (e.flagTest(EF_BACK)) - out.noteBackCall(e.value); - pos = parseUsing(e.body[0].body, holder, bytes, pos, end-pos, out); - break; // no additional scalar value to transmit - case EK_REF: - pos = parseInt(e, bytes, pos, buf); - int localRef = buf[0]; - ConstantPool.Entry globalRef; - if (localRef == 0) { - globalRef = null; // N.B. global null reference is -1 - } else { - ConstantPool.Entry[] cpMap = holder.getCPMap(); - globalRef = (localRef >= 0 && localRef < cpMap.length - ? cpMap[localRef] - : null); - byte tag = e.refKind; - if (globalRef != null && tag == Constants.CONSTANT_Signature - && globalRef.getTag() == Constants.CONSTANT_Utf8) { - // Cf. ClassReader.readSignatureRef. - String typeName = globalRef.stringValue(); - globalRef = ConstantPool.getSignatureEntry(typeName); - } - String got = (globalRef == null - ? "invalid CP index" - : "type=" + ConstantPool.tagName(globalRef.tag)); - if (globalRef == null || !globalRef.tagMatches(tag)) { - throw new IllegalArgumentException( - "Bad constant, expected type=" + - ConstantPool.tagName(tag) + " got " + got); - } - } - out.putRef(bandIndex, globalRef); - break; - default: assert(false); - } - } - return pos; - } - - static - Layout.Element matchCase(Layout.Element e, int value) { - assert(e.kind == EK_UN); - int lastj = e.body.length-1; - for (int j = 0; j < lastj; j++) { - Layout.Element ce = e.body[j]; - assert(ce.kind == EK_CASE); - if (value == ce.value) - return ce; - } - return e.body[lastj]; - } - - private static - int parseInt(Layout.Element e, byte[] bytes, int pos, int[] buf) { - int value = 0; - int loBits = e.len * 8; - // Read in big-endian order: - for (int bitPos = loBits; (bitPos -= 8) >= 0; ) { - value += (bytes[pos++] & 0xFF) << bitPos; - } - if (loBits < 32 && e.flagTest(EF_SIGN)) { - // sign-extend subword value - int hiBits = 32 - loBits; - value = (value << hiBits) >> hiBits; - } - buf[0] = value; - return pos; - } - - // Format attribute bytes, drawing values from bands. - // Used when emptying attribute bands into a package model. - // (At that point CP refs. are not yet assigned indexes.) - static - void unparseUsing(Layout.Element[] elems, Object[] fixups, - ValueStream in, ByteArrayOutputStream out) { - int prevBCI = 0; - int prevRBCI = 0; - for (int i = 0; i < elems.length; i++) { - Layout.Element e = elems[i]; - int bandIndex = e.bandIndex; - int value; - int BCI, RBCI; // "RBCI" is R(BCI), BCI's coded representation - switch (e.kind) { - case EK_INT: - value = in.getInt(bandIndex); - unparseInt(e, value, out); - break; - case EK_BCI: // PH, POH - value = in.getInt(bandIndex); - if (!e.flagTest(EF_DELTA)) { - // PH: transmit R(bci), store bci - RBCI = value; - } else { - // POH: transmit D(R(bci)), store bci - RBCI = prevRBCI + value; - } - assert(prevBCI == in.decodeBCI(prevRBCI)); - BCI = in.decodeBCI(RBCI); - unparseInt(e, BCI, out); - prevBCI = BCI; - prevRBCI = RBCI; - break; - case EK_BCO: // OH - value = in.getInt(bandIndex); - assert(e.flagTest(EF_DELTA)); - // OH: transmit D(R(bci)), store D(bci) - assert(prevBCI == in.decodeBCI(prevRBCI)); - RBCI = prevRBCI + value; - BCI = in.decodeBCI(RBCI); - unparseInt(e, BCI - prevBCI, out); - prevBCI = BCI; - prevRBCI = RBCI; - break; - case EK_FLAG: - value = in.getInt(bandIndex); - unparseInt(e, value, out); - break; - case EK_REPL: - value = in.getInt(bandIndex); - unparseInt(e, value, out); - for (int j = 0; j < value; j++) { - unparseUsing(e.body, fixups, in, out); - } - break; - case EK_UN: - value = in.getInt(bandIndex); - unparseInt(e, value, out); - Layout.Element ce = matchCase(e, value); - unparseUsing(ce.body, fixups, in, out); - break; - case EK_CALL: - assert(e.body.length == 1); - assert(e.body[0].kind == EK_CBLE); - unparseUsing(e.body[0].body, fixups, in, out); - break; - case EK_REF: - ConstantPool.Entry globalRef = in.getRef(bandIndex); - int localRef; - if (globalRef != null) { - // It's a one-element array, really an lvalue. - fixups[0] = Fixups.addRefWithLoc(fixups[0], out.size(), globalRef); - localRef = 0; // placeholder for fixups - } else { - localRef = 0; // fixed null value - } - unparseInt(e, localRef, out); - break; - default: assert(false); continue; - } - } - } - - private static - void unparseInt(Layout.Element e, int value, ByteArrayOutputStream out) { - int loBits = e.len * 8; - if (loBits == 0) { - // It is not stored at all ('V' layout). - return; - } - if (loBits < 32) { - int hiBits = 32 - loBits; - int codedValue; - if (e.flagTest(EF_SIGN)) - codedValue = (value << hiBits) >> hiBits; - else - codedValue = (value << hiBits) >>> hiBits; - if (codedValue != value) - throw new InternalError("cannot code in "+e.len+" bytes: "+value); - } - // Write in big-endian order: - for (int bitPos = loBits; (bitPos -= 8) >= 0; ) { - out.write((byte)(value >>> bitPos)); - } - } - -/* - /// Testing. - public static void main(String av[]) { - int maxVal = 12; - int iters = 0; - boolean verbose; - int ap = 0; - while (ap < av.length) { - if (!av[ap].startsWith("-")) break; - if (av[ap].startsWith("-m")) - maxVal = Integer.parseInt(av[ap].substring(2)); - else if (av[ap].startsWith("-i")) - iters = Integer.parseInt(av[ap].substring(2)); - else - throw new RuntimeException("Bad option: "+av[ap]); - ap++; - } - verbose = (iters == 0); - if (iters <= 0) iters = 1; - if (ap == av.length) { - av = new String[] { - "HH", // ClassFile.version - "RUH", // SourceFile - "RCHRDNH", // EnclosingMethod - "KQH", // ConstantValue - "NH[RCH]", // Exceptions - "NH[PHH]", // LineNumberTable - "NH[PHOHRUHRSHH]", // LocalVariableTable - "NH[PHPOHIIH]", // CharacterRangeTable - "NH[PHHII]", // CoverageTable - "NH[RCHRCNHRUNHFH]", // InnerClasses - "NH[RMHNH[KLH]]", // BootstrapMethods - "HHNI[B]NH[PHPOHPOHRCNH]NH[RUHNI[B]]", // Code - "=AnnotationDefault", - // Like metadata, but with a compact tag set: - "[NH[(1)]]" - +"[NH[(1)]]" - +"[RSHNH[RUH(1)]]" - +"[TB(0,1,3)[KIH](2)[KDH](5)[KFH](4)[KJH](7)[RSH](8)[RSHRUH](9)[RUH](10)[(-1)](6)[NH[(0)]]()[]]", - "" - }; - ap = 0; - } - Utils.currentInstance.set(new PackerImpl()); - final int[][] counts = new int[2][3]; // int bci ref - final Entry[] cpMap = new Entry[maxVal+1]; - for (int i = 0; i < cpMap.length; i++) { - if (i == 0) continue; // 0 => null - cpMap[i] = ConstantPool.getLiteralEntry(new Integer(i)); - } - Package.Class cls = new Package().new Class(""); - cls.cpMap = cpMap; - class TestValueStream extends ValueStream { - java.util.Random rand = new java.util.Random(0); - ArrayList history = new ArrayList(); - int ckidx = 0; - int maxVal; - boolean verbose; - void reset() { history.clear(); ckidx = 0; } - public int getInt(int bandIndex) { - counts[0][0]++; - int value = rand.nextInt(maxVal+1); - history.add(new Integer(bandIndex)); - history.add(new Integer(value)); - return value; - } - public void putInt(int bandIndex, int token) { - counts[1][0]++; - if (verbose) - System.out.print(" "+bandIndex+":"+token); - // Make sure this put parallels a previous get: - int check0 = ((Integer)history.get(ckidx+0)).intValue(); - int check1 = ((Integer)history.get(ckidx+1)).intValue(); - if (check0 != bandIndex || check1 != token) { - if (!verbose) - System.out.println(history.subList(0, ckidx)); - System.out.println(" *** Should be "+check0+":"+check1); - throw new RuntimeException("Failed test!"); - } - ckidx += 2; - } - public Entry getRef(int bandIndex) { - counts[0][2]++; - int value = getInt(bandIndex); - if (value < 0 || value > maxVal) { - System.out.println(" *** Unexpected ref code "+value); - return ConstantPool.getLiteralEntry(new Integer(value)); - } - return cpMap[value]; - } - public void putRef(int bandIndex, Entry ref) { - counts[1][2]++; - if (ref == null) { - putInt(bandIndex, 0); - return; - } - Number refValue = null; - if (ref instanceof ConstantPool.NumberEntry) - refValue = ((ConstantPool.NumberEntry)ref).numberValue(); - int value; - if (!(refValue instanceof Integer)) { - System.out.println(" *** Unexpected ref "+ref); - value = -1; - } else { - value = ((Integer)refValue).intValue(); - } - putInt(bandIndex, value); - } - public int encodeBCI(int bci) { - counts[1][1]++; - // move LSB to MSB of low byte - int code = (bci >> 8) << 8; // keep high bits - code += (bci & 0xFE) >> 1; - code += (bci & 0x01) << 7; - return code ^ (8<<8); // mark it clearly as coded - } - public int decodeBCI(int bciCode) { - counts[0][1]++; - bciCode ^= (8<<8); // remove extra mark - int bci = (bciCode >> 8) << 8; // keep high bits - bci += (bciCode & 0x7F) << 1; - bci += (bciCode & 0x80) >> 7; - return bci; - } - } - TestValueStream tts = new TestValueStream(); - tts.maxVal = maxVal; - tts.verbose = verbose; - ByteArrayOutputStream buf = new ByteArrayOutputStream(); - for (int i = 0; i < (1 << 30); i = (i + 1) * 5) { - int ei = tts.encodeBCI(i); - int di = tts.decodeBCI(ei); - if (di != i) System.out.println("i="+Integer.toHexString(i)+ - " ei="+Integer.toHexString(ei)+ - " di="+Integer.toHexString(di)); - } - while (iters-- > 0) { - for (int i = ap; i < av.length; i++) { - String layout = av[i]; - if (layout.startsWith("=")) { - String name = layout.substring(1); - for (Attribute a : standardDefs.values()) { - if (a.name().equals(name)) { - layout = a.layout().layout(); - break; - } - } - if (layout.startsWith("=")) { - System.out.println("Could not find "+name+" in "+standardDefs.values()); - } - } - Layout self = new Layout(0, "Foo", layout); - if (verbose) { - System.out.print("/"+layout+"/ => "); - System.out.println(Arrays.asList(self.elems)); - } - buf.reset(); - tts.reset(); - Object fixups = self.unparse(tts, buf); - byte[] bytes = buf.toByteArray(); - // Attach the references to the byte array. - Fixups.setBytes(fixups, bytes); - // Patch the references to their frozen values. - Fixups.finishRefs(fixups, bytes, new Index("test", cpMap)); - if (verbose) { - System.out.print(" bytes: {"); - for (int j = 0; j < bytes.length; j++) { - System.out.print(" "+bytes[j]); - } - System.out.println("}"); - } - if (verbose) { - System.out.print(" parse: {"); - } - self.parse(cls, bytes, 0, bytes.length, tts); - if (verbose) { - System.out.println("}"); - } - } - } - for (int j = 0; j <= 1; j++) { - System.out.print("values "+(j==0?"read":"written")+": {"); - for (int k = 0; k < counts[j].length; k++) { - System.out.print(" "+counts[j][k]); - } - System.out.println(" }"); - } - } -//*/ -} diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/BandStructure.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/BandStructure.java deleted file mode 100644 index d099b6428..000000000 --- a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/BandStructure.java +++ /dev/null @@ -1,2759 +0,0 @@ -/* - * Copyright (c) 2001, 2013, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package net.fabricmc.shade.com.sun.java.util.jar.pack; - -import java.io.BufferedOutputStream; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.EOFException; -import java.io.File; -import java.io.FileOutputStream; -import java.io.FilterInputStream; -import java.io.FilterOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.PrintStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import net.fabricmc.shade.java.util.jar.Pack200; - -import java.util.LinkedList; - -/** - * Define the structure and ordering of "bands" in a packed file. - * @author John Rose - */ -@SuppressWarnings({"removal"}) -abstract -class BandStructure { - static final int MAX_EFFORT = 9; - static final int MIN_EFFORT = 1; - static final int DEFAULT_EFFORT = 5; - - // Inherit options from Pack200: - PropMap p200 = Utils.currentPropMap(); - - int verbose = p200.getInteger(Utils.DEBUG_VERBOSE); - int effort = p200.getInteger(Pack200.Packer.EFFORT); - { if (effort == 0) effort = DEFAULT_EFFORT; } - boolean optDumpBands = p200.getBoolean(Utils.COM_PREFIX+"dump.bands"); - boolean optDebugBands = p200.getBoolean(Utils.COM_PREFIX+"debug.bands"); - - // Various heuristic options. - boolean optVaryCodings = !p200.getBoolean(Utils.COM_PREFIX+"no.vary.codings"); - boolean optBigStrings = !p200.getBoolean(Utils.COM_PREFIX+"no.big.strings"); - - protected abstract ConstantPool.Index getCPIndex(byte tag); - - // Local copy of highest class version. - private Package.Version highestClassVersion = null; - - /** Call this exactly once, early, to specify the archive major version. */ - public void initHighestClassVersion(Package.Version highestClassVersion) throws IOException { - if (this.highestClassVersion != null) { - throw new IOException( - "Highest class major version is already initialized to " + - this.highestClassVersion + "; new setting is " + highestClassVersion); - } - this.highestClassVersion = highestClassVersion; - adjustToClassVersion(); - } - - public Package.Version getHighestClassVersion() { - return highestClassVersion; - } - - private final boolean isReader = this instanceof PackageReader; - - protected BandStructure() {} - - static final Coding BYTE1 = Coding.of(1,256); - - static final Coding CHAR3 = Coding.of(3,128); - // Note: Tried sharper (3,16) with no post-zip benefit. - - // This is best used with BCI values: - static final Coding BCI5 = Coding.of(5,4); // mostly 1-byte offsets - static final Coding BRANCH5 = Coding.of(5,4,2); // mostly forward branches - - static final Coding UNSIGNED5 = Coding.of(5,64); - static final Coding UDELTA5 = UNSIGNED5.getDeltaCoding(); - // "sharp" (5,64) zips 0.4% better than "medium" (5,128) - // It zips 1.1% better than "flat" (5,192) - - static final Coding SIGNED5 = Coding.of(5,64,1); //sharp - static final Coding DELTA5 = SIGNED5.getDeltaCoding(); - // Note: Tried (5,128,2) and (5,192,2) with no benefit. - - static final Coding MDELTA5 = Coding.of(5,64,2).getDeltaCoding(); - - private static final Coding[] basicCodings = { - // Table of "Canonical BHSD Codings" from Pack200 spec. - null, // _meta_default - - // Fixed-length codings: - Coding.of(1,256,0), - Coding.of(1,256,1), - Coding.of(1,256,0).getDeltaCoding(), - Coding.of(1,256,1).getDeltaCoding(), - Coding.of(2,256,0), - Coding.of(2,256,1), - Coding.of(2,256,0).getDeltaCoding(), - Coding.of(2,256,1).getDeltaCoding(), - Coding.of(3,256,0), - Coding.of(3,256,1), - Coding.of(3,256,0).getDeltaCoding(), - Coding.of(3,256,1).getDeltaCoding(), - Coding.of(4,256,0), - Coding.of(4,256,1), - Coding.of(4,256,0).getDeltaCoding(), - Coding.of(4,256,1).getDeltaCoding(), - - // Full-range variable-length codings: - Coding.of(5, 4,0), - Coding.of(5, 4,1), - Coding.of(5, 4,2), - Coding.of(5, 16,0), - Coding.of(5, 16,1), - Coding.of(5, 16,2), - Coding.of(5, 32,0), - Coding.of(5, 32,1), - Coding.of(5, 32,2), - Coding.of(5, 64,0), - Coding.of(5, 64,1), - Coding.of(5, 64,2), - Coding.of(5,128,0), - Coding.of(5,128,1), - Coding.of(5,128,2), - - Coding.of(5, 4,0).getDeltaCoding(), - Coding.of(5, 4,1).getDeltaCoding(), - Coding.of(5, 4,2).getDeltaCoding(), - Coding.of(5, 16,0).getDeltaCoding(), - Coding.of(5, 16,1).getDeltaCoding(), - Coding.of(5, 16,2).getDeltaCoding(), - Coding.of(5, 32,0).getDeltaCoding(), - Coding.of(5, 32,1).getDeltaCoding(), - Coding.of(5, 32,2).getDeltaCoding(), - Coding.of(5, 64,0).getDeltaCoding(), - Coding.of(5, 64,1).getDeltaCoding(), - Coding.of(5, 64,2).getDeltaCoding(), - Coding.of(5,128,0).getDeltaCoding(), - Coding.of(5,128,1).getDeltaCoding(), - Coding.of(5,128,2).getDeltaCoding(), - - // Variable length subrange codings: - Coding.of(2,192,0), - Coding.of(2,224,0), - Coding.of(2,240,0), - Coding.of(2,248,0), - Coding.of(2,252,0), - - Coding.of(2, 8,0).getDeltaCoding(), - Coding.of(2, 8,1).getDeltaCoding(), - Coding.of(2, 16,0).getDeltaCoding(), - Coding.of(2, 16,1).getDeltaCoding(), - Coding.of(2, 32,0).getDeltaCoding(), - Coding.of(2, 32,1).getDeltaCoding(), - Coding.of(2, 64,0).getDeltaCoding(), - Coding.of(2, 64,1).getDeltaCoding(), - Coding.of(2,128,0).getDeltaCoding(), - Coding.of(2,128,1).getDeltaCoding(), - Coding.of(2,192,0).getDeltaCoding(), - Coding.of(2,192,1).getDeltaCoding(), - Coding.of(2,224,0).getDeltaCoding(), - Coding.of(2,224,1).getDeltaCoding(), - Coding.of(2,240,0).getDeltaCoding(), - Coding.of(2,240,1).getDeltaCoding(), - Coding.of(2,248,0).getDeltaCoding(), - Coding.of(2,248,1).getDeltaCoding(), - - Coding.of(3,192,0), - Coding.of(3,224,0), - Coding.of(3,240,0), - Coding.of(3,248,0), - Coding.of(3,252,0), - - Coding.of(3, 8,0).getDeltaCoding(), - Coding.of(3, 8,1).getDeltaCoding(), - Coding.of(3, 16,0).getDeltaCoding(), - Coding.of(3, 16,1).getDeltaCoding(), - Coding.of(3, 32,0).getDeltaCoding(), - Coding.of(3, 32,1).getDeltaCoding(), - Coding.of(3, 64,0).getDeltaCoding(), - Coding.of(3, 64,1).getDeltaCoding(), - Coding.of(3,128,0).getDeltaCoding(), - Coding.of(3,128,1).getDeltaCoding(), - Coding.of(3,192,0).getDeltaCoding(), - Coding.of(3,192,1).getDeltaCoding(), - Coding.of(3,224,0).getDeltaCoding(), - Coding.of(3,224,1).getDeltaCoding(), - Coding.of(3,240,0).getDeltaCoding(), - Coding.of(3,240,1).getDeltaCoding(), - Coding.of(3,248,0).getDeltaCoding(), - Coding.of(3,248,1).getDeltaCoding(), - - Coding.of(4,192,0), - Coding.of(4,224,0), - Coding.of(4,240,0), - Coding.of(4,248,0), - Coding.of(4,252,0), - - Coding.of(4, 8,0).getDeltaCoding(), - Coding.of(4, 8,1).getDeltaCoding(), - Coding.of(4, 16,0).getDeltaCoding(), - Coding.of(4, 16,1).getDeltaCoding(), - Coding.of(4, 32,0).getDeltaCoding(), - Coding.of(4, 32,1).getDeltaCoding(), - Coding.of(4, 64,0).getDeltaCoding(), - Coding.of(4, 64,1).getDeltaCoding(), - Coding.of(4,128,0).getDeltaCoding(), - Coding.of(4,128,1).getDeltaCoding(), - Coding.of(4,192,0).getDeltaCoding(), - Coding.of(4,192,1).getDeltaCoding(), - Coding.of(4,224,0).getDeltaCoding(), - Coding.of(4,224,1).getDeltaCoding(), - Coding.of(4,240,0).getDeltaCoding(), - Coding.of(4,240,1).getDeltaCoding(), - Coding.of(4,248,0).getDeltaCoding(), - Coding.of(4,248,1).getDeltaCoding(), - - null - }; - private static final Map basicCodingIndexes; - static { - assert(basicCodings[Constants._meta_default] == null); - assert(basicCodings[Constants._meta_canon_min] != null); - assert(basicCodings[Constants._meta_canon_max] != null); - Map map = new HashMap<>(); - for (int i = 0; i < basicCodings.length; i++) { - Coding c = basicCodings[i]; - if (c == null) continue; - assert(i >= Constants._meta_canon_min); - assert(i <= Constants._meta_canon_max); - map.put(c, i); - } - basicCodingIndexes = map; - } - public static Coding codingForIndex(int i) { - return i < basicCodings.length ? basicCodings[i] : null; - } - public static int indexOf(Coding c) { - Integer i = basicCodingIndexes.get(c); - if (i == null) return 0; - return i.intValue(); - } - public static Coding[] getBasicCodings() { - return basicCodings.clone(); - } - - protected byte[] bandHeaderBytes; // used for input only - protected int bandHeaderBytePos; // BHB read pointer, for input only - protected int bandHeaderBytePos0; // for debug - - protected CodingMethod getBandHeader(int XB, Coding regularCoding) { - CodingMethod[] res = {null}; - // push back XB onto the band header bytes - bandHeaderBytes[--bandHeaderBytePos] = (byte) XB; - bandHeaderBytePos0 = bandHeaderBytePos; - // scan forward through XB and any additional band header bytes - bandHeaderBytePos = parseMetaCoding(bandHeaderBytes, - bandHeaderBytePos, - regularCoding, - res); - return res[0]; - } - - public static int parseMetaCoding(byte[] bytes, int pos, Coding dflt, CodingMethod[] res) { - if ((bytes[pos] & 0xFF) == Constants._meta_default) { - res[0] = dflt; - return pos+1; - } - int pos2; - pos2 = Coding.parseMetaCoding(bytes, pos, dflt, res); - if (pos2 > pos) return pos2; - pos2 = PopulationCoding.parseMetaCoding(bytes, pos, dflt, res); - if (pos2 > pos) return pos2; - pos2 = AdaptiveCoding.parseMetaCoding(bytes, pos, dflt, res); - if (pos2 > pos) return pos2; - throw new RuntimeException("Bad meta-coding op "+(bytes[pos]&0xFF)); - } - - static final int SHORT_BAND_HEURISTIC = 100; - - public static final int NO_PHASE = 0; - - // package writing phases: - public static final int COLLECT_PHASE = 1; // collect data before write - public static final int FROZEN_PHASE = 3; // no longer collecting - public static final int WRITE_PHASE = 5; // ready to write bytes - - // package reading phases: - public static final int EXPECT_PHASE = 2; // gather expected counts - public static final int READ_PHASE = 4; // ready to read bytes - public static final int DISBURSE_PHASE = 6; // pass out data after read - - public static final int DONE_PHASE = 8; // done writing or reading - - static boolean phaseIsRead(int p) { - return (p % 2) == 0; - } - static int phaseCmp(int p0, int p1) { - assert((p0 % 2) == (p1 % 2) || (p0 % 8) == 0 || (p1 % 8) == 0); - return p0 - p1; - } - - /** The packed file is divided up into a number of segments. - * Most segments are typed as ValueBand, strongly-typed sequences - * of integer values, all interpreted in a single way. - * A few segments are ByteBands, which hetergeneous sequences - * of bytes. - * - * The two phases for writing a packed file are COLLECT and WRITE. - * 1. When writing a packed file, each band collects - * data in an ad-hoc order. - * 2. At the end, each band is assigned a coding scheme, - * and then all the bands are written in their global order. - * - * The three phases for reading a packed file are EXPECT, READ, - * and DISBURSE. - * 1. For each band, the expected number of integers is determined. - * 2. The data is actually read from the file into the band. - * 3. The band pays out its values as requested, in an ad hoc order. - * - * When the last phase of a band is done, it is marked so (DONE). - * Clearly, these phases must be properly ordered WRT each other. - */ - abstract class Band { - private int phase = NO_PHASE; - private final String name; - - private int valuesExpected; - - protected long outputSize = -1; // cache - - public final Coding regularCoding; - - public final int seqForDebug; - public int elementCountForDebug; - - - protected Band(String name, Coding regularCoding) { - this.name = name; - this.regularCoding = regularCoding; - this.seqForDebug = ++nextSeqForDebug; - if (verbose > 2) - Utils.log.fine("Band "+seqForDebug+" is "+name); - // caller must call init - } - - public Band init() { - // Cannot due this from the constructor, because constructor - // may wish to initialize some subclass variables. - // Set initial phase for reading or writing: - if (isReader) - readyToExpect(); - else - readyToCollect(); - return this; - } - - // common operations - boolean isReader() { return isReader; } - int phase() { return phase; } - String name() { return name; } - - /** Return -1 if data buffer not allocated, else max length. */ - public abstract int capacity(); - - /** Allocate data buffer to specified length. */ - protected abstract void setCapacity(int cap); - - /** Return current number of values in buffer, which must exist. */ - public abstract int length(); - - protected abstract int valuesRemainingForDebug(); - - public final int valuesExpected() { - return valuesExpected; - } - - /** Write out bytes, encoding the values. */ - public final void writeTo(OutputStream out) throws IOException { - assert(assertReadyToWriteTo(this, out)); - setPhase(WRITE_PHASE); - // subclasses continue by writing their contents to output - writeDataTo(out); - doneWriting(); - } - - abstract void chooseBandCodings() throws IOException; - - public final long outputSize() { - if (outputSize >= 0) { - long size = outputSize; - assert(size == computeOutputSize()); - return size; - } - return computeOutputSize(); - } - - protected abstract long computeOutputSize(); - - protected abstract void writeDataTo(OutputStream out) throws IOException; - - /** Expect a certain number of values. */ - void expectLength(int l) { - assert(assertPhase(this, EXPECT_PHASE)); - assert(valuesExpected == 0); // all at once - assert(l >= 0); - valuesExpected = l; - } - /** Expect more values. (Multiple calls accumulate.) */ - void expectMoreLength(int l) { - assert(assertPhase(this, EXPECT_PHASE)); - valuesExpected += l; - } - - - /// Phase change markers. - - private void readyToCollect() { // called implicitly by constructor - setCapacity(1); - setPhase(COLLECT_PHASE); - } - protected void doneWriting() { - assert(assertPhase(this, WRITE_PHASE)); - setPhase(DONE_PHASE); - } - private void readyToExpect() { // called implicitly by constructor - setPhase(EXPECT_PHASE); - } - /** Read in bytes, decoding the values. */ - public final void readFrom(InputStream in) throws IOException { - assert(assertReadyToReadFrom(this, in)); - setCapacity(valuesExpected()); - setPhase(READ_PHASE); - // subclasses continue by reading their contents from input: - readDataFrom(in); - readyToDisburse(); - } - protected abstract void readDataFrom(InputStream in) throws IOException; - protected void readyToDisburse() { - if (verbose > 1) Utils.log.fine("readyToDisburse "+this); - setPhase(DISBURSE_PHASE); - } - public void doneDisbursing() { - assert(assertPhase(this, DISBURSE_PHASE)); - setPhase(DONE_PHASE); - } - public final void doneWithUnusedBand() { - if (isReader) { - assert(assertPhase(this, EXPECT_PHASE)); - assert(valuesExpected() == 0); - // Fast forward: - setPhase(READ_PHASE); - setPhase(DISBURSE_PHASE); - setPhase(DONE_PHASE); - } else { - setPhase(FROZEN_PHASE); - } - } - - protected void setPhase(int newPhase) { - assert(assertPhaseChangeOK(this, phase, newPhase)); - this.phase = newPhase; - } - - protected int lengthForDebug = -1; // DEBUG ONLY - @Override - public String toString() { // DEBUG ONLY - int length = (lengthForDebug != -1 ? lengthForDebug : length()); - String str = name; - if (length != 0) - str += "[" + length + "]"; - if (elementCountForDebug != 0) - str += "(" + elementCountForDebug + ")"; - return str; - } - } - - class ValueBand extends Band { - private int[] values; // must be null in EXPECT phase - private int length; - private int valuesDisbursed; - - private CodingMethod bandCoding; - private byte[] metaCoding; - - protected ValueBand(String name, Coding regularCoding) { - super(name, regularCoding); - } - - @Override - public int capacity() { - return values == null ? -1 : values.length; - } - - /** Declare predicted or needed capacity. */ - @Override - protected void setCapacity(int cap) { - assert(length <= cap); - if (cap == -1) { values = null; return; } - values = realloc(values, cap); - } - - @Override - public int length() { - return length; - } - @Override - protected int valuesRemainingForDebug() { - return length - valuesDisbursed; - } - protected int valueAtForDebug(int i) { - return values[i]; - } - - void patchValue(int i, int value) { - // Only one use for this. - assert(this == archive_header_S); - assert(i == AH_ARCHIVE_SIZE_HI || i == AH_ARCHIVE_SIZE_LO); - assert(i < length); // must have already output a dummy - values[i] = value; - outputSize = -1; // decache - } - - protected void initializeValues(int[] values) { - assert(assertCanChangeLength(this)); - assert(length == 0); - this.values = values; - this.length = values.length; - } - - /** Collect one value, or store one decoded value. */ - protected void addValue(int x) { - assert(assertCanChangeLength(this)); - if (length == values.length) - setCapacity(length < 1000 ? length * 10 : length * 2); - values[length++] = x; - } - - private boolean canVaryCoding() { - if (!optVaryCodings) return false; - if (length == 0) return false; - // Can't read band_headers w/o the archive header: - if (this == archive_header_0) return false; - if (this == archive_header_S) return false; - if (this == archive_header_1) return false; - // BYTE1 bands can't vary codings, but the others can. - // All that's needed for the initial escape is at least - // 256 negative values or more than 256 non-negative values - return (regularCoding.min() <= -256 || regularCoding.max() >= 256); - } - - private boolean shouldVaryCoding() { - assert(canVaryCoding()); - if (effort < MAX_EFFORT && length < SHORT_BAND_HEURISTIC) - return false; - return true; - } - - @Override - protected void chooseBandCodings() throws IOException { - boolean canVary = canVaryCoding(); - if (!canVary || !shouldVaryCoding()) { - if (regularCoding.canRepresent(values, 0, length)) { - bandCoding = regularCoding; - } else { - assert(canVary); - if (verbose > 1) - Utils.log.fine("regular coding fails in band "+name()); - bandCoding = UNSIGNED5; - } - outputSize = -1; - } else { - int[] sizes = {0,0}; - bandCoding = chooseCoding(values, 0, length, - regularCoding, name(), - sizes); - outputSize = sizes[CodingChooser.BYTE_SIZE]; - if (outputSize == 0) // CodingChooser failed to size it. - outputSize = -1; - } - - // Compute and save the meta-coding bytes also. - if (bandCoding != regularCoding) { - metaCoding = bandCoding.getMetaCoding(regularCoding); - if (verbose > 1) { - Utils.log.fine("alternate coding "+this+" "+bandCoding); - } - } else if (canVary && - decodeEscapeValue(values[0], regularCoding) >= 0) { - // Need an explicit default. - metaCoding = defaultMetaCoding; - } else { - // Common case: Zero bytes of meta coding. - metaCoding = noMetaCoding; - } - if (metaCoding.length > 0 - && (verbose > 2 || verbose > 1 && metaCoding.length > 1)) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < metaCoding.length; i++) { - if (i == 1) sb.append(" /"); - sb.append(" ").append(metaCoding[i] & 0xFF); - } - Utils.log.fine(" meta-coding "+sb); - } - - assert((outputSize < 0) || - !(bandCoding instanceof Coding) || - (outputSize == ((Coding)bandCoding) - .getLength(values, 0, length))) - : (bandCoding+" : "+ - outputSize+" != "+ - ((Coding)bandCoding).getLength(values, 0, length) - +" ?= "+getCodingChooser().computeByteSize(bandCoding,values,0,length) - ); - - // Compute outputSize of the escape value X, if any. - if (metaCoding.length > 0) { - // First byte XB of meta-coding is treated specially, - // but any other bytes go into the band headers band. - // This must be done before any other output happens. - if (outputSize >= 0) - outputSize += computeEscapeSize(); // good cache - // Other bytes go into band_headers. - for (int i = 1; i < metaCoding.length; i++) { - band_headers.putByte(metaCoding[i] & 0xFF); - } - } - } - - @Override - protected long computeOutputSize() { - outputSize = getCodingChooser().computeByteSize(bandCoding, - values, 0, length); - assert(outputSize < Integer.MAX_VALUE); - outputSize += computeEscapeSize(); - return outputSize; - } - - protected int computeEscapeSize() { - if (metaCoding.length == 0) return 0; - int XB = metaCoding[0] & 0xFF; - int X = encodeEscapeValue(XB, regularCoding); - return regularCoding.setD(0).getLength(X); - } - - @Override - protected void writeDataTo(OutputStream out) throws IOException { - if (length == 0) return; // nothing to write - long len0 = 0; - if (out == outputCounter) { - len0 = outputCounter.getCount(); - } - if (metaCoding.length > 0) { - int XB = metaCoding[0] & 0xFF; - // We need an explicit band header, either because - // there is a non-default coding method, or because - // the first value would be parsed as an escape value. - int X = encodeEscapeValue(XB, regularCoding); - //System.out.println("X="+X+" XB="+XB+" in "+this); - regularCoding.setD(0).writeTo(out, X); - } - bandCoding.writeArrayTo(out, values, 0, length); - if (out == outputCounter) { - assert(outputSize == outputCounter.getCount() - len0) - : (outputSize+" != "+outputCounter.getCount()+"-"+len0); - } - if (optDumpBands) dumpBand(); - } - - @Override - protected void readDataFrom(InputStream in) throws IOException { - length = valuesExpected(); - if (length == 0) return; // nothing to read - if (verbose > 1) - Utils.log.fine("Reading band "+this); - if (!canVaryCoding()) { - bandCoding = regularCoding; - metaCoding = noMetaCoding; - } else { - assert(in.markSupported()); // input must be buffered - in.mark(Coding.B_MAX); - int X = regularCoding.setD(0).readFrom(in); - int XB = decodeEscapeValue(X, regularCoding); - if (XB < 0) { - // Do not consume this value. No alternate coding. - in.reset(); - bandCoding = regularCoding; - metaCoding = noMetaCoding; - } else if (XB == Constants._meta_default) { - bandCoding = regularCoding; - metaCoding = defaultMetaCoding; - } else { - if (verbose > 2) - Utils.log.fine("found X="+X+" => XB="+XB); - bandCoding = getBandHeader(XB, regularCoding); - // This is really used only by dumpBands. - int p0 = bandHeaderBytePos0; - int p1 = bandHeaderBytePos; - metaCoding = new byte[p1-p0]; - System.arraycopy(bandHeaderBytes, p0, - metaCoding, 0, metaCoding.length); - } - } - if (bandCoding != regularCoding) { - if (verbose > 1) - Utils.log.fine(name()+": irregular coding "+bandCoding); - } - bandCoding.readArrayFrom(in, values, 0, length); - if (optDumpBands) dumpBand(); - } - - @Override - public void doneDisbursing() { - super.doneDisbursing(); - values = null; // for GC - } - - private void dumpBand() throws IOException { - assert(optDumpBands); - try (PrintStream ps = new PrintStream(getDumpStream(this, ".txt"))) { - String irr = (bandCoding == regularCoding) ? "" : " irregular"; - ps.print("# length="+length+ - " size="+outputSize()+ - irr+" coding="+bandCoding); - if (metaCoding != noMetaCoding) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < metaCoding.length; i++) { - if (i == 1) sb.append(" /"); - sb.append(" ").append(metaCoding[i] & 0xFF); - } - ps.print(" //header: "+sb); - } - printArrayTo(ps, values, 0, length); - } - try (OutputStream ds = getDumpStream(this, ".bnd")) { - bandCoding.writeArrayTo(ds, values, 0, length); - } - } - - /** Disburse one value. */ - protected int getValue() { - assert(phase() == DISBURSE_PHASE); - // when debugging return a zero if lengths are zero - if (optDebugBands && length == 0 && valuesDisbursed == length) - return 0; - assert(valuesDisbursed <= length); - return values[valuesDisbursed++]; - } - - /** Reset for another pass over the same value set. */ - public void resetForSecondPass() { - assert(phase() == DISBURSE_PHASE); - assert(valuesDisbursed == length()); // 1st pass is complete - valuesDisbursed = 0; - } - } - - class ByteBand extends Band { - private ByteArrayOutputStream bytes; // input buffer - private ByteArrayOutputStream bytesForDump; - private InputStream in; - - public ByteBand(String name) { - super(name, BYTE1); - } - - @Override - public int capacity() { - return bytes == null ? -1 : Integer.MAX_VALUE; - } - @Override - protected void setCapacity(int cap) { - assert(bytes == null); // do this just once - bytes = new ByteArrayOutputStream(cap); - } - public void destroy() { - lengthForDebug = length(); - bytes = null; - } - - @Override - public int length() { - return bytes == null ? -1 : bytes.size(); - } - public void reset() { - bytes.reset(); - } - @Override - protected int valuesRemainingForDebug() { - return (bytes == null) ? -1 : ((ByteArrayInputStream)in).available(); - } - - @Override - protected void chooseBandCodings() throws IOException { - // No-op. - assert(decodeEscapeValue(regularCoding.min(), regularCoding) < 0); - assert(decodeEscapeValue(regularCoding.max(), regularCoding) < 0); - } - - @Override - protected long computeOutputSize() { - // do not cache - return bytes.size(); - } - - @Override - public void writeDataTo(OutputStream out) throws IOException { - if (length() == 0) return; - bytes.writeTo(out); - if (optDumpBands) dumpBand(); - destroy(); // done with the bits! - } - - private void dumpBand() throws IOException { - assert(optDumpBands); - try (OutputStream ds = getDumpStream(this, ".bnd")) { - if (bytesForDump != null) - bytesForDump.writeTo(ds); - else - bytes.writeTo(ds); - } - } - - @Override - public void readDataFrom(InputStream in) throws IOException { - int vex = valuesExpected(); - if (vex == 0) return; - if (verbose > 1) { - lengthForDebug = vex; - Utils.log.fine("Reading band "+this); - lengthForDebug = -1; - } - byte[] buf = new byte[Math.min(vex, 1<<14)]; - while (vex > 0) { - int nr = in.read(buf, 0, Math.min(vex, buf.length)); - if (nr < 0) throw new EOFException(); - bytes.write(buf, 0, nr); - vex -= nr; - } - if (optDumpBands) dumpBand(); - } - - @Override - public void readyToDisburse() { - in = new ByteArrayInputStream(bytes.toByteArray()); - super.readyToDisburse(); - } - - @Override - public void doneDisbursing() { - super.doneDisbursing(); - if (optDumpBands - && bytesForDump != null && bytesForDump.size() > 0) { - try { - dumpBand(); - } catch (IOException ee) { - throw new RuntimeException(ee); - } - } - in = null; // GC - bytes = null; // GC - bytesForDump = null; // GC - } - - // alternative to readFrom: - public void setInputStreamFrom(InputStream in) throws IOException { - assert(bytes == null); - assert(assertReadyToReadFrom(this, in)); - setPhase(READ_PHASE); - this.in = in; - if (optDumpBands) { - // Tap the stream. - bytesForDump = new ByteArrayOutputStream(); - this.in = new FilterInputStream(in) { - @Override - public int read() throws IOException { - int ch = in.read(); - if (ch >= 0) bytesForDump.write(ch); - return ch; - } - @Override - public int read(byte b[], int off, int len) throws IOException { - int nr = in.read(b, off, len); - if (nr >= 0) bytesForDump.write(b, off, nr); - return nr; - } - }; - } - super.readyToDisburse(); - } - - public OutputStream collectorStream() { - assert(phase() == COLLECT_PHASE); - assert(bytes != null); - return bytes; - } - - public InputStream getInputStream() { - assert(phase() == DISBURSE_PHASE); - assert(in != null); - return in; - } - public int getByte() throws IOException { - int b = getInputStream().read(); - if (b < 0) throw new EOFException(); - return b; - } - public void putByte(int b) throws IOException { - assert(b == (b & 0xFF)); - collectorStream().write(b); - } - @Override - public String toString() { - return "byte "+super.toString(); - } - } - - class IntBand extends ValueBand { - // The usual coding for bands is 7bit/5byte/delta. - public IntBand(String name, Coding regularCoding) { - super(name, regularCoding); - } - - public void putInt(int x) { - assert(phase() == COLLECT_PHASE); - addValue(x); - } - - public int getInt() { - return getValue(); - } - /** Return the sum of all values in this band. */ - public int getIntTotal() { - assert(phase() == DISBURSE_PHASE); - // assert that this is the whole pass; no other reads allowed - assert(valuesRemainingForDebug() == length()); - int total = 0; - for (int k = length(); k > 0; k--) { - total += getInt(); - } - resetForSecondPass(); - return total; - } - /** Return the occurrence count of a specific value in this band. */ - public int getIntCount(int value) { - assert(phase() == DISBURSE_PHASE); - // assert that this is the whole pass; no other reads allowed - assert(valuesRemainingForDebug() == length()); - int total = 0; - for (int k = length(); k > 0; k--) { - if (getInt() == value) { - total += 1; - } - } - resetForSecondPass(); - return total; - } - } - - static int getIntTotal(int[] values) { - int total = 0; - for (int i = 0; i < values.length; i++) { - total += values[i]; - } - return total; - } - - class CPRefBand extends ValueBand { - ConstantPool.Index index; - boolean nullOK; - - public CPRefBand(String name, Coding regularCoding, byte cpTag, boolean nullOK) { - super(name, regularCoding); - this.nullOK = nullOK; - if (cpTag != Constants.CONSTANT_None) - setBandIndex(this, cpTag); - } - public CPRefBand(String name, Coding regularCoding, byte cpTag) { - this(name, regularCoding, cpTag, false); - } - public CPRefBand(String name, Coding regularCoding, Object undef) { - this(name, regularCoding, Constants.CONSTANT_None, false); - } - - public void setIndex(ConstantPool.Index index) { - this.index = index; - } - - protected void readDataFrom(InputStream in) throws IOException { - super.readDataFrom(in); - assert(assertValidCPRefs(this)); - } - - /** Write a constant pool reference. */ - public void putRef(ConstantPool.Entry e) { - addValue(encodeRefOrNull(e, index)); - } - public void putRef(ConstantPool.Entry e, ConstantPool.Index index) { - assert(this.index == null); - addValue(encodeRefOrNull(e, index)); - } - public void putRef(ConstantPool.Entry e, byte cptag) { - putRef(e, getCPIndex(cptag)); - } - - public ConstantPool.Entry getRef() { - if (index == null) Utils.log.warning("No index for "+this); - assert(index != null); - return decodeRefOrNull(getValue(), index); - } - public ConstantPool.Entry getRef(ConstantPool.Index index) { - assert(this.index == null); - return decodeRefOrNull(getValue(), index); - } - public ConstantPool.Entry getRef(byte cptag) { - return getRef(getCPIndex(cptag)); - } - - private int encodeRefOrNull(ConstantPool.Entry e, ConstantPool.Index index) { - int nonNullCode; // NNC is the coding which assumes nulls are rare - if (e == null) { - nonNullCode = -1; // negative values are rare - } else { - nonNullCode = encodeRef(e, index); - } - // If nulls are expected, increment, to make -1 code turn to 0. - return (nullOK ? 1 : 0) + nonNullCode; - } - private ConstantPool.Entry decodeRefOrNull(int code, ConstantPool.Index index) { - // Inverse to encodeRefOrNull... - int nonNullCode = code - (nullOK ? 1 : 0); - if (nonNullCode == -1) { - return null; - } else { - return decodeRef(nonNullCode, index); - } - } - } - - // Bootstrap support for CPRefBands. These are needed to record - // intended CP indexes, before the CP has been created. - private final List allKQBands = new ArrayList<>(); - private List needPredefIndex = new ArrayList<>(); - - - int encodeRef(ConstantPool.Entry e, ConstantPool.Index ix) { - if (ix == null) - throw new RuntimeException("null index for " + e.stringValue()); - int coding = ix.indexOf(e); - if (verbose > 2) - Utils.log.fine("putRef "+coding+" => "+e); - return coding; - } - - ConstantPool.Entry decodeRef(int n, ConstantPool.Index ix) { - if (n < 0 || n >= ix.size()) - Utils.log.warning("decoding bad ref "+n+" in "+ix); - ConstantPool.Entry e = ix.getEntry(n); - if (verbose > 2) - Utils.log.fine("getRef "+n+" => "+e); - return e; - } - - private CodingChooser codingChooser; - protected CodingChooser getCodingChooser() { - if (codingChooser == null) { - codingChooser = new CodingChooser(effort, basicCodings); - if (codingChooser.stress != null - && this instanceof PackageWriter) { - // Twist the random state based on my first file. - // This sends each segment off in a different direction. - List classes = ((PackageWriter)this).pkg.classes; - if (!classes.isEmpty()) { - Package.Class cls = classes.get(0); - codingChooser.addStressSeed(cls.getName().hashCode()); - } - } - } - return codingChooser; - } - - public CodingMethod chooseCoding(int[] values, int start, int end, - Coding regular, String bandName, - int[] sizes) { - assert(optVaryCodings); - if (effort <= MIN_EFFORT) { - return regular; - } - CodingChooser cc = getCodingChooser(); - if (verbose > 1 || cc.verbose > 1) { - Utils.log.fine("--- chooseCoding "+bandName); - } - return cc.choose(values, start, end, regular, sizes); - } - - static final byte[] defaultMetaCoding = { Constants._meta_default }; - static final byte[] noMetaCoding = {}; - - // The first value in a band is always coded with the default coding D. - // If this first value X is an escape value, it actually represents the - // first (and perhaps only) byte of a meta-coding. - // - // If D.S != 0 and D includes the range [-256..-1], - // the escape values are in that range, - // and the first byte XB is -1-X. - // - // If D.S == 0 and D includes the range [(D.L)..(D.L)+255], - // the escape values are in that range, - // and XB is X-(D.L). - // - // This representation is designed so that a band header is unlikely - // to be confused with the initial value of a headerless band, - // and yet so that a band header is likely to occupy only a byte or two. - // - // Result is in [0..255] if XB was successfully extracted, else -1. - // See section "Coding Specifier Meta-Encoding" in the JSR 200 spec. - protected static int decodeEscapeValue(int X, Coding regularCoding) { - // The first value in a band is always coded with the default coding D. - // If this first value X is an escape value, it actually represents the - // first (and perhaps only) byte of a meta-coding. - // Result is in [0..255] if XB was successfully extracted, else -1. - if (regularCoding.B() == 1 || regularCoding.L() == 0) - return -1; // degenerate regular coding (BYTE1) - if (regularCoding.S() != 0) { - if (-256 <= X && X <= -1 && regularCoding.min() <= -256) { - int XB = -1-X; - assert(XB >= 0 && XB < 256); - return XB; - } - } else { - int L = regularCoding.L(); - if (L <= X && X <= L+255 && regularCoding.max() >= L+255) { - int XB = X-L; - assert(XB >= 0 && XB < 256); - return XB; - } - } - return -1; // negative value for failure - } - // Inverse to decodeEscapeValue(). - protected static int encodeEscapeValue(int XB, Coding regularCoding) { - assert(XB >= 0 && XB < 256); - assert(regularCoding.B() > 1 && regularCoding.L() > 0); - int X; - if (regularCoding.S() != 0) { - assert(regularCoding.min() <= -256); - X = -1-XB; - } else { - int L = regularCoding.L(); - assert(regularCoding.max() >= L+255); - X = XB+L; - } - assert(decodeEscapeValue(X, regularCoding) == XB) - : (regularCoding+" XB="+XB+" X="+X); - return X; - } - - static { - boolean checkXB = false; - assert(checkXB = true); - if (checkXB) { - for (int i = 0; i < basicCodings.length; i++) { - Coding D = basicCodings[i]; - if (D == null) continue; - if (D.B() == 1) continue; - if (D.L() == 0) continue; - for (int XB = 0; XB <= 255; XB++) { - // The following exercises decodeEscapeValue also: - encodeEscapeValue(XB, D); - } - } - } - } - - class MultiBand extends Band { - MultiBand(String name, Coding regularCoding) { - super(name, regularCoding); - } - - @Override - public Band init() { - super.init(); - // This is all just to keep the asserts happy: - setCapacity(0); - if (phase() == EXPECT_PHASE) { - // Fast forward: - setPhase(READ_PHASE); - setPhase(DISBURSE_PHASE); - } - return this; - } - - Band[] bands = new Band[10]; - int bandCount = 0; - - int size() { - return bandCount; - } - Band get(int i) { - assert(i < bandCount); - return bands[i]; - } - Band[] toArray() { - return (Band[]) realloc(bands, bandCount); - } - - void add(Band b) { - assert(bandCount == 0 || notePrevForAssert(b, bands[bandCount-1])); - if (bandCount == bands.length) { - bands = (Band[]) realloc(bands); - } - bands[bandCount++] = b; - } - - ByteBand newByteBand(String name) { - ByteBand b = new ByteBand(name); - b.init(); add(b); - return b; - } - IntBand newIntBand(String name) { - IntBand b = new IntBand(name, regularCoding); - b.init(); add(b); - return b; - } - IntBand newIntBand(String name, Coding regularCoding) { - IntBand b = new IntBand(name, regularCoding); - b.init(); add(b); - return b; - } - MultiBand newMultiBand(String name, Coding regularCoding) { - MultiBand b = new MultiBand(name, regularCoding); - b.init(); add(b); - return b; - } - CPRefBand newCPRefBand(String name, byte cpTag) { - CPRefBand b = new CPRefBand(name, regularCoding, cpTag); - b.init(); add(b); - return b; - } - CPRefBand newCPRefBand(String name, Coding regularCoding, - byte cpTag) { - CPRefBand b = new CPRefBand(name, regularCoding, cpTag); - b.init(); add(b); - return b; - } - CPRefBand newCPRefBand(String name, Coding regularCoding, - byte cpTag, boolean nullOK) { - CPRefBand b = new CPRefBand(name, regularCoding, cpTag, nullOK); - b.init(); add(b); - return b; - } - - int bandCount() { return bandCount; } - - private int cap = -1; - @Override - public int capacity() { return cap; } - @Override - public void setCapacity(int cap) { this.cap = cap; } - - @Override - public int length() { return 0; } - @Override - public int valuesRemainingForDebug() { return 0; } - - @Override - protected void chooseBandCodings() throws IOException { - // coding decision pass - for (int i = 0; i < bandCount; i++) { - Band b = bands[i]; - b.chooseBandCodings(); - } - } - - @Override - protected long computeOutputSize() { - // coding decision pass - long sum = 0; - for (int i = 0; i < bandCount; i++) { - Band b = bands[i]; - long bsize = b.outputSize(); - assert(bsize >= 0) : b; - sum += bsize; - } - // do not cache - return sum; - } - - @Override - protected void writeDataTo(OutputStream out) throws IOException { - long preCount = 0; - if (outputCounter != null) preCount = outputCounter.getCount(); - for (int i = 0; i < bandCount; i++) { - Band b = bands[i]; - b.writeTo(out); - if (outputCounter != null) { - long postCount = outputCounter.getCount(); - long len = postCount - preCount; - preCount = postCount; - if ((verbose > 0 && len > 0) || verbose > 1) { - Utils.log.info(" ...wrote "+len+" bytes from "+b); - } - } - } - } - - @Override - protected void readDataFrom(InputStream in) throws IOException { - assert(false); // not called? - for (int i = 0; i < bandCount; i++) { - Band b = bands[i]; - b.readFrom(in); - if ((verbose > 0 && b.length() > 0) || verbose > 1) { - Utils.log.info(" ...read "+b); - } - } - } - - @Override - public String toString() { - return "{"+bandCount()+" bands: "+super.toString()+"}"; - } - } - - /** - * An output stream which counts the number of bytes written. - */ - private static - class ByteCounter extends FilterOutputStream { - // (should go public under the name CountingOutputStream?) - - private long count; - - public ByteCounter(OutputStream out) { - super(out); - } - - public long getCount() { return count; } - public void setCount(long c) { count = c; } - - @Override - public void write(int b) throws IOException { - count++; - if (out != null) out.write(b); - } - @Override - public void write(byte b[], int off, int len) throws IOException { - count += len; - if (out != null) out.write(b, off, len); - } - @Override - public String toString() { - return String.valueOf(getCount()); - } - } - ByteCounter outputCounter; - - void writeAllBandsTo(OutputStream out) throws IOException { - // Wrap a byte-counter around the output stream. - outputCounter = new ByteCounter(out); - out = outputCounter; - all_bands.writeTo(out); - if (verbose > 0) { - long nbytes = outputCounter.getCount(); - Utils.log.info("Wrote total of "+nbytes+" bytes."); - assert(nbytes == archiveSize0+archiveSize1); - } - outputCounter = null; - } - - // random AO_XXX bits, decoded from the archive header - protected int archiveOptions; - - // archiveSize1 sizes most of the archive [archive_options..file_bits). - protected long archiveSize0; // size through archive_size_lo - protected long archiveSize1; // size reported in archive_header - protected int archiveNextCount; // reported in archive_header - - static final int AH_LENGTH_0 = 3; // archive_header_0 = {minver, majver, options} - static final int AH_LENGTH_MIN = 15; // observed in spec {header_0[3], cp_counts[8], class_counts[4]} - // Length contributions from optional archive size fields: - static final int AH_LENGTH_S = 2; // archive_header_S = optional {size_hi, size_lo} - static final int AH_ARCHIVE_SIZE_HI = 0; // offset in archive_header_S - static final int AH_ARCHIVE_SIZE_LO = 1; // offset in archive_header_S - // Length contributions from optional header fields: - static final int AH_FILE_HEADER_LEN = 5; // file_counts = {{size_hi, size_lo}, next, modtime, files} - static final int AH_SPECIAL_FORMAT_LEN = 2; // special_counts = {layouts, band_headers} - static final int AH_CP_NUMBER_LEN = 4; // cp_number_counts = {int, float, long, double} - static final int AH_CP_EXTRA_LEN = 4; // cp_attr_counts = {MH, MT, InDy, BSM} - - // Common structure of attribute band groups: - static final int AB_FLAGS_HI = 0; - static final int AB_FLAGS_LO = 1; - static final int AB_ATTR_COUNT = 2; - static final int AB_ATTR_INDEXES = 3; - static final int AB_ATTR_CALLS = 4; - - static IntBand getAttrBand(MultiBand xxx_attr_bands, int which) { - IntBand b = (IntBand) xxx_attr_bands.get(which); - switch (which) { - case AB_FLAGS_HI: - assert(b.name().endsWith("_flags_hi")); break; - case AB_FLAGS_LO: - assert(b.name().endsWith("_flags_lo")); break; - case AB_ATTR_COUNT: - assert(b.name().endsWith("_attr_count")); break; - case AB_ATTR_INDEXES: - assert(b.name().endsWith("_attr_indexes")); break; - case AB_ATTR_CALLS: - assert(b.name().endsWith("_attr_calls")); break; - default: - assert(false); break; - } - return b; - } - - private static final boolean NULL_IS_OK = true; - - MultiBand all_bands = (MultiBand) new MultiBand("(package)", UNSIGNED5).init(); - - // file header (various random bytes) - ByteBand archive_magic = all_bands.newByteBand("archive_magic"); - IntBand archive_header_0 = all_bands.newIntBand("archive_header_0", UNSIGNED5); - IntBand archive_header_S = all_bands.newIntBand("archive_header_S", UNSIGNED5); - IntBand archive_header_1 = all_bands.newIntBand("archive_header_1", UNSIGNED5); - ByteBand band_headers = all_bands.newByteBand("band_headers"); - - // constant pool contents - MultiBand cp_bands = all_bands.newMultiBand("(constant_pool)", DELTA5); - IntBand cp_Utf8_prefix = cp_bands.newIntBand("cp_Utf8_prefix"); - IntBand cp_Utf8_suffix = cp_bands.newIntBand("cp_Utf8_suffix", UNSIGNED5); - IntBand cp_Utf8_chars = cp_bands.newIntBand("cp_Utf8_chars", CHAR3); - IntBand cp_Utf8_big_suffix = cp_bands.newIntBand("cp_Utf8_big_suffix"); - MultiBand cp_Utf8_big_chars = cp_bands.newMultiBand("(cp_Utf8_big_chars)", DELTA5); - IntBand cp_Int = cp_bands.newIntBand("cp_Int", UDELTA5); - IntBand cp_Float = cp_bands.newIntBand("cp_Float", UDELTA5); - IntBand cp_Long_hi = cp_bands.newIntBand("cp_Long_hi", UDELTA5); - IntBand cp_Long_lo = cp_bands.newIntBand("cp_Long_lo"); - IntBand cp_Double_hi = cp_bands.newIntBand("cp_Double_hi", UDELTA5); - IntBand cp_Double_lo = cp_bands.newIntBand("cp_Double_lo"); - CPRefBand cp_String = cp_bands.newCPRefBand("cp_String", UDELTA5, Constants.CONSTANT_Utf8); - CPRefBand cp_Class = cp_bands.newCPRefBand("cp_Class", UDELTA5, Constants.CONSTANT_Utf8); - CPRefBand cp_Signature_form = cp_bands.newCPRefBand("cp_Signature_form", Constants.CONSTANT_Utf8); - CPRefBand cp_Signature_classes = cp_bands.newCPRefBand("cp_Signature_classes", UDELTA5, Constants.CONSTANT_Class); - CPRefBand cp_Descr_name = cp_bands.newCPRefBand("cp_Descr_name", Constants.CONSTANT_Utf8); - CPRefBand cp_Descr_type = cp_bands.newCPRefBand("cp_Descr_type", UDELTA5, Constants.CONSTANT_Signature); - CPRefBand cp_Field_class = cp_bands.newCPRefBand("cp_Field_class", Constants.CONSTANT_Class); - CPRefBand cp_Field_desc = cp_bands.newCPRefBand("cp_Field_desc", UDELTA5, Constants.CONSTANT_NameandType); - CPRefBand cp_Method_class = cp_bands.newCPRefBand("cp_Method_class", Constants.CONSTANT_Class); - CPRefBand cp_Method_desc = cp_bands.newCPRefBand("cp_Method_desc", UDELTA5, Constants.CONSTANT_NameandType); - CPRefBand cp_Imethod_class = cp_bands.newCPRefBand("cp_Imethod_class", Constants.CONSTANT_Class); - CPRefBand cp_Imethod_desc = cp_bands.newCPRefBand("cp_Imethod_desc", UDELTA5, Constants.CONSTANT_NameandType); - IntBand cp_MethodHandle_refkind = cp_bands.newIntBand("cp_MethodHandle_refkind", DELTA5); - CPRefBand cp_MethodHandle_member = cp_bands.newCPRefBand("cp_MethodHandle_member", UDELTA5, Constants.CONSTANT_AnyMember); - CPRefBand cp_MethodType = cp_bands.newCPRefBand("cp_MethodType", UDELTA5, Constants.CONSTANT_Signature); - CPRefBand cp_BootstrapMethod_ref = cp_bands.newCPRefBand("cp_BootstrapMethod_ref", DELTA5, Constants.CONSTANT_MethodHandle); - IntBand cp_BootstrapMethod_arg_count = cp_bands.newIntBand("cp_BootstrapMethod_arg_count", UDELTA5); - CPRefBand cp_BootstrapMethod_arg = cp_bands.newCPRefBand("cp_BootstrapMethod_arg", DELTA5, Constants.CONSTANT_LoadableValue); - CPRefBand cp_InvokeDynamic_spec = cp_bands.newCPRefBand("cp_InvokeDynamic_spec", DELTA5, Constants.CONSTANT_BootstrapMethod); - CPRefBand cp_InvokeDynamic_desc = cp_bands.newCPRefBand("cp_InvokeDynamic_desc", UDELTA5, Constants.CONSTANT_NameandType); - - // bands for carrying attribute definitions: - MultiBand attr_definition_bands = all_bands.newMultiBand("(attr_definition_bands)", UNSIGNED5); - ByteBand attr_definition_headers = attr_definition_bands.newByteBand("attr_definition_headers"); - CPRefBand attr_definition_name = attr_definition_bands.newCPRefBand("attr_definition_name", Constants.CONSTANT_Utf8); - CPRefBand attr_definition_layout = attr_definition_bands.newCPRefBand("attr_definition_layout", Constants.CONSTANT_Utf8); - - // bands for hardwired InnerClasses attribute (shared across the package) - MultiBand ic_bands = all_bands.newMultiBand("(ic_bands)", DELTA5); - CPRefBand ic_this_class = ic_bands.newCPRefBand("ic_this_class", UDELTA5, Constants.CONSTANT_Class); - IntBand ic_flags = ic_bands.newIntBand("ic_flags", UNSIGNED5); - // These bands contain data only where flags sets ACC_IC_LONG_FORM: - CPRefBand ic_outer_class = ic_bands.newCPRefBand("ic_outer_class", DELTA5, Constants.CONSTANT_Class, NULL_IS_OK); - CPRefBand ic_name = ic_bands.newCPRefBand("ic_name", DELTA5, Constants.CONSTANT_Utf8, NULL_IS_OK); - - // bands for carrying class schema information: - MultiBand class_bands = all_bands.newMultiBand("(class_bands)", DELTA5); - CPRefBand class_this = class_bands.newCPRefBand("class_this", Constants.CONSTANT_Class); - CPRefBand class_super = class_bands.newCPRefBand("class_super", Constants.CONSTANT_Class); - IntBand class_interface_count = class_bands.newIntBand("class_interface_count"); - CPRefBand class_interface = class_bands.newCPRefBand("class_interface", Constants.CONSTANT_Class); - - // bands for class members - IntBand class_field_count = class_bands.newIntBand("class_field_count"); - IntBand class_method_count = class_bands.newIntBand("class_method_count"); - - CPRefBand field_descr = class_bands.newCPRefBand("field_descr", Constants.CONSTANT_NameandType); - MultiBand field_attr_bands = class_bands.newMultiBand("(field_attr_bands)", UNSIGNED5); - IntBand field_flags_hi = field_attr_bands.newIntBand("field_flags_hi"); - IntBand field_flags_lo = field_attr_bands.newIntBand("field_flags_lo"); - IntBand field_attr_count = field_attr_bands.newIntBand("field_attr_count"); - IntBand field_attr_indexes = field_attr_bands.newIntBand("field_attr_indexes"); - IntBand field_attr_calls = field_attr_bands.newIntBand("field_attr_calls"); - - // bands for predefined field attributes - CPRefBand field_ConstantValue_KQ = field_attr_bands.newCPRefBand("field_ConstantValue_KQ", Constants.CONSTANT_FieldSpecific); - CPRefBand field_Signature_RS = field_attr_bands.newCPRefBand("field_Signature_RS", Constants.CONSTANT_Signature); - MultiBand field_metadata_bands = field_attr_bands.newMultiBand("(field_metadata_bands)", UNSIGNED5); - MultiBand field_type_metadata_bands = field_attr_bands.newMultiBand("(field_type_metadata_bands)", UNSIGNED5); - - CPRefBand method_descr = class_bands.newCPRefBand("method_descr", MDELTA5, Constants.CONSTANT_NameandType); - MultiBand method_attr_bands = class_bands.newMultiBand("(method_attr_bands)", UNSIGNED5); - IntBand method_flags_hi = method_attr_bands.newIntBand("method_flags_hi"); - IntBand method_flags_lo = method_attr_bands.newIntBand("method_flags_lo"); - IntBand method_attr_count = method_attr_bands.newIntBand("method_attr_count"); - IntBand method_attr_indexes = method_attr_bands.newIntBand("method_attr_indexes"); - IntBand method_attr_calls = method_attr_bands.newIntBand("method_attr_calls"); - // band for predefined method attributes - IntBand method_Exceptions_N = method_attr_bands.newIntBand("method_Exceptions_N"); - CPRefBand method_Exceptions_RC = method_attr_bands.newCPRefBand("method_Exceptions_RC", Constants.CONSTANT_Class); - CPRefBand method_Signature_RS = method_attr_bands.newCPRefBand("method_Signature_RS", Constants.CONSTANT_Signature); - MultiBand method_metadata_bands = method_attr_bands.newMultiBand("(method_metadata_bands)", UNSIGNED5); - // band for predefine method parameters - IntBand method_MethodParameters_NB = method_attr_bands.newIntBand("method_MethodParameters_NB", BYTE1); - CPRefBand method_MethodParameters_name_RUN = method_attr_bands.newCPRefBand("method_MethodParameters_name_RUN", UNSIGNED5, Constants.CONSTANT_Utf8, NULL_IS_OK); - IntBand method_MethodParameters_flag_FH = method_attr_bands.newIntBand("method_MethodParameters_flag_FH"); - MultiBand method_type_metadata_bands = method_attr_bands.newMultiBand("(method_type_metadata_bands)", UNSIGNED5); - - MultiBand class_attr_bands = class_bands.newMultiBand("(class_attr_bands)", UNSIGNED5); - IntBand class_flags_hi = class_attr_bands.newIntBand("class_flags_hi"); - IntBand class_flags_lo = class_attr_bands.newIntBand("class_flags_lo"); - IntBand class_attr_count = class_attr_bands.newIntBand("class_attr_count"); - IntBand class_attr_indexes = class_attr_bands.newIntBand("class_attr_indexes"); - IntBand class_attr_calls = class_attr_bands.newIntBand("class_attr_calls"); - // band for predefined SourceFile and other class attributes - CPRefBand class_SourceFile_RUN = class_attr_bands.newCPRefBand("class_SourceFile_RUN", UNSIGNED5, Constants.CONSTANT_Utf8, NULL_IS_OK); - CPRefBand class_EnclosingMethod_RC = class_attr_bands.newCPRefBand("class_EnclosingMethod_RC", Constants.CONSTANT_Class); - CPRefBand class_EnclosingMethod_RDN = class_attr_bands.newCPRefBand("class_EnclosingMethod_RDN", UNSIGNED5, Constants.CONSTANT_NameandType, NULL_IS_OK); - CPRefBand class_Signature_RS = class_attr_bands.newCPRefBand("class_Signature_RS", Constants.CONSTANT_Signature); - MultiBand class_metadata_bands = class_attr_bands.newMultiBand("(class_metadata_bands)", UNSIGNED5); - IntBand class_InnerClasses_N = class_attr_bands.newIntBand("class_InnerClasses_N"); - CPRefBand class_InnerClasses_RC = class_attr_bands.newCPRefBand("class_InnerClasses_RC", Constants.CONSTANT_Class); - IntBand class_InnerClasses_F = class_attr_bands.newIntBand("class_InnerClasses_F"); - CPRefBand class_InnerClasses_outer_RCN = class_attr_bands.newCPRefBand("class_InnerClasses_outer_RCN", UNSIGNED5, Constants.CONSTANT_Class, NULL_IS_OK); - CPRefBand class_InnerClasses_name_RUN = class_attr_bands.newCPRefBand("class_InnerClasses_name_RUN", UNSIGNED5, Constants.CONSTANT_Utf8, NULL_IS_OK); - IntBand class_ClassFile_version_minor_H = class_attr_bands.newIntBand("class_ClassFile_version_minor_H"); - IntBand class_ClassFile_version_major_H = class_attr_bands.newIntBand("class_ClassFile_version_major_H"); - MultiBand class_type_metadata_bands = class_attr_bands.newMultiBand("(class_type_metadata_bands)", UNSIGNED5); - - MultiBand code_bands = class_bands.newMultiBand("(code_bands)", UNSIGNED5); - ByteBand code_headers = code_bands.newByteBand("code_headers"); //BYTE1 - IntBand code_max_stack = code_bands.newIntBand("code_max_stack", UNSIGNED5); - IntBand code_max_na_locals = code_bands.newIntBand("code_max_na_locals", UNSIGNED5); - IntBand code_handler_count = code_bands.newIntBand("code_handler_count", UNSIGNED5); - IntBand code_handler_start_P = code_bands.newIntBand("code_handler_start_P", BCI5); - IntBand code_handler_end_PO = code_bands.newIntBand("code_handler_end_PO", BRANCH5); - IntBand code_handler_catch_PO = code_bands.newIntBand("code_handler_catch_PO", BRANCH5); - CPRefBand code_handler_class_RCN = code_bands.newCPRefBand("code_handler_class_RCN", UNSIGNED5, Constants.CONSTANT_Class, NULL_IS_OK); - - MultiBand code_attr_bands = class_bands.newMultiBand("(code_attr_bands)", UNSIGNED5); - IntBand code_flags_hi = code_attr_bands.newIntBand("code_flags_hi"); - IntBand code_flags_lo = code_attr_bands.newIntBand("code_flags_lo"); - IntBand code_attr_count = code_attr_bands.newIntBand("code_attr_count"); - IntBand code_attr_indexes = code_attr_bands.newIntBand("code_attr_indexes"); - IntBand code_attr_calls = code_attr_bands.newIntBand("code_attr_calls"); - - MultiBand stackmap_bands = code_attr_bands.newMultiBand("(StackMapTable_bands)", UNSIGNED5); - IntBand code_StackMapTable_N = stackmap_bands.newIntBand("code_StackMapTable_N"); - IntBand code_StackMapTable_frame_T = stackmap_bands.newIntBand("code_StackMapTable_frame_T",BYTE1); - IntBand code_StackMapTable_local_N = stackmap_bands.newIntBand("code_StackMapTable_local_N"); - IntBand code_StackMapTable_stack_N = stackmap_bands.newIntBand("code_StackMapTable_stack_N"); - IntBand code_StackMapTable_offset = stackmap_bands.newIntBand("code_StackMapTable_offset", UNSIGNED5); - IntBand code_StackMapTable_T = stackmap_bands.newIntBand("code_StackMapTable_T", BYTE1); - CPRefBand code_StackMapTable_RC = stackmap_bands.newCPRefBand("code_StackMapTable_RC", Constants.CONSTANT_Class); - IntBand code_StackMapTable_P = stackmap_bands.newIntBand("code_StackMapTable_P", BCI5); - - // bands for predefined LineNumberTable attribute - IntBand code_LineNumberTable_N = code_attr_bands.newIntBand("code_LineNumberTable_N"); - IntBand code_LineNumberTable_bci_P = code_attr_bands.newIntBand("code_LineNumberTable_bci_P", BCI5); - IntBand code_LineNumberTable_line = code_attr_bands.newIntBand("code_LineNumberTable_line"); - - // bands for predefined LocalVariable{Type}Table attributes - IntBand code_LocalVariableTable_N = code_attr_bands.newIntBand("code_LocalVariableTable_N"); - IntBand code_LocalVariableTable_bci_P = code_attr_bands.newIntBand("code_LocalVariableTable_bci_P", BCI5); - IntBand code_LocalVariableTable_span_O = code_attr_bands.newIntBand("code_LocalVariableTable_span_O", BRANCH5); - CPRefBand code_LocalVariableTable_name_RU = code_attr_bands.newCPRefBand("code_LocalVariableTable_name_RU", Constants.CONSTANT_Utf8); - CPRefBand code_LocalVariableTable_type_RS = code_attr_bands.newCPRefBand("code_LocalVariableTable_type_RS", Constants.CONSTANT_Signature); - IntBand code_LocalVariableTable_slot = code_attr_bands.newIntBand("code_LocalVariableTable_slot"); - IntBand code_LocalVariableTypeTable_N = code_attr_bands.newIntBand("code_LocalVariableTypeTable_N"); - IntBand code_LocalVariableTypeTable_bci_P = code_attr_bands.newIntBand("code_LocalVariableTypeTable_bci_P", BCI5); - IntBand code_LocalVariableTypeTable_span_O = code_attr_bands.newIntBand("code_LocalVariableTypeTable_span_O", BRANCH5); - CPRefBand code_LocalVariableTypeTable_name_RU = code_attr_bands.newCPRefBand("code_LocalVariableTypeTable_name_RU", Constants.CONSTANT_Utf8); - CPRefBand code_LocalVariableTypeTable_type_RS = code_attr_bands.newCPRefBand("code_LocalVariableTypeTable_type_RS", Constants.CONSTANT_Signature); - IntBand code_LocalVariableTypeTable_slot = code_attr_bands.newIntBand("code_LocalVariableTypeTable_slot"); - MultiBand code_type_metadata_bands = code_attr_bands.newMultiBand("(code_type_metadata_bands)", UNSIGNED5); - - // bands for bytecodes - MultiBand bc_bands = all_bands.newMultiBand("(byte_codes)", UNSIGNED5); - ByteBand bc_codes = bc_bands.newByteBand("bc_codes"); //BYTE1 - // remaining bands provide typed opcode fields required by the bc_codes - - IntBand bc_case_count = bc_bands.newIntBand("bc_case_count"); // *switch - IntBand bc_case_value = bc_bands.newIntBand("bc_case_value", DELTA5); // *switch - ByteBand bc_byte = bc_bands.newByteBand("bc_byte"); //BYTE1 // bipush, iinc, *newarray - IntBand bc_short = bc_bands.newIntBand("bc_short", DELTA5); // sipush, wide iinc - IntBand bc_local = bc_bands.newIntBand("bc_local"); // *load, *store, iinc, ret - IntBand bc_label = bc_bands.newIntBand("bc_label", BRANCH5); // if*, goto*, jsr*, *switch - - // Most CP refs exhibit some correlation, and benefit from delta coding. - // The notable exceptions are class and method references. - - // ldc* operands: - CPRefBand bc_intref = bc_bands.newCPRefBand("bc_intref", DELTA5, Constants.CONSTANT_Integer); - CPRefBand bc_floatref = bc_bands.newCPRefBand("bc_floatref", DELTA5, Constants.CONSTANT_Float); - CPRefBand bc_longref = bc_bands.newCPRefBand("bc_longref", DELTA5, Constants.CONSTANT_Long); - CPRefBand bc_doubleref = bc_bands.newCPRefBand("bc_doubleref", DELTA5, Constants.CONSTANT_Double); - CPRefBand bc_stringref = bc_bands.newCPRefBand("bc_stringref", DELTA5, Constants.CONSTANT_String); - CPRefBand bc_loadablevalueref = bc_bands.newCPRefBand("bc_loadablevalueref", DELTA5, Constants.CONSTANT_LoadableValue); - - // nulls produced by bc_classref are taken to mean the current class - CPRefBand bc_classref = bc_bands.newCPRefBand("bc_classref", UNSIGNED5, Constants.CONSTANT_Class, NULL_IS_OK); // new, *anew*, c*cast, i*of, ldc - CPRefBand bc_fieldref = bc_bands.newCPRefBand("bc_fieldref", DELTA5, Constants.CONSTANT_Fieldref); // get*, put* - CPRefBand bc_methodref = bc_bands.newCPRefBand("bc_methodref", Constants.CONSTANT_Methodref); // invoke[vs]* - CPRefBand bc_imethodref = bc_bands.newCPRefBand("bc_imethodref", DELTA5, Constants.CONSTANT_InterfaceMethodref); // invokeinterface - CPRefBand bc_indyref = bc_bands.newCPRefBand("bc_indyref", DELTA5, Constants.CONSTANT_InvokeDynamic); // invokedynamic - - // _self_linker_op family - CPRefBand bc_thisfield = bc_bands.newCPRefBand("bc_thisfield", Constants.CONSTANT_None); // any field within cur. class - CPRefBand bc_superfield = bc_bands.newCPRefBand("bc_superfield", Constants.CONSTANT_None); // any field within superclass - CPRefBand bc_thismethod = bc_bands.newCPRefBand("bc_thismethod", Constants.CONSTANT_None); // any method within cur. class - CPRefBand bc_supermethod = bc_bands.newCPRefBand("bc_supermethod", Constants.CONSTANT_None); // any method within superclass - // bc_invokeinit family: - IntBand bc_initref = bc_bands.newIntBand("bc_initref"); - // escapes - CPRefBand bc_escref = bc_bands.newCPRefBand("bc_escref", Constants.CONSTANT_All); - IntBand bc_escrefsize = bc_bands.newIntBand("bc_escrefsize"); - IntBand bc_escsize = bc_bands.newIntBand("bc_escsize"); - ByteBand bc_escbyte = bc_bands.newByteBand("bc_escbyte"); - - // bands for carrying resource files and file attributes: - MultiBand file_bands = all_bands.newMultiBand("(file_bands)", UNSIGNED5); - CPRefBand file_name = file_bands.newCPRefBand("file_name", Constants.CONSTANT_Utf8); - IntBand file_size_hi = file_bands.newIntBand("file_size_hi"); - IntBand file_size_lo = file_bands.newIntBand("file_size_lo"); - IntBand file_modtime = file_bands.newIntBand("file_modtime", DELTA5); - IntBand file_options = file_bands.newIntBand("file_options"); - ByteBand file_bits = file_bands.newByteBand("file_bits"); - - // End of band definitions! - - /** Given CP indexes, distribute tag-specific indexes to bands. */ - protected void setBandIndexes() { - // Handle prior calls to setBandIndex: - for (Object[] need : needPredefIndex) { - CPRefBand b = (CPRefBand) need[0]; - Byte which = (Byte) need[1]; - b.setIndex(getCPIndex(which.byteValue())); - } - needPredefIndex = null; // no more predefs - - if (verbose > 3) { - printCDecl(all_bands); - } - } - - protected void setBandIndex(CPRefBand b, byte which) { - Object[] need = { b, Byte.valueOf(which) }; - if (which == Constants.CONSTANT_FieldSpecific) { - // I.e., attribute layouts KQ (no null) or KQN (null ok). - allKQBands.add(b); - } else if (needPredefIndex != null) { - needPredefIndex.add(need); - } else { - // Not in predefinition mode; getCPIndex now works. - b.setIndex(getCPIndex(which)); - } - } - - protected void setConstantValueIndex(Package.Class.Field f) { - ConstantPool.Index ix = null; - if (f != null) { - byte tag = f.getLiteralTag(); - ix = getCPIndex(tag); - if (verbose > 2) - Utils.log.fine("setConstantValueIndex "+f+" "+ConstantPool.tagName(tag)+" => "+ix); - assert(ix != null); - } - // Typically, allKQBands is the singleton of field_ConstantValue_KQ. - for (CPRefBand xxx_KQ : allKQBands) { - xxx_KQ.setIndex(ix); - } - } - - // Table of bands which contain metadata. - protected MultiBand[] metadataBands = new MultiBand[Constants.ATTR_CONTEXT_LIMIT]; - { - metadataBands[Constants.ATTR_CONTEXT_CLASS] = class_metadata_bands; - metadataBands[Constants.ATTR_CONTEXT_FIELD] = field_metadata_bands; - metadataBands[Constants.ATTR_CONTEXT_METHOD] = method_metadata_bands; - } - // Table of bands which contains type_metadata (TypeAnnotations) - protected MultiBand[] typeMetadataBands = new MultiBand[Constants.ATTR_CONTEXT_LIMIT]; - { - typeMetadataBands[Constants.ATTR_CONTEXT_CLASS] = class_type_metadata_bands; - typeMetadataBands[Constants.ATTR_CONTEXT_FIELD] = field_type_metadata_bands; - typeMetadataBands[Constants.ATTR_CONTEXT_METHOD] = method_type_metadata_bands; - typeMetadataBands[Constants.ATTR_CONTEXT_CODE] = code_type_metadata_bands; - } - - // Attribute layouts. - public static final int ADH_CONTEXT_MASK = 0x3; // (ad_hdr & ADH_CONTEXT_MASK) - public static final int ADH_BIT_SHIFT = 0x2; // (ad_hdr >> ADH_BIT_SHIFT) - public static final int ADH_BIT_IS_LSB = 1; - public static final int ATTR_INDEX_OVERFLOW = -1; - - public int[] attrIndexLimit = new int[Constants.ATTR_CONTEXT_LIMIT]; - // Each index limit is either 32 or 63, depending on AO_HAVE_XXX_FLAGS_HI. - - // Which flag bits are taken over by attributes? - protected long[] attrFlagMask = new long[Constants.ATTR_CONTEXT_LIMIT]; - // Which flag bits have been taken over explicitly? - protected long[] attrDefSeen = new long[Constants.ATTR_CONTEXT_LIMIT]; - - // What pseudo-attribute bits are there to watch for? - protected int[] attrOverflowMask = new int[Constants.ATTR_CONTEXT_LIMIT]; - protected int attrClassFileVersionMask; - - // Mapping from Attribute.Layout to Band[] (layout element bands). - protected Map attrBandTable = new HashMap<>(); - - // Well-known attributes: - protected final Attribute.Layout attrCodeEmpty; - protected final Attribute.Layout attrInnerClassesEmpty; - protected final Attribute.Layout attrClassFileVersion; - protected final Attribute.Layout attrConstantValue; - - // Mapping from Attribute.Layout to Integer (inverse of attrDefs) - Map attrIndexTable = new HashMap<>(); - - // Mapping from attribute index (<32 are flag bits) to attributes. - protected List> attrDefs = - new FixedList<>(Constants.ATTR_CONTEXT_LIMIT); - { - for (int i = 0; i < Constants.ATTR_CONTEXT_LIMIT; i++) { - assert(attrIndexLimit[i] == 0); - attrIndexLimit[i] = 32; // just for the sake of predefs. - attrDefs.set(i, new ArrayList<>(Collections.nCopies( - attrIndexLimit[i], (Attribute.Layout)null))); - - } - - // Add predefined attribute definitions: - attrInnerClassesEmpty = - predefineAttribute(Constants.CLASS_ATTR_InnerClasses, Constants.ATTR_CONTEXT_CLASS, null, - "InnerClasses", ""); - assert(attrInnerClassesEmpty == Package.attrInnerClassesEmpty); - predefineAttribute(Constants.CLASS_ATTR_SourceFile, Constants.ATTR_CONTEXT_CLASS, - new Band[] { class_SourceFile_RUN }, - "SourceFile", "RUNH"); - predefineAttribute(Constants.CLASS_ATTR_EnclosingMethod, Constants.ATTR_CONTEXT_CLASS, - new Band[] { - class_EnclosingMethod_RC, - class_EnclosingMethod_RDN - }, - "EnclosingMethod", "RCHRDNH"); - attrClassFileVersion = - predefineAttribute(Constants.CLASS_ATTR_ClassFile_version, Constants.ATTR_CONTEXT_CLASS, - new Band[] { - class_ClassFile_version_minor_H, - class_ClassFile_version_major_H - }, - ".ClassFile.version", "HH"); - predefineAttribute(Constants.X_ATTR_Signature, Constants.ATTR_CONTEXT_CLASS, - new Band[] { class_Signature_RS }, - "Signature", "RSH"); - predefineAttribute(Constants.X_ATTR_Deprecated, Constants.ATTR_CONTEXT_CLASS, null, - "Deprecated", ""); - //predefineAttribute(X_ATTR_Synthetic, ATTR_CONTEXT_CLASS, null, - // "Synthetic", ""); - predefineAttribute(Constants.X_ATTR_OVERFLOW, Constants.ATTR_CONTEXT_CLASS, null, - ".Overflow", ""); - attrConstantValue = - predefineAttribute(Constants.FIELD_ATTR_ConstantValue, Constants.ATTR_CONTEXT_FIELD, - new Band[] { field_ConstantValue_KQ }, - "ConstantValue", "KQH"); - predefineAttribute(Constants.X_ATTR_Signature, Constants.ATTR_CONTEXT_FIELD, - new Band[] { field_Signature_RS }, - "Signature", "RSH"); - predefineAttribute(Constants.X_ATTR_Deprecated, Constants.ATTR_CONTEXT_FIELD, null, - "Deprecated", ""); - //predefineAttribute(X_ATTR_Synthetic, ATTR_CONTEXT_FIELD, null, - // "Synthetic", ""); - predefineAttribute(Constants.X_ATTR_OVERFLOW, Constants.ATTR_CONTEXT_FIELD, null, - ".Overflow", ""); - attrCodeEmpty = - predefineAttribute(Constants.METHOD_ATTR_Code, Constants.ATTR_CONTEXT_METHOD, null, - "Code", ""); - predefineAttribute(Constants.METHOD_ATTR_Exceptions, Constants.ATTR_CONTEXT_METHOD, - new Band[] { - method_Exceptions_N, - method_Exceptions_RC - }, - "Exceptions", "NH[RCH]"); - predefineAttribute(Constants.METHOD_ATTR_MethodParameters, Constants.ATTR_CONTEXT_METHOD, - new Band[]{ - method_MethodParameters_NB, - method_MethodParameters_name_RUN, - method_MethodParameters_flag_FH - }, - "MethodParameters", "NB[RUNHFH]"); - assert(attrCodeEmpty == Package.attrCodeEmpty); - predefineAttribute(Constants.X_ATTR_Signature, Constants.ATTR_CONTEXT_METHOD, - new Band[] { method_Signature_RS }, - "Signature", "RSH"); - predefineAttribute(Constants.X_ATTR_Deprecated, Constants.ATTR_CONTEXT_METHOD, null, - "Deprecated", ""); - //predefineAttribute(X_ATTR_Synthetic, ATTR_CONTEXT_METHOD, null, - // "Synthetic", ""); - predefineAttribute(Constants.X_ATTR_OVERFLOW, Constants.ATTR_CONTEXT_METHOD, null, - ".Overflow", ""); - - for (int ctype = 0; ctype < Constants.ATTR_CONTEXT_LIMIT; ctype++) { - MultiBand xxx_metadata_bands = metadataBands[ctype]; - if (ctype != Constants.ATTR_CONTEXT_CODE) { - // These arguments cause the bands to be built - // automatically for this complicated layout: - predefineAttribute(Constants.X_ATTR_RuntimeVisibleAnnotations, - Constants.ATTR_CONTEXT_NAME[ctype]+"_RVA_", - xxx_metadata_bands, - Attribute.lookup(null, ctype, - "RuntimeVisibleAnnotations")); - predefineAttribute(Constants.X_ATTR_RuntimeInvisibleAnnotations, - Constants.ATTR_CONTEXT_NAME[ctype]+"_RIA_", - xxx_metadata_bands, - Attribute.lookup(null, ctype, - "RuntimeInvisibleAnnotations")); - - if (ctype == Constants.ATTR_CONTEXT_METHOD) { - predefineAttribute(Constants.METHOD_ATTR_RuntimeVisibleParameterAnnotations, - "method_RVPA_", xxx_metadata_bands, - Attribute.lookup(null, ctype, - "RuntimeVisibleParameterAnnotations")); - predefineAttribute(Constants.METHOD_ATTR_RuntimeInvisibleParameterAnnotations, - "method_RIPA_", xxx_metadata_bands, - Attribute.lookup(null, ctype, - "RuntimeInvisibleParameterAnnotations")); - predefineAttribute(Constants.METHOD_ATTR_AnnotationDefault, - "method_AD_", xxx_metadata_bands, - Attribute.lookup(null, ctype, - "AnnotationDefault")); - } - } - // All contexts have these - MultiBand xxx_type_metadata_bands = typeMetadataBands[ctype]; - predefineAttribute(Constants.X_ATTR_RuntimeVisibleTypeAnnotations, - Constants.ATTR_CONTEXT_NAME[ctype] + "_RVTA_", - xxx_type_metadata_bands, - Attribute.lookup(null, ctype, - "RuntimeVisibleTypeAnnotations")); - predefineAttribute(Constants.X_ATTR_RuntimeInvisibleTypeAnnotations, - Constants.ATTR_CONTEXT_NAME[ctype] + "_RITA_", - xxx_type_metadata_bands, - Attribute.lookup(null, ctype, - "RuntimeInvisibleTypeAnnotations")); - } - - - Attribute.Layout stackMapDef = Attribute.lookup(null, Constants.ATTR_CONTEXT_CODE, "StackMapTable").layout(); - predefineAttribute(Constants.CODE_ATTR_StackMapTable, Constants.ATTR_CONTEXT_CODE, - stackmap_bands.toArray(), - stackMapDef.name(), stackMapDef.layout()); - - predefineAttribute(Constants.CODE_ATTR_LineNumberTable, Constants.ATTR_CONTEXT_CODE, - new Band[] { - code_LineNumberTable_N, - code_LineNumberTable_bci_P, - code_LineNumberTable_line - }, - "LineNumberTable", "NH[PHH]"); - predefineAttribute(Constants.CODE_ATTR_LocalVariableTable, Constants.ATTR_CONTEXT_CODE, - new Band[] { - code_LocalVariableTable_N, - code_LocalVariableTable_bci_P, - code_LocalVariableTable_span_O, - code_LocalVariableTable_name_RU, - code_LocalVariableTable_type_RS, - code_LocalVariableTable_slot - }, - "LocalVariableTable", "NH[PHOHRUHRSHH]"); - predefineAttribute(Constants.CODE_ATTR_LocalVariableTypeTable, Constants.ATTR_CONTEXT_CODE, - new Band[] { - code_LocalVariableTypeTable_N, - code_LocalVariableTypeTable_bci_P, - code_LocalVariableTypeTable_span_O, - code_LocalVariableTypeTable_name_RU, - code_LocalVariableTypeTable_type_RS, - code_LocalVariableTypeTable_slot - }, - "LocalVariableTypeTable", "NH[PHOHRUHRSHH]"); - predefineAttribute(Constants.X_ATTR_OVERFLOW, Constants.ATTR_CONTEXT_CODE, null, - ".Overflow", ""); - - // Clear the record of having seen these definitions, - // so they may be redefined without error. - for (int i = 0; i < Constants.ATTR_CONTEXT_LIMIT; i++) { - attrDefSeen[i] = 0; - } - - // Set up the special masks: - for (int i = 0; i < Constants.ATTR_CONTEXT_LIMIT; i++) { - attrOverflowMask[i] = (1< 0) Utils.log.fine("Legacy package version"); - // Revoke definition of pre-1.6 attribute type. - undefineAttribute(Constants.CODE_ATTR_StackMapTable, Constants.ATTR_CONTEXT_CODE); - } - } - - protected void initAttrIndexLimit() { - for (int i = 0; i < Constants.ATTR_CONTEXT_LIMIT; i++) { - assert(attrIndexLimit[i] == 0); // decide on it now! - attrIndexLimit[i] = (haveFlagsHi(i)? 63: 32); - List defList = attrDefs.get(i); - assert(defList.size() == 32); // all predef indexes are <32 - int addMore = attrIndexLimit[i] - defList.size(); - defList.addAll(Collections.nCopies(addMore, (Attribute.Layout) null)); - } - } - - protected boolean haveFlagsHi(int ctype) { - int mask = 1<<(Constants.LG_AO_HAVE_XXX_FLAGS_HI+ctype); - switch (ctype) { - case Constants.ATTR_CONTEXT_CLASS: - assert(mask == Constants.AO_HAVE_CLASS_FLAGS_HI); break; - case Constants.ATTR_CONTEXT_FIELD: - assert(mask == Constants.AO_HAVE_FIELD_FLAGS_HI); break; - case Constants.ATTR_CONTEXT_METHOD: - assert(mask == Constants.AO_HAVE_METHOD_FLAGS_HI); break; - case Constants.ATTR_CONTEXT_CODE: - assert(mask == Constants.AO_HAVE_CODE_FLAGS_HI); break; - default: - assert(false); - } - return testBit(archiveOptions, mask); - } - - protected List getPredefinedAttrs(int ctype) { - assert(attrIndexLimit[ctype] != 0); - List res = new ArrayList<>(attrIndexLimit[ctype]); - // Remove nulls and non-predefs. - for (int ai = 0; ai < attrIndexLimit[ctype]; ai++) { - if (testBit(attrDefSeen[ctype], 1L<= attrIndexLimit[ctype]) return false; - // If the bit is set, it was explicitly def'd. - if (testBit(attrDefSeen[ctype], 1L<= 0) { - setAttributeLayoutIndex(def, index); - } - if (ab == null) { - ab = new Band[0]; - } - assert(attrBandTable.get(def) == null); // no redef - attrBandTable.put(def, ab); - assert(def.bandCount == ab.length) - : (def+" // "+Arrays.asList(ab)); - // Let's make sure the band types match: - assert(assertBandOKForElems(ab, def.elems)); - return def; - } - - // This version takes bandPrefix/addHere instead of prebuilt Band[] ab. - private - Attribute.Layout predefineAttribute(int index, - String bandPrefix, MultiBand addHere, - Attribute attr) { - //Attribute.Layout def = Attribute.find(ctype, name, layout).layout(); - Attribute.Layout def = attr.layout(); - int ctype = def.ctype(); - return predefineAttribute(index, ctype, - makeNewAttributeBands(bandPrefix, def, addHere), - def.name(), def.layout()); - } - - private - void undefineAttribute(int index, int ctype) { - if (verbose > 1) { - System.out.println("Removing predefined "+Constants.ATTR_CONTEXT_NAME[ctype]+ - " attribute on bit "+index); - } - List defList = attrDefs.get(ctype); - Attribute.Layout def = defList.get(index); - assert(def != null); - defList.set(index, null); - attrIndexTable.put(def, null); - // Clear the def bit. (For predefs, it's already clear.) - assert(index < 64); - attrDefSeen[ctype] &= ~(1L< 1) - Utils.log.fine("Making new bands for "+def); - Band[] newAB = makeNewAttributeBands(pfx, def, - xxx_attr_bands); - assert(newAB.length == def.bandCount); - Band[] prevAB = attrBandTable.put(def, newAB); - if (prevAB != null) { - // We won't be using these predefined bands. - for (int j = 0; j < prevAB.length; j++) { - prevAB[j].doneWithUnusedBand(); - } - } - } - } - //System.out.println(prevForAssertMap); - } - private - Band[] makeNewAttributeBands(String pfx, Attribute.Layout def, - MultiBand addHere) { - int base = addHere.size(); - makeNewAttributeBands(pfx, def.elems, addHere); - int nb = addHere.size() - base; - Band[] newAB = new Band[nb]; - for (int i = 0; i < nb; i++) { - newAB[i] = addHere.get(base+i); - } - return newAB; - } - // Recursive helper, operates on a "body" or other sequence of elems: - private - void makeNewAttributeBands(String pfx, Attribute.Layout.Element[] elems, - MultiBand ab) { - for (int i = 0; i < elems.length; i++) { - Attribute.Layout.Element e = elems[i]; - String name = pfx+ab.size()+"_"+e.layout; - { - int tem; - if ((tem = name.indexOf('[')) > 0) - name = name.substring(0, tem); - if ((tem = name.indexOf('(')) > 0) - name = name.substring(0, tem); - if (name.endsWith("H")) - name = name.substring(0, name.length()-1); - } - Band nb; - switch (e.kind) { - case Attribute.EK_INT: - nb = newElemBand(e, name, ab); - break; - case Attribute.EK_BCI: - if (!e.flagTest(Attribute.EF_DELTA)) { - // PH: transmit R(bci), store bci - nb = ab.newIntBand(name, BCI5); - } else { - // POH: transmit D(R(bci)), store bci - nb = ab.newIntBand(name, BRANCH5); - } - // Note: No case for BYTE1 here. - break; - case Attribute.EK_BCO: - // OH: transmit D(R(bci)), store D(bci) - nb = ab.newIntBand(name, BRANCH5); - // Note: No case for BYTE1 here. - break; - case Attribute.EK_FLAG: - assert(!e.flagTest(Attribute.EF_SIGN)); - nb = newElemBand(e, name, ab); - break; - case Attribute.EK_REPL: - assert(!e.flagTest(Attribute.EF_SIGN)); - nb = newElemBand(e, name, ab); - makeNewAttributeBands(pfx, e.body, ab); - break; - case Attribute.EK_UN: - nb = newElemBand(e, name, ab); - makeNewAttributeBands(pfx, e.body, ab); - break; - case Attribute.EK_CASE: - if (!e.flagTest(Attribute.EF_BACK)) { - // If it's not a duplicate body, make the bands. - makeNewAttributeBands(pfx, e.body, ab); - } - continue; // no new band to make - case Attribute.EK_REF: - byte refKind = e.refKind; - boolean nullOK = e.flagTest(Attribute.EF_NULL); - nb = ab.newCPRefBand(name, UNSIGNED5, refKind, nullOK); - // Note: No case for BYTE1 here. - break; - case Attribute.EK_CALL: - continue; // no new band to make - case Attribute.EK_CBLE: - makeNewAttributeBands(pfx, e.body, ab); - continue; // no new band to make - default: assert(false); continue; - } - if (verbose > 1) { - Utils.log.fine("New attribute band "+nb); - } - } - } - private - Band newElemBand(Attribute.Layout.Element e, String name, MultiBand ab) { - if (e.flagTest(Attribute.EF_SIGN)) { - return ab.newIntBand(name, SIGNED5); - } else if (e.len == 1) { - return ab.newIntBand(name, BYTE1); // Not ByteBand, please. - } else { - return ab.newIntBand(name, UNSIGNED5); - } - } - - protected int setAttributeLayoutIndex(Attribute.Layout def, int index) { - int ctype = def.ctype; - assert(ATTR_INDEX_OVERFLOW <= index && index < attrIndexLimit[ctype]); - List defList = attrDefs.get(ctype); - if (index == ATTR_INDEX_OVERFLOW) { - // Overflow attribute. - index = defList.size(); - defList.add(def); - if (verbose > 0) - Utils.log.info("Adding new attribute at "+def +": "+index); - attrIndexTable.put(def, index); - return index; - } - - // Detect redefinitions: - if (testBit(attrDefSeen[ctype], 1L< (attrClassFileVersionMask == 0? 2:0)) - Utils.log.fine("Fixing new attribute at "+index - +": "+def - +(defList.get(index) == null? "": - "; replacing "+defList.get(index))); - attrFlagMask[ctype] |= (1L<= shortCodeLimits.length) return LONG_CODE_HEADER; - int siglen = code.getMethod().getArgumentSize(); - assert(l0 >= siglen); // enough locals for signature! - if (l0 < siglen) return LONG_CODE_HEADER; - int l1 = l0 - siglen; // do not count locals required by the signature - int lims = shortCodeLimits[h][0]; - int liml = shortCodeLimits[h][1]; - if (s >= lims || l1 >= liml) return LONG_CODE_HEADER; - int sc = shortCodeHeader_h_base(h); - sc += s + lims*l1; - if (sc > 255) return LONG_CODE_HEADER; - assert(shortCodeHeader_max_stack(sc) == s); - assert(shortCodeHeader_max_na_locals(sc) == l1); - assert(shortCodeHeader_handler_count(sc) == h); - return sc; - } - - static final int LONG_CODE_HEADER = 0; - static int shortCodeHeader_handler_count(int sc) { - assert(sc > 0 && sc <= 255); - for (int h = 0; ; h++) { - if (sc < shortCodeHeader_h_base(h+1)) - return h; - } - } - static int shortCodeHeader_max_stack(int sc) { - int h = shortCodeHeader_handler_count(sc); - int lims = shortCodeLimits[h][0]; - return (sc - shortCodeHeader_h_base(h)) % lims; - } - static int shortCodeHeader_max_na_locals(int sc) { - int h = shortCodeHeader_handler_count(sc); - int lims = shortCodeLimits[h][0]; - return (sc - shortCodeHeader_h_base(h)) / lims; - } - - private static int shortCodeHeader_h_base(int h) { - assert(h <= shortCodeLimits.length); - int sc = 1; - for (int h0 = 0; h0 < h; h0++) { - int lims = shortCodeLimits[h0][0]; - int liml = shortCodeLimits[h0][1]; - sc += lims * liml; - } - return sc; - } - - // utilities for accessing the bc_label band: - protected void putLabel(IntBand bc_label, Code c, int pc, int targetPC) { - bc_label.putInt(c.encodeBCI(targetPC) - c.encodeBCI(pc)); - } - protected int getLabel(IntBand bc_label, Code c, int pc) { - return c.decodeBCI(bc_label.getInt() + c.encodeBCI(pc)); - } - - protected CPRefBand getCPRefOpBand(int bc) { - switch (Instruction.getCPRefOpTag(bc)) { - case Constants.CONSTANT_Class: - return bc_classref; - case Constants.CONSTANT_Fieldref: - return bc_fieldref; - case Constants.CONSTANT_Methodref: - return bc_methodref; - case Constants.CONSTANT_InterfaceMethodref: - return bc_imethodref; - case Constants.CONSTANT_InvokeDynamic: - return bc_indyref; - case Constants.CONSTANT_LoadableValue: - switch (bc) { - case Constants._ildc: case Constants._ildc_w: - return bc_intref; - case Constants._fldc: case Constants._fldc_w: - return bc_floatref; - case Constants._lldc2_w: - return bc_longref; - case Constants._dldc2_w: - return bc_doubleref; - case Constants._sldc: case Constants._sldc_w: - return bc_stringref; - case Constants._cldc: case Constants._cldc_w: - return bc_classref; - case Constants._qldc: case Constants._qldc_w: - return bc_loadablevalueref; - } - break; - } - assert(false); - return null; - } - - protected CPRefBand selfOpRefBand(int self_bc) { - assert(Instruction.isSelfLinkerOp(self_bc)); - int idx = (self_bc - Constants._self_linker_op); - boolean isSuper = (idx >= Constants._self_linker_super_flag); - if (isSuper) idx -= Constants._self_linker_super_flag; - boolean isAload = (idx >= Constants._self_linker_aload_flag); - if (isAload) idx -= Constants._self_linker_aload_flag; - int origBC = Constants._first_linker_op + idx; - boolean isField = Instruction.isFieldOp(origBC); - if (!isSuper) - return isField? bc_thisfield: bc_thismethod; - else - return isField? bc_superfield: bc_supermethod; - } - - //////////////////////////////////////////////////////////////////// - - static int nextSeqForDebug; - static File dumpDir = null; - static OutputStream getDumpStream(Band b, String ext) throws IOException { - return getDumpStream(b.name, b.seqForDebug, ext, b); - } - static OutputStream getDumpStream(ConstantPool.Index ix, String ext) throws IOException { - if (ix.size() == 0) return new ByteArrayOutputStream(); - int seq = ConstantPool.TAG_ORDER[ix.cpMap[0].tag]; - return getDumpStream(ix.debugName, seq, ext, ix); - } - static OutputStream getDumpStream(String name, int seq, String ext, Object b) throws IOException { - if (dumpDir == null) { - dumpDir = File.createTempFile("BD_", "", new File(".")); - dumpDir.delete(); - if (dumpDir.mkdir()) - Utils.log.info("Dumping bands to "+dumpDir); - } - name = name.replace('(', ' ').replace(')', ' '); - name = name.replace('/', ' '); - name = name.replace('*', ' '); - name = name.trim().replace(' ','_'); - name = ((10000+seq) + "_" + name).substring(1); - File dumpFile = new File(dumpDir, name+ext); - Utils.log.info("Dumping "+b+" to "+dumpFile); - return new BufferedOutputStream(new FileOutputStream(dumpFile)); - } - - // DEBUG ONLY: Validate me at each length change. - static boolean assertCanChangeLength(Band b) { - switch (b.phase) { - case COLLECT_PHASE: - case READ_PHASE: - return true; - } - return false; - } - - // DEBUG ONLY: Validate a phase. - static boolean assertPhase(Band b, int phaseExpected) { - if (b.phase() != phaseExpected) { - Utils.log.warning("phase expected "+phaseExpected+" was "+b.phase()+" in "+b); - return false; - } - return true; - } - - - // DEBUG ONLY: Tells whether verbosity is turned on. - static int verbose() { - return Utils.currentPropMap().getInteger(Utils.DEBUG_VERBOSE); - } - - - // DEBUG ONLY: Validate me at each phase change. - static boolean assertPhaseChangeOK(Band b, int p0, int p1) { - switch (p0*10+p1) { - /// Writing phases: - case NO_PHASE*10+COLLECT_PHASE: - // Ready to collect data from the input classes. - assert(!b.isReader()); - assert(b.capacity() >= 0); - assert(b.length() == 0); - return true; - case COLLECT_PHASE*10+FROZEN_PHASE: - case FROZEN_PHASE*10+FROZEN_PHASE: - assert(b.length() == 0); - return true; - case COLLECT_PHASE*10+WRITE_PHASE: - case FROZEN_PHASE*10+WRITE_PHASE: - // Data is all collected. Ready to write bytes to disk. - return true; - case WRITE_PHASE*10+DONE_PHASE: - // Done writing to disk. Ready to reset, in principle. - return true; - - /// Reading phases: - case NO_PHASE*10+EXPECT_PHASE: - assert(b.isReader()); - assert(b.capacity() < 0); - return true; - case EXPECT_PHASE*10+READ_PHASE: - // Ready to read values from disk. - assert(Math.max(0,b.capacity()) >= b.valuesExpected()); - assert(b.length() <= 0); - return true; - case READ_PHASE*10+DISBURSE_PHASE: - // Ready to disburse values. - assert(b.valuesRemainingForDebug() == b.length()); - return true; - case DISBURSE_PHASE*10+DONE_PHASE: - // Done disbursing values. Ready to reset, in principle. - assert(assertDoneDisbursing(b)); - return true; - } - if (p0 == p1) - Utils.log.warning("Already in phase "+p0); - else - Utils.log.warning("Unexpected phase "+p0+" -> "+p1); - return false; - } - - private static boolean assertDoneDisbursing(Band b) { - if (b.phase != DISBURSE_PHASE) { - Utils.log.warning("assertDoneDisbursing: still in phase "+b.phase+": "+b); - if (verbose() <= 1) return false; // fail now - } - int left = b.valuesRemainingForDebug(); - if (left > 0) { - Utils.log.warning("assertDoneDisbursing: "+left+" values left in "+b); - if (verbose() <= 1) return false; // fail now - } - if (b instanceof MultiBand) { - MultiBand mb = (MultiBand) b; - for (int i = 0; i < mb.bandCount; i++) { - Band sub = mb.bands[i]; - if (sub.phase != DONE_PHASE) { - Utils.log.warning("assertDoneDisbursing: sub-band still in phase "+sub.phase+": "+sub); - if (verbose() <= 1) return false; // fail now - } - } - } - return true; - } - - private static void printCDecl(Band b) { - if (b instanceof MultiBand) { - MultiBand mb = (MultiBand) b; - for (int i = 0; i < mb.bandCount; i++) { - printCDecl(mb.bands[i]); - } - return; - } - String ixS = "NULL"; - if (b instanceof CPRefBand) { - ConstantPool.Index ix = ((CPRefBand)b).index; - if (ix != null) ixS = "INDEX("+ix.debugName+")"; - } - Coding[] knownc = { BYTE1, CHAR3, BCI5, BRANCH5, UNSIGNED5, - UDELTA5, SIGNED5, DELTA5, MDELTA5 }; - String[] knowns = { "BYTE1", "CHAR3", "BCI5", "BRANCH5", "UNSIGNED5", - "UDELTA5", "SIGNED5", "DELTA5", "MDELTA5" }; - Coding rc = b.regularCoding; - int rci = Arrays.asList(knownc).indexOf(rc); - String cstr; - if (rci >= 0) - cstr = knowns[rci]; - else - cstr = "CODING"+rc.keyString(); - System.out.println(" BAND_INIT(\""+b.name()+"\"" - +", "+cstr+", "+ixS+"),"); - } - - private Map prevForAssertMap; - - // DEBUG ONLY: Record something about the band order. - boolean notePrevForAssert(Band b, Band p) { - if (prevForAssertMap == null) - prevForAssertMap = new HashMap<>(); - prevForAssertMap.put(b, p); - return true; - } - - // DEBUG ONLY: Validate next input band, ensure bands are read in sequence - private boolean assertReadyToReadFrom(Band b, InputStream in) throws IOException { - Band p = prevForAssertMap.get(b); - // Any previous band must be done reading before this one starts. - if (p != null && phaseCmp(p.phase(), DISBURSE_PHASE) < 0) { - Utils.log.warning("Previous band not done reading."); - Utils.log.info(" Previous band: "+p); - Utils.log.info(" Next band: "+b); - assert(verbose > 0); // die unless verbose is true - } - String name = b.name; - if (optDebugBands && !name.startsWith("(")) { - assert(bandSequenceList != null); - // Verify synchronization between reader & writer: - String inName = bandSequenceList.removeFirst(); - // System.out.println("Reading: " + name); - if (!inName.equals(name)) { - Utils.log.warning("Expected " + name + " but read: " + inName); - return false; - } - Utils.log.info("Read band in sequence: " + name); - } - return true; - } - - // DEBUG ONLY: Make sure a bunch of cprefs are correct. - private boolean assertValidCPRefs(CPRefBand b) { - if (b.index == null) return true; - int limit = b.index.size()+1; - for (int i = 0; i < b.length(); i++) { - int v = b.valueAtForDebug(i); - if (v < 0 || v >= limit) { - Utils.log.warning("CP ref out of range "+ - "["+i+"] = "+v+" in "+b); - return false; - } - } - return true; - } - - /* - * DEBUG ONLY: write the bands to a list and read back the list in order, - * this works perfectly if we use the java packer and unpacker, typically - * this will work with --repack or if they are in the same jvm instance. - */ - static LinkedList bandSequenceList = null; - private boolean assertReadyToWriteTo(Band b, OutputStream out) throws IOException { - Band p = prevForAssertMap.get(b); - // Any previous band must be done writing before this one starts. - if (p != null && phaseCmp(p.phase(), DONE_PHASE) < 0) { - Utils.log.warning("Previous band not done writing."); - Utils.log.info(" Previous band: "+p); - Utils.log.info(" Next band: "+b); - assert(verbose > 0); // die unless verbose is true - } - String name = b.name; - if (optDebugBands && !name.startsWith("(")) { - if (bandSequenceList == null) - bandSequenceList = new LinkedList<>(); - // Verify synchronization between reader & writer: - bandSequenceList.add(name); - // System.out.println("Writing: " + b); - } - return true; - } - - protected static boolean testBit(int flags, int bitMask) { - return (flags & bitMask) != 0; - } - protected static int setBit(int flags, int bitMask, boolean z) { - return z ? (flags | bitMask) : (flags &~ bitMask); - } - protected static boolean testBit(long flags, long bitMask) { - return (flags & bitMask) != 0; - } - protected static long setBit(long flags, long bitMask, boolean z) { - return z ? (flags | bitMask) : (flags &~ bitMask); - } - - - static void printArrayTo(PrintStream ps, int[] values, int start, int end) { - int len = end-start; - for (int i = 0; i < len; i++) { - if (i % 10 == 0) - ps.println(); - else - ps.print(" "); - ps.print(values[start+i]); - } - ps.println(); - } - - static void printArrayTo(PrintStream ps, ConstantPool.Entry[] cpMap, int start, int end) { - printArrayTo(ps, cpMap, start, end, false); - } - static void printArrayTo(PrintStream ps, ConstantPool.Entry[] cpMap, int start, int end, boolean showTags) { - StringBuffer buf = new StringBuffer(); - int len = end-start; - for (int i = 0; i < len; i++) { - ConstantPool.Entry e = cpMap[start+i]; - ps.print(start+i); ps.print("="); - if (showTags) { ps.print(e.tag); ps.print(":"); } - String s = e.stringValue(); - buf.setLength(0); - for (int j = 0; j < s.length(); j++) { - char ch = s.charAt(j); - if (!(ch < ' ' || ch > '~' || ch == '\\')) { - buf.append(ch); - } else if (ch == '\\') { - buf.append("\\\\"); - } else if (ch == '\n') { - buf.append("\\n"); - } else if (ch == '\t') { - buf.append("\\t"); - } else if (ch == '\r') { - buf.append("\\r"); - } else { - String str = "000"+Integer.toHexString(ch); - buf.append("\\u").append(str.substring(str.length()-4)); - } - } - ps.println(buf); - } - } - - - // Utilities for reallocating: - protected static Object[] realloc(Object[] a, int len) { - Class elt = a.getClass().getComponentType(); - Object[] na = (Object[]) java.lang.reflect.Array.newInstance(elt, len); - System.arraycopy(a, 0, na, 0, Math.min(a.length, len)); - return na; - } - protected static Object[] realloc(Object[] a) { - return realloc(a, Math.max(10, a.length*2)); - } - - protected static int[] realloc(int[] a, int len) { - if (len == 0) return Constants.noInts; - if (a == null) return new int[len]; - int[] na = new int[len]; - System.arraycopy(a, 0, na, 0, Math.min(a.length, len)); - return na; - } - protected static int[] realloc(int[] a) { - return realloc(a, Math.max(10, a.length*2)); - } - - protected static byte[] realloc(byte[] a, int len) { - if (len == 0) return Constants.noBytes; - if (a == null) return new byte[len]; - byte[] na = new byte[len]; - System.arraycopy(a, 0, na, 0, Math.min(a.length, len)); - return na; - } - protected static byte[] realloc(byte[] a) { - return realloc(a, Math.max(10, a.length*2)); - } -} diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/ClassReader.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/ClassReader.java deleted file mode 100644 index f55aba900..000000000 --- a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/ClassReader.java +++ /dev/null @@ -1,636 +0,0 @@ -/* - * Copyright (c) 2001, 2013, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package net.fabricmc.shade.com.sun.java.util.jar.pack; - -import java.io.DataInputStream; -import java.io.FilterInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Map; - -/** - * Reader for a class file that is being incorporated into a package. - * @author John Rose - */ -class ClassReader { - int verbose; - - Package pkg; - Package.Class cls; - long inPos; - long constantPoolLimit = -1; - DataInputStream in; - Map attrDefs; - Map attrCommands; - String unknownAttrCommand = "error";; - - ClassReader(Package.Class cls, InputStream in) throws IOException { - this.pkg = cls.getPackage(); - this.cls = cls; - this.verbose = pkg.verbose; - this.in = new DataInputStream(new FilterInputStream(in) { - public int read(byte b[], int off, int len) throws IOException { - int nr = super.read(b, off, len); - if (nr >= 0) inPos += nr; - return nr; - } - public int read() throws IOException { - int ch = super.read(); - if (ch >= 0) inPos += 1; - return ch; - } - public long skip(long n) throws IOException { - long ns = super.skip(n); - if (ns >= 0) inPos += ns; - return ns; - } - }); - } - - public void setAttrDefs(Map attrDefs) { - this.attrDefs = attrDefs; - } - - public void setAttrCommands(Map attrCommands) { - this.attrCommands = attrCommands; - } - - private void skip(int n, String what) throws IOException { - Utils.log.warning("skipping "+n+" bytes of "+what); - long skipped = 0; - while (skipped < n) { - long j = in.skip(n - skipped); - assert(j > 0); - skipped += j; - } - assert(skipped == n); - } - - private int readUnsignedShort() throws IOException { - return in.readUnsignedShort(); - } - - private int readInt() throws IOException { - return in.readInt(); - } - - /** Read a 2-byte int, and return the global CP entry for it. */ - private ConstantPool.Entry readRef() throws IOException { - int i = in.readUnsignedShort(); - return i == 0 ? null : cls.cpMap[i]; - } - - private ConstantPool.Entry readRef(byte tag) throws IOException { - ConstantPool.Entry e = readRef(); - assert(!(e instanceof UnresolvedEntry)); - checkTag(e, tag); - return e; - } - - private ConstantPool.Entry checkValid(ConstantPool.Entry e) { - if (e == INVALID_ENTRY) { - throw new IllegalStateException("Invalid constant pool reference"); - } - return e; - } - - /** Throw a ClassFormatException if the entry does not match the expected tag type. */ - private ConstantPool.Entry checkTag(ConstantPool.Entry e, byte tag) throws ClassFormatException { - if (e == null || !e.tagMatches(tag)) { - String where = (inPos == constantPoolLimit - ? " in constant pool" - : " at pos: " + inPos); - String got = (e == null - ? "null CP index" - : "type=" + ConstantPool.tagName(e.tag)); - throw new ClassFormatException("Bad constant, expected type=" + - ConstantPool.tagName(tag) + - " got "+ got + ", in File: " + cls.file.nameString + where); - } - return e; - } - private ConstantPool.Entry checkTag(ConstantPool.Entry e, byte tag, boolean nullOK) throws ClassFormatException { - return nullOK && e == null ? null : checkTag(e, tag); - } - - private ConstantPool.Entry readRefOrNull(byte tag) throws IOException { - ConstantPool.Entry e = readRef(); - checkTag(e, tag, true); - return e; - } - - private ConstantPool.Utf8Entry readUtf8Ref() throws IOException { - return (ConstantPool.Utf8Entry) readRef(Constants.CONSTANT_Utf8); - } - - private ConstantPool.ClassEntry readClassRef() throws IOException { - return (ConstantPool.ClassEntry) readRef(Constants.CONSTANT_Class); - } - - private ConstantPool.ClassEntry readClassRefOrNull() throws IOException { - return (ConstantPool.ClassEntry) readRefOrNull(Constants.CONSTANT_Class); - } - - private ConstantPool.SignatureEntry readSignatureRef() throws IOException { - // The class file stores a Utf8, but we want a Signature. - ConstantPool.Entry e = readRef(Constants.CONSTANT_Signature); - return (e != null && e.getTag() == Constants.CONSTANT_Utf8) - ? ConstantPool.getSignatureEntry(e.stringValue()) - : (ConstantPool.SignatureEntry) e; - } - - void read() throws IOException { - boolean ok = false; - try { - readMagicNumbers(); - readConstantPool(); - readHeader(); - readMembers(false); // fields - readMembers(true); // methods - readAttributes(Constants.ATTR_CONTEXT_CLASS, cls); - fixUnresolvedEntries(); - cls.finishReading(); - assert(0 >= in.read(new byte[1])); - ok = true; - } finally { - if (!ok) { - if (verbose > 0) Utils.log.warning("Erroneous data at input offset "+inPos+" of "+cls.file); - } - } - } - - void readMagicNumbers() throws IOException { - cls.magic = in.readInt(); - if (cls.magic != Constants.JAVA_MAGIC) - throw new Attribute.FormatException - ("Bad magic number in class file " - +Integer.toHexString(cls.magic), - Constants.ATTR_CONTEXT_CLASS, "magic-number", "pass"); - int minver = (short) readUnsignedShort(); - int majver = (short) readUnsignedShort(); - cls.version = Package.Version.of(majver, minver); - - //System.out.println("ClassFile.version="+cls.majver+"."+cls.minver); - String bad = checkVersion(cls.version); - if (bad != null) { - throw new Attribute.FormatException - ("classfile version too "+bad+": " - +cls.version+" in "+cls.file, - Constants.ATTR_CONTEXT_CLASS, "version", "pass"); - } - } - - private String checkVersion(Package.Version ver) { - int majver = ver.major; - int minver = ver.minor; - if (majver < pkg.minClassVersion.major || - (majver == pkg.minClassVersion.major && - minver < pkg.minClassVersion.minor)) { - return "small"; - } - if (majver > pkg.maxClassVersion.major || - (majver == pkg.maxClassVersion.major && - minver > pkg.maxClassVersion.minor)) { - return "large"; - } - return null; // OK - } - - // use this identity for invalid references - private static final ConstantPool.Entry INVALID_ENTRY = new ConstantPool.Entry((byte) -1) { - @Override - public boolean equals(Object o) { - throw new IllegalStateException("Should not call this"); - } - - @Override - protected int computeValueHash() { - throw new IllegalStateException("Should not call this"); - } - - @Override - public int compareTo(Object o) { - throw new IllegalStateException("Should not call this"); - } - - @Override - public String stringValue() { - throw new IllegalStateException("Should not call this"); - } - }; - - void readConstantPool() throws IOException { - int length = in.readUnsignedShort(); - //System.err.println("reading CP, length="+length); - - int[] fixups = new int[length*4]; - int fptr = 0; - - ConstantPool.Entry[] cpMap = new ConstantPool.Entry[length]; - cpMap[0] = INVALID_ENTRY; - for (int i = 1; i < length; i++) { - //System.err.println("reading CP elt, i="+i); - int tag = in.readByte(); - switch (tag) { - case Constants.CONSTANT_Utf8: - cpMap[i] = ConstantPool.getUtf8Entry(in.readUTF()); - break; - case Constants.CONSTANT_Integer: - { - cpMap[i] = ConstantPool.getLiteralEntry(in.readInt()); - } - break; - case Constants.CONSTANT_Float: - { - cpMap[i] = ConstantPool.getLiteralEntry(in.readFloat()); - } - break; - case Constants.CONSTANT_Long: - { - cpMap[i] = ConstantPool.getLiteralEntry(in.readLong()); - cpMap[++i] = INVALID_ENTRY; - } - break; - case Constants.CONSTANT_Double: - { - cpMap[i] = ConstantPool.getLiteralEntry(in.readDouble()); - cpMap[++i] = INVALID_ENTRY; - } - break; - - // just read the refs; do not attempt to resolve while reading - case Constants.CONSTANT_Class: - case Constants.CONSTANT_String: - case Constants.CONSTANT_MethodType: - fixups[fptr++] = i; - fixups[fptr++] = tag; - fixups[fptr++] = in.readUnsignedShort(); - fixups[fptr++] = -1; // empty ref2 - break; - case Constants.CONSTANT_Fieldref: - case Constants.CONSTANT_Methodref: - case Constants.CONSTANT_InterfaceMethodref: - case Constants.CONSTANT_NameandType: - fixups[fptr++] = i; - fixups[fptr++] = tag; - fixups[fptr++] = in.readUnsignedShort(); - fixups[fptr++] = in.readUnsignedShort(); - break; - case Constants.CONSTANT_InvokeDynamic: - fixups[fptr++] = i; - fixups[fptr++] = tag; - fixups[fptr++] = -1 ^ in.readUnsignedShort(); // not a ref - fixups[fptr++] = in.readUnsignedShort(); - break; - case Constants.CONSTANT_MethodHandle: - fixups[fptr++] = i; - fixups[fptr++] = tag; - fixups[fptr++] = -1 ^ in.readUnsignedByte(); - fixups[fptr++] = in.readUnsignedShort(); - break; - default: - throw new ClassFormatException("Bad constant pool tag " + - tag + " in File: " + cls.file.nameString + - " at pos: " + inPos); - } - } - constantPoolLimit = inPos; - - // Fix up refs, which might be out of order. - while (fptr > 0) { - if (verbose > 3) - Utils.log.fine("CP fixups ["+fptr/4+"]"); - int flimit = fptr; - fptr = 0; - for (int fi = 0; fi < flimit; ) { - int cpi = fixups[fi++]; - int tag = fixups[fi++]; - int ref = fixups[fi++]; - int ref2 = fixups[fi++]; - if (verbose > 3) - Utils.log.fine(" cp["+cpi+"] = "+ConstantPool.tagName(tag)+"{"+ref+","+ref2+"}"); - if (ref >= 0 && checkValid(cpMap[ref]) == null || ref2 >= 0 && checkValid(cpMap[ref2]) == null) { - // Defer. - fixups[fptr++] = cpi; - fixups[fptr++] = tag; - fixups[fptr++] = ref; - fixups[fptr++] = ref2; - continue; - } - switch (tag) { - case Constants.CONSTANT_Class: - cpMap[cpi] = ConstantPool.getClassEntry(cpMap[ref].stringValue()); - break; - case Constants.CONSTANT_String: - cpMap[cpi] = ConstantPool.getStringEntry(cpMap[ref].stringValue()); - break; - case Constants.CONSTANT_Fieldref: - case Constants.CONSTANT_Methodref: - case Constants.CONSTANT_InterfaceMethodref: - ConstantPool.ClassEntry mclass = (ConstantPool.ClassEntry) checkTag(cpMap[ref], Constants.CONSTANT_Class); - ConstantPool.DescriptorEntry mdescr = (ConstantPool.DescriptorEntry) checkTag(cpMap[ref2], Constants.CONSTANT_NameandType); - cpMap[cpi] = ConstantPool.getMemberEntry((byte)tag, mclass, mdescr); - break; - case Constants.CONSTANT_NameandType: - ConstantPool.Utf8Entry mname = (ConstantPool.Utf8Entry) checkTag(cpMap[ref], Constants.CONSTANT_Utf8); - ConstantPool.Utf8Entry mtype = (ConstantPool.Utf8Entry) checkTag(cpMap[ref2], Constants.CONSTANT_Signature); - cpMap[cpi] = ConstantPool.getDescriptorEntry(mname, mtype); - break; - case Constants.CONSTANT_MethodType: - cpMap[cpi] = ConstantPool.getMethodTypeEntry((ConstantPool.Utf8Entry) checkTag(cpMap[ref], Constants.CONSTANT_Signature)); - break; - case Constants.CONSTANT_MethodHandle: - byte refKind = (byte)(-1 ^ ref); - ConstantPool.MemberEntry memRef = (ConstantPool.MemberEntry) checkTag(cpMap[ref2], Constants.CONSTANT_AnyMember); - cpMap[cpi] = ConstantPool.getMethodHandleEntry(refKind, memRef); - break; - case Constants.CONSTANT_InvokeDynamic: - ConstantPool.DescriptorEntry idescr = (ConstantPool.DescriptorEntry) checkTag(cpMap[ref2], Constants.CONSTANT_NameandType); - cpMap[cpi] = new UnresolvedEntry((byte)tag, (-1 ^ ref), idescr); - // Note that ref must be resolved later, using the BootstrapMethods attribute. - break; - default: - assert(false); - } - } - assert(fptr < flimit); // Must make progress. - } - - cls.cpMap = cpMap; - } - private /*non-static*/ - class UnresolvedEntry extends ConstantPool.Entry { - final Object[] refsOrIndexes; - UnresolvedEntry(byte tag, Object... refsOrIndexes) { - super(tag); - this.refsOrIndexes = refsOrIndexes; - ClassReader.this.haveUnresolvedEntry = true; - } - ConstantPool.Entry resolve() { - Package.Class cls = ClassReader.this.cls; - ConstantPool.Entry res; - switch (tag) { - case Constants.CONSTANT_InvokeDynamic: - ConstantPool.BootstrapMethodEntry iboots = cls.bootstrapMethods.get((Integer) refsOrIndexes[0]); - ConstantPool.DescriptorEntry idescr = (ConstantPool.DescriptorEntry) refsOrIndexes[1]; - res = ConstantPool.getInvokeDynamicEntry(iboots, idescr); - break; - default: - throw new AssertionError(); - } - return res; - } - private void unresolved() { throw new RuntimeException("unresolved entry has no string"); } - public int compareTo(Object x) { unresolved(); return 0; } - public boolean equals(Object x) { unresolved(); return false; } - protected int computeValueHash() { unresolved(); return 0; } - public String stringValue() { unresolved(); return toString(); } - public String toString() { return "(unresolved "+ConstantPool.tagName(tag)+")"; } - } - - boolean haveUnresolvedEntry; - private void fixUnresolvedEntries() { - if (!haveUnresolvedEntry) return; - ConstantPool.Entry[] cpMap = cls.getCPMap(); - for (int i = 0; i < cpMap.length; i++) { - ConstantPool.Entry e = cpMap[i]; - if (e instanceof UnresolvedEntry) { - cpMap[i] = e = ((UnresolvedEntry)e).resolve(); - assert(!(e instanceof UnresolvedEntry)); - } - } - haveUnresolvedEntry = false; - } - - void readHeader() throws IOException { - cls.flags = readUnsignedShort(); - cls.thisClass = readClassRef(); - cls.superClass = readClassRefOrNull(); - int ni = readUnsignedShort(); - cls.interfaces = new ConstantPool.ClassEntry[ni]; - for (int i = 0; i < ni; i++) { - cls.interfaces[i] = readClassRef(); - } - } - - void readMembers(boolean doMethods) throws IOException { - int nm = readUnsignedShort(); - for (int i = 0; i < nm; i++) { - readMember(doMethods); - } - } - - void readMember(boolean doMethod) throws IOException { - int mflags = readUnsignedShort(); - ConstantPool.Utf8Entry mname = readUtf8Ref(); - ConstantPool.SignatureEntry mtype = readSignatureRef(); - ConstantPool.DescriptorEntry descr = ConstantPool.getDescriptorEntry(mname, mtype); - Package.Class.Member m; - if (!doMethod) - m = cls.new Field(mflags, descr); - else - m = cls.new Method(mflags, descr); - readAttributes(!doMethod ? Constants.ATTR_CONTEXT_FIELD : Constants.ATTR_CONTEXT_METHOD, - m); - } - void readAttributes(int ctype, Attribute.Holder h) throws IOException { - int na = readUnsignedShort(); - if (na == 0) return; // nothing to do here - if (verbose > 3) - Utils.log.fine("readAttributes "+h+" ["+na+"]"); - for (int i = 0; i < na; i++) { - String name = readUtf8Ref().stringValue(); - int length = readInt(); - // See if there is a special command that applies. - if (attrCommands != null) { - Attribute.Layout lkey = Attribute.keyForLookup(ctype, name); - String cmd = attrCommands.get(lkey); - if (cmd != null) { - switch (cmd) { - case "pass": - String message1 = "passing attribute bitwise in " + h; - throw new Attribute.FormatException(message1, ctype, name, cmd); - case "error": - String message2 = "attribute not allowed in " + h; - throw new Attribute.FormatException(message2, ctype, name, cmd); - case "strip": - skip(length, name + " attribute in " + h); - continue; - } - } - } - // Find canonical instance of the requested attribute. - Attribute a = Attribute.lookup(Package.attrDefs, ctype, name); - if (verbose > 4 && a != null) - Utils.log.fine("pkg_attribute_lookup "+name+" = "+a); - if (a == null) { - a = Attribute.lookup(this.attrDefs, ctype, name); - if (verbose > 4 && a != null) - Utils.log.fine("this "+name+" = "+a); - } - if (a == null) { - a = Attribute.lookup(null, ctype, name); - if (verbose > 4 && a != null) - Utils.log.fine("null_attribute_lookup "+name+" = "+a); - } - if (a == null && length == 0) { - // Any zero-length attr is "known"... - // We can assume an empty attr. has an empty layout. - // Handles markers like Enum, Bridge, Synthetic, Deprecated. - a = Attribute.find(ctype, name, ""); - } - boolean isStackMap = (ctype == Constants.ATTR_CONTEXT_CODE - && (name.equals("StackMap") || - name.equals("StackMapX"))); - if (isStackMap) { - // Known attribute but with a corner case format, "pass" it. - Code code = (Code) h; - final int TOO_BIG = 0x10000; - if (code.max_stack >= TOO_BIG || - code.max_locals >= TOO_BIG || - code.getLength() >= TOO_BIG || - name.endsWith("X")) { - // No, we don't really know what to do with this one. - // Do not compress the rare and strange "u4" and "X" cases. - a = null; - } - } - if (a == null) { - if (isStackMap) { - // Known attribute but w/o a format; pass it. - String message = "unsupported StackMap variant in "+h; - throw new Attribute.FormatException(message, ctype, name, - "pass"); - } else if ("strip".equals(unknownAttrCommand)) { - // Skip the unknown attribute. - skip(length, "unknown "+name+" attribute in "+h); - continue; - } else { - String message = " is unknown attribute in class " + h; - throw new Attribute.FormatException(message, ctype, name, - unknownAttrCommand); - } - } - long pos0 = inPos; // in case we want to check it - if (a.layout() == Package.attrCodeEmpty) { - // These are hardwired. - Package.Class.Method m = (Package.Class.Method) h; - m.code = new Code(m); - try { - readCode(m.code); - } catch (Instruction.FormatException iie) { - String message = iie.getMessage() + " in " + h; - throw new ClassFormatException(message, iie); - } - assert(length == inPos - pos0); - // Keep empty attribute a... - } else if (a.layout() == Package.attrBootstrapMethodsEmpty) { - assert(h == cls); - readBootstrapMethods(cls); - assert(length == inPos - pos0); - // Delete the attribute; it is logically part of the constant pool. - continue; - } else if (a.layout() == Package.attrInnerClassesEmpty) { - // These are hardwired also. - assert(h == cls); - readInnerClasses(cls); - assert(length == inPos - pos0); - // Keep empty attribute a... - } else if (length > 0) { - byte[] bytes = new byte[length]; - in.readFully(bytes); - a = a.addContent(bytes); - } - if (a.size() == 0 && !a.layout().isEmpty()) { - throw new ClassFormatException(name + - ": attribute length cannot be zero, in " + h); - } - h.addAttribute(a); - if (verbose > 2) - Utils.log.fine("read "+a); - } - } - - void readCode(Code code) throws IOException { - code.max_stack = readUnsignedShort(); - code.max_locals = readUnsignedShort(); - code.bytes = new byte[readInt()]; - in.readFully(code.bytes); - ConstantPool.Entry[] cpMap = cls.getCPMap(); - Instruction.opcodeChecker(code.bytes, cpMap, this.cls.version); - int nh = readUnsignedShort(); - code.setHandlerCount(nh); - for (int i = 0; i < nh; i++) { - code.handler_start[i] = readUnsignedShort(); - code.handler_end[i] = readUnsignedShort(); - code.handler_catch[i] = readUnsignedShort(); - code.handler_class[i] = readClassRefOrNull(); - } - readAttributes(Constants.ATTR_CONTEXT_CODE, code); - } - - void readBootstrapMethods(Package.Class cls) throws IOException { - ConstantPool.BootstrapMethodEntry[] bsms = new ConstantPool.BootstrapMethodEntry[readUnsignedShort()]; - for (int i = 0; i < bsms.length; i++) { - ConstantPool.MethodHandleEntry bsmRef = (ConstantPool.MethodHandleEntry) readRef(Constants.CONSTANT_MethodHandle); - ConstantPool.Entry[] argRefs = new ConstantPool.Entry[readUnsignedShort()]; - for (int j = 0; j < argRefs.length; j++) { - argRefs[j] = readRef(Constants.CONSTANT_LoadableValue); - } - bsms[i] = ConstantPool.getBootstrapMethodEntry(bsmRef, argRefs); - } - cls.setBootstrapMethods(Arrays.asList(bsms)); - } - - void readInnerClasses(Package.Class cls) throws IOException { - int nc = readUnsignedShort(); - ArrayList ics = new ArrayList<>(nc); - for (int i = 0; i < nc; i++) { - Package.InnerClass ic = - new Package.InnerClass(readClassRef(), - readClassRefOrNull(), - (ConstantPool.Utf8Entry)readRefOrNull(Constants.CONSTANT_Utf8), - readUnsignedShort()); - ics.add(ic); - } - cls.innerClasses = ics; // set directly; do not use setInnerClasses. - // (Later, ics may be transferred to the pkg.) - } - - static class ClassFormatException extends IOException { - private static final long serialVersionUID = -3564121733989501833L; - - public ClassFormatException(String message) { - super(message); - } - - public ClassFormatException(String message, Throwable cause) { - super(message, cause); - } - } -} diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/ClassWriter.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/ClassWriter.java deleted file mode 100644 index 503be0c95..000000000 --- a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/ClassWriter.java +++ /dev/null @@ -1,310 +0,0 @@ -/* - * Copyright (c) 2001, 2012, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package net.fabricmc.shade.com.sun.java.util.jar.pack; - - -import java.io.BufferedOutputStream; -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.util.List; - -/** - * Writer for a class file that is incorporated into a package. - * @author John Rose - */ -class ClassWriter { - int verbose; - - Package pkg; - Package.Class cls; - DataOutputStream out; - ConstantPool.Index cpIndex; - ConstantPool.Index bsmIndex; - - ClassWriter(Package.Class cls, OutputStream out) throws IOException { - this.pkg = cls.getPackage(); - this.cls = cls; - this.verbose = pkg.verbose; - this.out = new DataOutputStream(new BufferedOutputStream(out)); - this.cpIndex = ConstantPool.makeIndex(cls.toString(), cls.getCPMap()); - this.cpIndex.flattenSigs = true; - if (cls.hasBootstrapMethods()) { - this.bsmIndex = ConstantPool.makeIndex(cpIndex.debugName+".BootstrapMethods", - cls.getBootstrapMethodMap()); - } - if (verbose > 1) - Utils.log.fine("local CP="+(verbose > 2 ? cpIndex.dumpString() : cpIndex.toString())); - } - - private void writeShort(int x) throws IOException { - out.writeShort(x); - } - - private void writeInt(int x) throws IOException { - out.writeInt(x); - } - - /** Write a 2-byte int representing a CP entry, using the local cpIndex. */ - private void writeRef(ConstantPool.Entry e) throws IOException { - writeRef(e, cpIndex); - } - - /** Write a 2-byte int representing a CP entry, using the given cpIndex. */ - private void writeRef(ConstantPool.Entry e, ConstantPool.Index cpIndex) throws IOException { - int i = (e == null) ? 0 : cpIndex.indexOf(e); - writeShort(i); - } - - void write() throws IOException { - boolean ok = false; - try { - if (verbose > 1) Utils.log.fine("...writing "+cls); - writeMagicNumbers(); - writeConstantPool(); - writeHeader(); - writeMembers(false); // fields - writeMembers(true); // methods - writeAttributes(Constants.ATTR_CONTEXT_CLASS, cls); - /* Closing here will cause all the underlying - streams to close, Causing the jar stream - to close prematurely, instead we just flush. - out.close(); - */ - out.flush(); - ok = true; - } finally { - if (!ok) { - Utils.log.warning("Error on output of "+cls); - } - } - } - - void writeMagicNumbers() throws IOException { - writeInt(cls.magic); - writeShort(cls.version.minor); - writeShort(cls.version.major); - } - - void writeConstantPool() throws IOException { - ConstantPool.Entry[] cpMap = cls.cpMap; - writeShort(cpMap.length); - for (int i = 0; i < cpMap.length; i++) { - ConstantPool.Entry e = cpMap[i]; - assert((e == null) == (i == 0 || cpMap[i-1] != null && cpMap[i-1].isDoubleWord())); - if (e == null) continue; - byte tag = e.getTag(); - if (verbose > 2) Utils.log.fine(" CP["+i+"] = "+e); - out.write(tag); - switch (tag) { - case Constants.CONSTANT_Signature: - throw new AssertionError("CP should have Signatures remapped to Utf8"); - case Constants.CONSTANT_Utf8: - out.writeUTF(e.stringValue()); - break; - case Constants.CONSTANT_Integer: - out.writeInt(((ConstantPool.NumberEntry)e).numberValue().intValue()); - break; - case Constants.CONSTANT_Float: - float fval = ((ConstantPool.NumberEntry)e).numberValue().floatValue(); - out.writeInt(Float.floatToRawIntBits(fval)); - break; - case Constants.CONSTANT_Long: - out.writeLong(((ConstantPool.NumberEntry)e).numberValue().longValue()); - break; - case Constants.CONSTANT_Double: - double dval = ((ConstantPool.NumberEntry)e).numberValue().doubleValue(); - out.writeLong(Double.doubleToRawLongBits(dval)); - break; - case Constants.CONSTANT_Class: - case Constants.CONSTANT_String: - case Constants.CONSTANT_MethodType: - writeRef(e.getRef(0)); - break; - case Constants.CONSTANT_MethodHandle: - ConstantPool.MethodHandleEntry mhe = (ConstantPool.MethodHandleEntry) e; - out.writeByte(mhe.refKind); - writeRef(mhe.getRef(0)); - break; - case Constants.CONSTANT_Fieldref: - case Constants.CONSTANT_Methodref: - case Constants.CONSTANT_InterfaceMethodref: - case Constants.CONSTANT_NameandType: - writeRef(e.getRef(0)); - writeRef(e.getRef(1)); - break; - case Constants.CONSTANT_InvokeDynamic: - writeRef(e.getRef(0), bsmIndex); - writeRef(e.getRef(1)); - break; - case Constants.CONSTANT_BootstrapMethod: - throw new AssertionError("CP should have BootstrapMethods moved to side-table"); - default: - throw new IOException("Bad constant pool tag "+tag); - } - } - } - - void writeHeader() throws IOException { - writeShort(cls.flags); - writeRef(cls.thisClass); - writeRef(cls.superClass); - writeShort(cls.interfaces.length); - for (int i = 0; i < cls.interfaces.length; i++) { - writeRef(cls.interfaces[i]); - } - } - - void writeMembers(boolean doMethods) throws IOException { - List mems; - if (!doMethods) - mems = cls.getFields(); - else - mems = cls.getMethods(); - writeShort(mems.size()); - for (Package.Class.Member m : mems) { - writeMember(m, doMethods); - } - } - - void writeMember(Package.Class.Member m, boolean doMethod) throws IOException { - if (verbose > 2) Utils.log.fine("writeMember "+m); - writeShort(m.flags); - writeRef(m.getDescriptor().nameRef); - writeRef(m.getDescriptor().typeRef); - writeAttributes(!doMethod ? Constants.ATTR_CONTEXT_FIELD : Constants.ATTR_CONTEXT_METHOD, - m); - } - - private void reorderBSMandICS(Attribute.Holder h) { - Attribute bsmAttr = h.getAttribute(Package.attrBootstrapMethodsEmpty); - if (bsmAttr == null) return; - - Attribute icsAttr = h.getAttribute(Package.attrInnerClassesEmpty); - if (icsAttr == null) return; - - int bsmidx = h.attributes.indexOf(bsmAttr); - int icsidx = h.attributes.indexOf(icsAttr); - if (bsmidx > icsidx) { - h.attributes.remove(bsmAttr); - h.attributes.add(icsidx, bsmAttr); - } - return; - } - - // handy buffer for collecting attrs - ByteArrayOutputStream buf = new ByteArrayOutputStream(); - DataOutputStream bufOut = new DataOutputStream(buf); - - void writeAttributes(int ctype, Attribute.Holder h) throws IOException { - if (h.attributes == null) { - writeShort(0); // attribute size - return; - } - // there may be cases if an InnerClass attribute is explicit, then the - // ordering could be wrong, fix the ordering before we write it out. - if (h instanceof Package.Class) - reorderBSMandICS(h); - - writeShort(h.attributes.size()); - for (Attribute a : h.attributes) { - a.finishRefs(cpIndex); - writeRef(a.getNameRef()); - if (a.layout() == Package.attrCodeEmpty || - a.layout() == Package.attrBootstrapMethodsEmpty || - a.layout() == Package.attrInnerClassesEmpty) { - // These are hardwired. - DataOutputStream savedOut = out; - assert(out != bufOut); - buf.reset(); - out = bufOut; - if ("Code".equals(a.name())) { - Package.Class.Method m = (Package.Class.Method) h; - writeCode(m.code); - } else if ("BootstrapMethods".equals(a.name())) { - assert(h == cls); - writeBootstrapMethods(cls); - } else if ("InnerClasses".equals(a.name())) { - assert(h == cls); - writeInnerClasses(cls); - } else { - throw new AssertionError(); - } - out = savedOut; - if (verbose > 2) - Utils.log.fine("Attribute "+a.name()+" ["+buf.size()+"]"); - writeInt(buf.size()); - buf.writeTo(out); - } else { - if (verbose > 2) - Utils.log.fine("Attribute "+a.name()+" ["+a.size()+"]"); - writeInt(a.size()); - out.write(a.bytes()); - } - } - } - - void writeCode(Code code) throws IOException { - code.finishRefs(cpIndex); - writeShort(code.max_stack); - writeShort(code.max_locals); - writeInt(code.bytes.length); - out.write(code.bytes); - int nh = code.getHandlerCount(); - writeShort(nh); - for (int i = 0; i < nh; i++) { - writeShort(code.handler_start[i]); - writeShort(code.handler_end[i]); - writeShort(code.handler_catch[i]); - writeRef(code.handler_class[i]); - } - writeAttributes(Constants.ATTR_CONTEXT_CODE, code); - } - - void writeBootstrapMethods(Package.Class cls) throws IOException { - List bsms = cls.getBootstrapMethods(); - writeShort(bsms.size()); - for (ConstantPool.BootstrapMethodEntry e : bsms) { - writeRef(e.bsmRef); - writeShort(e.argRefs.length); - for (ConstantPool.Entry argRef : e.argRefs) { - writeRef(argRef); - } - } - } - - void writeInnerClasses(Package.Class cls) throws IOException { - List ics = cls.getInnerClasses(); - writeShort(ics.size()); - for (Package.InnerClass ic : ics) { - writeRef(ic.thisClass); - writeRef(ic.outerClass); - writeRef(ic.name); - writeShort(ic.flags); - } - } -} diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Code.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Code.java deleted file mode 100644 index 424bd7f57..000000000 --- a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Code.java +++ /dev/null @@ -1,396 +0,0 @@ -/* - * Copyright (c) 2001, 2013, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package net.fabricmc.shade.com.sun.java.util.jar.pack; - -import java.lang.reflect.Modifier; -import java.util.Arrays; -import java.util.Collection; - -/** - * Represents a chunk of bytecodes. - * @author John Rose - */ -class Code extends Attribute.Holder { - Package.Class.Method m; - - public Code(Package.Class.Method m) { - this.m = m; - } - - public Package.Class.Method getMethod() { - return m; - } - public Package.Class thisClass() { - return m.thisClass(); - } - public Package getPackage() { - return m.thisClass().getPackage(); - } - - public ConstantPool.Entry[] getCPMap() { - return m.getCPMap(); - } - - private static final ConstantPool.Entry[] noRefs = ConstantPool.noRefs; - - // The following fields are used directly by the ClassReader, etc. - int max_stack; - int max_locals; - - ConstantPool.Entry handler_class[] = noRefs; - int handler_start[] = Constants.noInts; - int handler_end[] = Constants.noInts; - int handler_catch[] = Constants.noInts; - - byte[] bytes; - Fixups fixups; // reference relocations, if any are required - Object insnMap; // array of instruction boundaries - - int getLength() { return bytes.length; } - - int getMaxStack() { - return max_stack; - } - void setMaxStack(int ms) { - max_stack = ms; - } - - int getMaxNALocals() { - int argsize = m.getArgumentSize(); - return max_locals - argsize; - } - void setMaxNALocals(int ml) { - int argsize = m.getArgumentSize(); - max_locals = argsize + ml; - } - - int getHandlerCount() { - assert(handler_class.length == handler_start.length); - assert(handler_class.length == handler_end.length); - assert(handler_class.length == handler_catch.length); - return handler_class.length; - } - void setHandlerCount(int h) { - if (h > 0) { - handler_class = new ConstantPool.Entry[h]; - handler_start = new int[h]; - handler_end = new int[h]; - handler_catch = new int[h]; - // caller must fill these in ASAP - } - } - - void setBytes(byte[] bytes) { - this.bytes = bytes; - if (fixups != null) - fixups.setBytes(bytes); - } - - void setInstructionMap(int[] insnMap, int mapLen) { - //int[] oldMap = null; - //assert((oldMap = getInstructionMap()) != null); - this.insnMap = allocateInstructionMap(insnMap, mapLen); - //assert(Arrays.equals(oldMap, getInstructionMap())); - } - void setInstructionMap(int[] insnMap) { - setInstructionMap(insnMap, insnMap.length); - } - - int[] getInstructionMap() { - return expandInstructionMap(getInsnMap()); - } - - void addFixups(Collection moreFixups) { - if (fixups == null) { - fixups = new Fixups(bytes); - } - assert(fixups.getBytes() == bytes); - fixups.addAll(moreFixups); - } - - public void trimToSize() { - if (fixups != null) { - fixups.trimToSize(); - if (fixups.size() == 0) - fixups = null; - } - super.trimToSize(); - } - - protected void visitRefs(int mode, Collection refs) { - int verbose = getPackage().verbose; - if (verbose > 2) - System.out.println("Reference scan "+this); - refs.addAll(Arrays.asList(handler_class)); - if (fixups != null) { - fixups.visitRefs(refs); - } else { - // References (to a local cpMap) are embedded in the bytes. - ConstantPool.Entry[] cpMap = getCPMap(); - for (Instruction i = instructionAt(0); i != null; i = i.next()) { - if (verbose > 4) - System.out.println(i); - int cpref = i.getCPIndex(); - if (cpref >= 0) { - refs.add(cpMap[cpref]); - } - } - } - // Handle attribute list: - super.visitRefs(mode, refs); - } - - // Since bytecodes are the single largest contributor to - // package size, it's worth a little bit of trouble - // to reduce the per-bytecode memory footprint. - // In the current scheme, half of the bulk of these arrays - // due to bytes, and half to shorts. (Ints are insignificant.) - // Given an average of 1.8 bytes per instruction, this means - // instruction boundary arrays are about a 75% overhead--tolerable. - // (By using bytes, we get 33% savings over just shorts and ints. - // Using both bytes and shorts gives 66% savings over just ints.) - static final boolean shrinkMaps = true; - - private Object allocateInstructionMap(int[] insnMap, int mapLen) { - int PClimit = getLength(); - if (shrinkMaps && PClimit <= Byte.MAX_VALUE - Byte.MIN_VALUE) { - byte[] map = new byte[mapLen+1]; - for (int i = 0; i < mapLen; i++) { - map[i] = (byte)(insnMap[i] + Byte.MIN_VALUE); - } - map[mapLen] = (byte)(PClimit + Byte.MIN_VALUE); - return map; - } else if (shrinkMaps && PClimit < Short.MAX_VALUE - Short.MIN_VALUE) { - short[] map = new short[mapLen+1]; - for (int i = 0; i < mapLen; i++) { - map[i] = (short)(insnMap[i] + Short.MIN_VALUE); - } - map[mapLen] = (short)(PClimit + Short.MIN_VALUE); - return map; - } else { - int[] map = Arrays.copyOf(insnMap, mapLen + 1); - map[mapLen] = PClimit; - return map; - } - } - private int[] expandInstructionMap(Object map0) { - int[] imap; - if (map0 instanceof byte[]) { - byte[] map = (byte[]) map0; - imap = new int[map.length-1]; - for (int i = 0; i < imap.length; i++) { - imap[i] = map[i] - Byte.MIN_VALUE; - } - } else if (map0 instanceof short[]) { - short[] map = (short[]) map0; - imap = new int[map.length-1]; - for (int i = 0; i < imap.length; i++) { - imap[i] = map[i] - Byte.MIN_VALUE; - } - } else { - int[] map = (int[]) map0; - imap = Arrays.copyOfRange(map, 0, map.length - 1); - } - return imap; - } - - Object getInsnMap() { - // Build a map of instruction boundaries. - if (insnMap != null) { - return insnMap; - } - int[] map = new int[getLength()]; - int fillp = 0; - for (Instruction i = instructionAt(0); i != null; i = i.next()) { - map[fillp++] = i.getPC(); - } - // Make it byte[], short[], or int[] according to the max BCI. - insnMap = allocateInstructionMap(map, fillp); - //assert(assertBCICodingsOK()); - return insnMap; - } - - /** Encode the given BCI as an instruction boundary number. - * For completeness, irregular (non-boundary) BCIs are - * encoded compactly immediately after the boundary numbers. - * This encoding is the identity mapping outside 0..length, - * and it is 1-1 everywhere. All by itself this technique - * improved zipped rt.jar compression by 2.6%. - */ - public int encodeBCI(int bci) { - if (bci <= 0 || bci > getLength()) return bci; - Object map0 = getInsnMap(); - int i, len; - if (shrinkMaps && map0 instanceof byte[]) { - byte[] map = (byte[]) map0; - len = map.length; - i = Arrays.binarySearch(map, (byte)(bci + Byte.MIN_VALUE)); - } else if (shrinkMaps && map0 instanceof short[]) { - short[] map = (short[]) map0; - len = map.length; - i = Arrays.binarySearch(map, (short)(bci + Short.MIN_VALUE)); - } else { - int[] map = (int[]) map0; - len = map.length; - i = Arrays.binarySearch(map, bci); - } - assert(i != -1); - assert(i != 0); - assert(i != len); - assert(i != -len-1); - return (i >= 0) ? i : len + bci - (-i-1); - } - public int decodeBCI(int bciCode) { - if (bciCode <= 0 || bciCode > getLength()) return bciCode; - Object map0 = getInsnMap(); - int i, len; - // len == map.length - // If bciCode < len, result is map[bciCode], the common and fast case. - // Otherwise, let map[i] be the smallest map[*] larger than bci. - // Then, required by the return statement of encodeBCI: - // bciCode == len + bci - i - // Thus: - // bci-i == bciCode-len - // map[i]-adj-i == bciCode-len ; adj in (0..map[i]-map[i-1]) - // We can solve this by searching for adjacent entries - // map[i-1], map[i] such that: - // map[i-1]-(i-1) <= bciCode-len < map[i]-i - // This can be approximated by searching map[i] for bciCode and then - // linear searching backward. Given the right i, we then have: - // bci == bciCode-len + i - // This linear search is at its worst case for indexes in the beginning - // of a large method, but it's not clear that this is a problem in - // practice, since BCIs are usually on instruction boundaries. - if (shrinkMaps && map0 instanceof byte[]) { - byte[] map = (byte[]) map0; - len = map.length; - if (bciCode < len) - return map[bciCode] - Byte.MIN_VALUE; - i = Arrays.binarySearch(map, (byte)(bciCode + Byte.MIN_VALUE)); - if (i < 0) i = -i-1; - int key = bciCode-len + Byte.MIN_VALUE; - for (;; i--) { - if (map[i-1]-(i-1) <= key) break; - } - } else if (shrinkMaps && map0 instanceof short[]) { - short[] map = (short[]) map0; - len = map.length; - if (bciCode < len) - return map[bciCode] - Short.MIN_VALUE; - i = Arrays.binarySearch(map, (short)(bciCode + Short.MIN_VALUE)); - if (i < 0) i = -i-1; - int key = bciCode-len + Short.MIN_VALUE; - for (;; i--) { - if (map[i-1]-(i-1) <= key) break; - } - } else { - int[] map = (int[]) map0; - len = map.length; - if (bciCode < len) - return map[bciCode]; - i = Arrays.binarySearch(map, bciCode); - if (i < 0) i = -i-1; - int key = bciCode-len; - for (;; i--) { - if (map[i-1]-(i-1) <= key) break; - } - } - return bciCode-len + i; - } - - public void finishRefs(ConstantPool.Index ix) { - if (fixups != null) { - fixups.finishRefs(ix); - fixups = null; - } - // Code attributes are finished in ClassWriter.writeAttributes. - } - - Instruction instructionAt(int pc) { - return Instruction.at(bytes, pc); - } - - static boolean flagsRequireCode(int flags) { - // A method's flags force it to have a Code attribute, - // if the flags are neither native nor abstract. - return (flags & (Modifier.NATIVE | Modifier.ABSTRACT)) == 0; - } - - public String toString() { - return m+".Code"; - } - - /// Fetching values from my own array. - public int getInt(int pc) { return Instruction.getInt(bytes, pc); } - public int getShort(int pc) { return Instruction.getShort(bytes, pc); } - public int getByte(int pc) { return Instruction.getByte(bytes, pc); } - void setInt(int pc, int x) { Instruction.setInt(bytes, pc, x); } - void setShort(int pc, int x) { Instruction.setShort(bytes, pc, x); } - void setByte(int pc, int x) { Instruction.setByte(bytes, pc, x); } - -/* TEST CODE ONLY - private boolean assertBCICodingsOK() { - boolean ok = true; - int len = java.lang.reflect.Array.getLength(insnMap); - int base = 0; - if (insnMap.getClass().getComponentType() == Byte.TYPE) - base = Byte.MIN_VALUE; - if (insnMap.getClass().getComponentType() == Short.TYPE) - base = Short.MIN_VALUE; - for (int i = -1, imax = getLength()+1; i <= imax; i++) { - int bci = i; - int enc = Math.min(-999, bci-1); - int dec = enc; - try { - enc = encodeBCI(bci); - dec = decodeBCI(enc); - } catch (RuntimeException ee) { - ee.printStackTrace(); - } - if (dec == bci) { - //System.out.println("BCI="+bci+(enc, CodingMethod, Histogram.BitMetric { - /* - Coding schema for single integers, parameterized by (B,H,S): - - Let B in [1,5], H in [1,256], S in [0,3]. - (S limit is arbitrary. B follows the 32-bit limit. H is byte size.) - - A given (B,H,S) code varies in length from 1 to B bytes. - - The 256 values a byte may take on are divided into L=(256-H) and H - values, with all the H values larger than the L values. - (That is, the L values are [0,L) and the H are [L,256).) - - The last byte is always either the B-th byte, a byte with "L value" - (=L). - - Therefore, if L==0, the code always has the full length of B bytes. - The coding then becomes a classic B-byte little-endian unsigned integer. - (Also, if L==128, the high bit of each byte acts signals the presence - of a following byte, up to the maximum length.) - - In the unsigned case (S==0), the coding is compact and monotonic - in the ordering of byte sequences defined by appending zero bytes - to pad them to a common length B, reversing them, and ordering them - lexicographically. (This agrees with "little-endian" byte order.) - - Therefore, the unsigned value of a byte sequence may be defined as: -
-        U(b0)           == b0
-                           in [0..L)
-                           or [0..256) if B==1 (**)
-
-        U(b0,b1)        == b0 + b1*H
-                           in [L..L*(1+H))
-                           or [L..L*(1+H) + H^2) if B==2
-
-        U(b0,b1,b2)     == b0 + b1*H + b2*H^2
-                           in [L*(1+H)..L*(1+H+H^2))
-                           or [L*(1+H)..L*(1+H+H^2) + H^3) if B==3
-
-        U(b[i]: i
-
-      (**) If B==1, the values H,L play no role in the coding.
-      As a convention, we require that any (1,H,S) code must always
-      encode values less than H.  Thus, a simple unsigned byte is coded
-      specifically by the code (1,256,0).
-
-      (Properly speaking, the unsigned case should be parameterized as
-      S==Infinity.  If the schema were regular, the case S==0 would really
-      denote a numbering in which all coded values are negative.)
-
-      If S>0, the unsigned value of a byte sequence is regarded as a binary
-      integer.  If any of the S low-order bits are zero, the corresponding
-      signed value will be non-negative.  If all of the S low-order bits
-      (S>0) are one, the corresponding signed value will be negative.
-
-      The non-negative signed values are compact and monotonically increasing
-      (from 0) in the ordering of the corresponding unsigned values.
-
-      The negative signed values are compact and monotonically decreasing
-      (from -1) in the ordering of the corresponding unsigned values.
-
-      In essence, the low-order S bits function as a collective sign bit
-      for negative signed numbers, and as a low-order base-(2^S-1) digit
-      for non-negative signed numbers.
-
-      Therefore, the signed value corresponding to an unsigned value is:
-      
-        Sgn(x)  == x                               if S==0
-        Sgn(x)  == (x / 2^S)*(2^S-1) + (x % 2^S),  if S>0, (x % 2^S) < 2^S-1
-        Sgn(x)  == -(x / 2^S)-1,                   if S>0, (x % 2^S) == 2^S-1
-      
- - Finally, the value of a byte sequence, given the coding parameters - (B,H,S), is defined as: -
-        V(b[i]: i
-
-      The extremal positive and negative signed value for a given range
-      of unsigned values may be found by sign-encoding the largest unsigned
-      value which is not 2^S-1 mod 2^S, and that which is, respectively.
-
-      Because B,H,S are variable, this is not a single coding but a schema
-      of codings.  For optimal compression, it is necessary to adaptively
-      select specific codings to the data being compressed.
-
-      For example, if a sequence of values happens never to be negative,
-      S==0 is the best choice.  If the values are equally balanced between
-      negative and positive, S==1.  If negative values are rare, then S>1
-      is more appropriate.
-
-      A (B,H,S) encoding is called a "subrange" if it does not encode
-      the largest 32-bit value, and if the number R of values it does
-      encode can be expressed as a positive 32-bit value.  (Note that
-      B=1 implies R<=256, B=2 implies R<=65536, etc.)
-
-      A delta version of a given (B,H,S) coding encodes an array of integers
-      by writing their successive differences in the (B,H,S) coding.
-      The original integers themselves may be recovered by making a
-      running accumulation of sum of the differences as they are read.
-
-      As a special case, if a (B,H,S) encoding is a subrange, its delta
-      version will only encode arrays of numbers in the coding's unsigned
-      range, [0..R-1].  The coding of deltas is still in the normal signed
-      range, if S!=0.  During delta encoding, all subtraction results are
-      reduced to the signed range, by adding multiples of R.  Likewise,
-.     during encoding, all addition results are reduced to the unsigned range.
-      This special case for subranges allows the benefits of wraparound
-      when encoding correlated sequences of very small positive numbers.
-     */
-
-    // Code-specific limits:
-    private static int saturate32(long x) {
-        if (x > Integer.MAX_VALUE)   return Integer.MAX_VALUE;
-        if (x < Integer.MIN_VALUE)   return Integer.MIN_VALUE;
-        return (int)x;
-    }
-    private static long codeRangeLong(int B, int H) {
-        return codeRangeLong(B, H, B);
-    }
-    private static long codeRangeLong(int B, int H, int nMax) {
-        // Code range for a all (B,H) codes of length <=nMax (<=B).
-        // n < B:   L*Sum[i= 0 && nMax <= B);
-        assert(B >= 1 && B <= 5);
-        assert(H >= 1 && H <= 256);
-        if (nMax == 0)  return 0;  // no codes of zero length
-        if (B == 1)     return H;  // special case; see (**) above
-        int L = 256-H;
-        long sum = 0;
-        long H_i = 1;
-        for (int n = 1; n <= nMax; n++) {
-            sum += H_i;
-            H_i *= H;
-        }
-        sum *= L;
-        if (nMax == B)
-            sum += H_i;
-        return sum;
-    }
-    /** Largest int representable by (B,H,S) in up to nMax bytes. */
-    public static int codeMax(int B, int H, int S, int nMax) {
-        //assert(S >= 0 && S <= S_MAX);
-        long range = codeRangeLong(B, H, nMax);
-        if (range == 0)
-            return -1;  // degenerate max value for empty set of codes
-        if (S == 0 || range >= (long)1<<32)
-            return saturate32(range-1);
-        long maxPos = range-1;
-        while (isNegativeCode(maxPos, S)) {
-            --maxPos;
-        }
-        if (maxPos < 0)  return -1;  // No positive codings at all.
-        int smax = decodeSign32(maxPos, S);
-        // check for 32-bit wraparound:
-        if (smax < 0)
-            return Integer.MAX_VALUE;
-        return smax;
-    }
-    /** Smallest int representable by (B,H,S) in up to nMax bytes.
-        Returns Integer.MIN_VALUE if 32-bit wraparound covers
-        the entire negative range.
-     */
-    public static int codeMin(int B, int H, int S, int nMax) {
-        //assert(S >= 0 && S <= S_MAX);
-        long range = codeRangeLong(B, H, nMax);
-        if (range >= (long)1<<32 && nMax == B) {
-            // Can code negative values via 32-bit wraparound.
-            return Integer.MIN_VALUE;
-        }
-        if (S == 0) {
-            return 0;
-        }
-        long maxNeg = range-1;
-        while (!isNegativeCode(maxNeg, S))
-            --maxNeg;
-
-        if (maxNeg < 0)  return 0;  // No negative codings at all.
-        return decodeSign32(maxNeg, S);
-    }
-
-    // Some of the arithmetic below is on unsigned 32-bit integers.
-    // These must be represented in Java as longs in the range [0..2^32-1].
-    // The conversion to a signed int is just the Java cast (int), but
-    // the conversion to an unsigned int is the following little method:
-    private static long toUnsigned32(int sx) {
-        return ((long)sx << 32) >>> 32;
-    }
-
-    // Sign encoding:
-    private static boolean isNegativeCode(long ux, int S) {
-        assert(S > 0);
-        assert(ux >= -1);  // can be out of 32-bit range; who cares
-        int Smask = (1< 0);
-        // If S>=2 very low negatives are coded by 32-bit-wrapped positives.
-        // The lowest negative representable by a negative coding is
-        // ~(umax32 >> S), and the next lower number is coded by wrapping
-        // the highest positive:
-        //    CodePos(umax32-1)  ->  (umax32-1)-((umax32-1)>>S)
-        // which simplifies to ~(umax32 >> S)-1.
-        return (0 > sx) && (sx >= ~(-1>>>S));
-    }
-    private static int decodeSign32(long ux, int S) {
-        assert(ux == toUnsigned32((int)ux))  // must be unsigned 32-bit number
-            : (Long.toHexString(ux));
-        if (S == 0) {
-            return (int) ux;  // cast to signed int
-        }
-        int sx;
-        if (isNegativeCode(ux, S)) {
-            // Sgn(x)  == -(x / 2^S)-1
-            sx = ~((int)ux >>> S);
-        } else {
-            // Sgn(x)  == (x / 2^S)*(2^S-1) + (x % 2^S)
-            sx = (int)ux - ((int)ux >>> S);
-        }
-        // Assert special case of S==1:
-        assert(!(S == 1) || sx == (((int)ux >>> 1) ^ -((int)ux & 1)));
-        return sx;
-    }
-    private static long encodeSign32(int sx, int S) {
-        if (S == 0) {
-            return toUnsigned32(sx);  // unsigned 32-bit int
-        }
-        int Smask = (1< "+
-               Integer.toHexString(sx)+" != "+
-               Integer.toHexString(decodeSign32(ux, S)));
-        return ux;
-    }
-
-    // Top-level coding of single integers:
-    public static void writeInt(byte[] out, int[] outpos, int sx, int B, int H, int S) {
-        long ux = encodeSign32(sx, S);
-        assert(ux == toUnsigned32((int)ux));
-        assert(ux < codeRangeLong(B, H))
-            : Long.toHexString(ux);
-        int L = 256-H;
-        long sum = ux;
-        int pos = outpos[0];
-        for (int i = 0; i < B-1; i++) {
-            if (sum < L)
-                break;
-            sum -= L;
-            int b_i = (int)( L + (sum % H) );
-            sum /= H;
-            out[pos++] = (byte)b_i;
-        }
-        out[pos++] = (byte)sum;
-        // Report number of bytes written by updating outpos[0]:
-        outpos[0] = pos;
-        // Check right away for mis-coding.
-        //assert(sx == readInt(out, new int[1], B, H, S));
-    }
-    public static int readInt(byte[] in, int[] inpos, int B, int H, int S) {
-        // U(b[i]: i= 0 && sum < codeRangeLong(B, H));
-        // Report number of bytes read by updating inpos[0]:
-        inpos[0] = pos;
-        return decodeSign32(sum, S);
-    }
-    // The Stream version doesn't fetch a byte unless it is needed for coding.
-    public static int readIntFrom(InputStream in, int B, int H, int S) throws IOException {
-        // U(b[i]: i= 0 && sum < codeRangeLong(B, H));
-        return decodeSign32(sum, S);
-    }
-
-    public static final int B_MAX = 5;    /* B: [1,5] */
-    public static final int H_MAX = 256;  /* H: [1,256] */
-    public static final int S_MAX = 2;    /* S: [0,2] */
-
-    // END OF STATICS.
-
-    private final int B; /*1..5*/       // # bytes (1..5)
-    private final int H; /*1..256*/     // # codes requiring a higher byte
-    private final int L; /*0..255*/     // # codes requiring a higher byte
-    private final int S; /*0..3*/       // # low-order bits representing sign
-    private final int del; /*0..2*/     // type of delta encoding (0 == none)
-    private final int min;              // smallest representable value
-    private final int max;              // largest representable value
-    private final int umin;             // smallest representable uns. value
-    private final int umax;             // largest representable uns. value
-    private final int[] byteMin;        // smallest repr. value, given # bytes
-    private final int[] byteMax;        // largest repr. value, given # bytes
-
-    private Coding(int B, int H, int S) {
-        this(B, H, S, 0);
-    }
-    private Coding(int B, int H, int S, int del) {
-        this.B = B;
-        this.H = H;
-        this.L = 256-H;
-        this.S = S;
-        this.del = del;
-        this.min = codeMin(B, H, S, B);
-        this.max = codeMax(B, H, S, B);
-        this.umin = codeMin(B, H, 0, B);
-        this.umax = codeMax(B, H, 0, B);
-        this.byteMin = new int[B];
-        this.byteMax = new int[B];
-
-        for (int nMax = 1; nMax <= B; nMax++) {
-            byteMin[nMax-1] = codeMin(B, H, S, nMax);
-            byteMax[nMax-1] = codeMax(B, H, S, nMax);
-        }
-    }
-
-    public boolean equals(Object x) {
-        if (!(x instanceof Coding))  return false;
-        Coding that = (Coding) x;
-        if (this.B != that.B)  return false;
-        if (this.H != that.H)  return false;
-        if (this.S != that.S)  return false;
-        if (this.del != that.del)  return false;
-        return true;
-    }
-
-    public int hashCode() {
-        return (del<<14)+(S<<11)+(B<<8)+(H<<0);
-    }
-
-    private static Map codeMap;
-
-    private static synchronized Coding of(int B, int H, int S, int del) {
-        if (codeMap == null)  codeMap = new HashMap<>();
-        Coding x0 = new Coding(B, H, S, del);
-        Coding x1 = codeMap.get(x0);
-        if (x1 == null)  codeMap.put(x0, x1 = x0);
-        return x1;
-    }
-
-    public static Coding of(int B, int H) {
-        return of(B, H, 0, 0);
-    }
-
-    public static Coding of(int B, int H, int S) {
-        return of(B, H, S, 0);
-    }
-
-    public boolean canRepresentValue(int x) {
-        if (isSubrange())
-            return canRepresentUnsigned(x);
-        else
-            return canRepresentSigned(x);
-    }
-    /** Can this coding represent a single value, possibly a delta?
-     *  This ignores the D property.  That is, for delta codings,
-     *  this tests whether a delta value of 'x' can be coded.
-     *  For signed delta codings which produce unsigned end values,
-     *  use canRepresentUnsigned.
-     */
-    public boolean canRepresentSigned(int x) {
-        return (x >= min && x <= max);
-    }
-    /** Can this coding, apart from its S property,
-     *  represent a single value?  (Negative values
-     *  can only be represented via 32-bit overflow,
-     *  so this returns true for negative values
-     *  if isFullRange is true.)
-     */
-    public boolean canRepresentUnsigned(int x) {
-        return (x >= umin && x <= umax);
-    }
-
-    // object-oriented code/decode
-    public int readFrom(byte[] in, int[] inpos) {
-        return readInt(in, inpos, B, H, S);
-    }
-    public void writeTo(byte[] out, int[] outpos, int x) {
-        writeInt(out, outpos, x, B, H, S);
-    }
-
-    // Stream versions
-    public int readFrom(InputStream in) throws IOException {
-        return readIntFrom(in, B, H, S);
-    }
-    public void writeTo(OutputStream out, int x) throws IOException {
-        byte[] buf = new byte[B];
-        int[] pos = new int[1];
-        writeInt(buf, pos, x, B, H, S);
-        out.write(buf, 0, pos[0]);
-    }
-
-    // Stream/array versions
-    public void readArrayFrom(InputStream in, int[] a, int start, int end) throws IOException {
-        // %%% use byte[] buffer
-        for (int i = start; i < end; i++)
-            a[i] = readFrom(in);
-
-        for (int dstep = 0; dstep < del; dstep++) {
-            long state = 0;
-            for (int i = start; i < end; i++) {
-                state += a[i];
-                // Reduce array values to the required range.
-                if (isSubrange()) {
-                    state = reduceToUnsignedRange(state);
-                }
-                a[i] = (int) state;
-            }
-        }
-    }
-    public void writeArrayTo(OutputStream out, int[] a, int start, int end) throws IOException {
-        if (end <= start)  return;
-        for (int dstep = 0; dstep < del; dstep++) {
-            int[] deltas;
-            if (!isSubrange())
-                deltas = makeDeltas(a, start, end, 0, 0);
-            else
-                deltas = makeDeltas(a, start, end, min, max);
-            a = deltas;
-            start = 0;
-            end = deltas.length;
-        }
-        // The following code is a buffered version of this loop:
-        //    for (int i = start; i < end; i++)
-        //        writeTo(out, a[i]);
-        byte[] buf = new byte[1<<8];
-        final int bufmax = buf.length-B;
-        int[] pos = { 0 };
-        for (int i = start; i < end; ) {
-            while (pos[0] <= bufmax) {
-                writeTo(buf, pos, a[i++]);
-                if (i >= end)  break;
-            }
-            out.write(buf, 0, pos[0]);
-            pos[0] = 0;
-        }
-    }
-
-    /** Tell if the range of this coding (number of distinct
-     *  representable values) can be expressed in 32 bits.
-     */
-    boolean isSubrange() {
-        return max < Integer.MAX_VALUE
-            && ((long)max - (long)min + 1) <= Integer.MAX_VALUE;
-    }
-
-    /** Tell if this coding can represent all 32-bit values.
-     *  Note:  Some codings, such as unsigned ones, can be neither
-     *  subranges nor full-range codings.
-     */
-    boolean isFullRange() {
-        return max == Integer.MAX_VALUE && min == Integer.MIN_VALUE;
-    }
-
-    /** Return the number of values this coding (a subrange) can represent. */
-    int getRange() {
-        assert(isSubrange());
-        return (max - min) + 1;  // range includes both min & max
-    }
-
-    Coding setB(int B) { return Coding.of(B, H, S, del); }
-    Coding setH(int H) { return Coding.of(B, H, S, del); }
-    Coding setS(int S) { return Coding.of(B, H, S, del); }
-    Coding setL(int L) { return setH(256-L); }
-    Coding setD(int del) { return Coding.of(B, H, S, del); }
-    Coding getDeltaCoding() { return setD(del+1); }
-
-    /** Return a coding suitable for representing summed, modulo-reduced values. */
-    Coding getValueCoding() {
-        if (isDelta())
-            return Coding.of(B, H, 0, del-1);
-        else
-            return this;
-    }
-
-    /** Reduce the given value to be within this coding's unsigned range,
-     *  by adding or subtracting a multiple of (max-min+1).
-     */
-    int reduceToUnsignedRange(long value) {
-        if (value == (int)value && canRepresentUnsigned((int)value))
-            // already in unsigned range
-            return (int)value;
-        int range = getRange();
-        assert(range > 0);
-        value %= range;
-        if (value < 0)  value += range;
-        assert(canRepresentUnsigned((int)value));
-        return (int)value;
-    }
-
-    int reduceToSignedRange(int value) {
-        if (canRepresentSigned(value))
-            // already in signed range
-            return value;
-        return reduceToSignedRange(value, min, max);
-    }
-    static int reduceToSignedRange(int value, int min, int max) {
-        int range = (max-min+1);
-        assert(range > 0);
-        int value0 = value;
-        value -= min;
-        if (value < 0 && value0 >= 0) {
-            // 32-bit overflow, but the next '%=' op needs to be unsigned
-            value -= range;
-            assert(value >= 0);
-        }
-        value %= range;
-        if (value < 0)  value += range;
-        value += min;
-        assert(min <= value && value <= max);
-        return value;
-    }
-
-    /** Does this coding support at least one negative value?
-        Includes codings that can do so via 32-bit wraparound.
-     */
-    boolean isSigned() {
-        return min < 0;
-    }
-    /** Does this coding code arrays by making successive differences? */
-    boolean isDelta() {
-        return del != 0;
-    }
-
-    public int B() { return B; }
-    public int H() { return H; }
-    public int L() { return L; }
-    public int S() { return S; }
-    public int del() { return del; }
-    public int min() { return min; }
-    public int max() { return max; }
-    public int umin() { return umin; }
-    public int umax() { return umax; }
-    public int byteMin(int b) { return byteMin[b-1]; }
-    public int byteMax(int b) { return byteMax[b-1]; }
-
-    public int compareTo(Coding that) {
-        int dkey = this.del - that.del;
-        if (dkey == 0)
-            dkey = this.B - that.B;
-        if (dkey == 0)
-            dkey = this.H - that.H;
-        if (dkey == 0)
-            dkey = this.S - that.S;
-        return dkey;
-    }
-
-    /** Heuristic measure of the difference between two codings. */
-    public int distanceFrom(Coding that) {
-        int diffdel = this.del - that.del;
-        if (diffdel < 0)  diffdel = -diffdel;
-        int diffS = this.S - that.S;
-        if (diffS < 0)  diffS = -diffS;
-        int diffB = this.B - that.B;
-        if (diffB < 0)  diffB = -diffB;
-        int diffHL;
-        if (this.H == that.H) {
-            diffHL = 0;
-        } else {
-            // Distance in log space of H (<=128) and L (<128).
-            int thisHL = this.getHL();
-            int thatHL = that.getHL();
-            // Double the accuracy of the log:
-            thisHL *= thisHL;
-            thatHL *= thatHL;
-            if (thisHL > thatHL)
-                diffHL = ceil_lg2(1+(thisHL-1)/thatHL);
-            else
-                diffHL = ceil_lg2(1+(thatHL-1)/thisHL);
-        }
-        int norm = 5*(diffdel + diffS + diffB) + diffHL;
-        assert(norm != 0 || this.compareTo(that) == 0);
-        return norm;
-    }
-    private int getHL() {
-        // Follow H in log space by the multiplicative inverse of L.
-        if (H <= 128)  return H;
-        if (L >= 1)    return 128*128/L;
-        return 128*256;
-    }
-
-    /** ceiling(log[2](x)): {1->0, 2->1, 3->2, 4->2, ...} */
-    static int ceil_lg2(int x) {
-        assert(x-1 >= 0);  // x in range (int.MIN_VALUE -> 32)
-        x -= 1;
-        int lg = 0;
-        while (x != 0) {
-            lg++;
-            x >>= 1;
-        }
-        return lg;
-    }
-
-    private static final byte[] byteBitWidths = new byte[0x100];
-    static {
-        for (int b = 0; b < byteBitWidths.length; b++) {
-            byteBitWidths[b] = (byte) ceil_lg2(b + 1);
-        }
-        for (int i = 10; i >= 0; i = (i << 1) - (i >> 3)) {
-            assert(bitWidth(i) == ceil_lg2(i + 1));
-        }
-    }
-
-    /** Number of significant bits in i, not counting sign bits.
-     *  For positive i, it is ceil_lg2(i + 1).
-     */
-    static int bitWidth(int i) {
-        if (i < 0)  i = ~i;  // change sign
-        int w = 0;
-        int lo = i;
-        if (lo < byteBitWidths.length)
-            return byteBitWidths[lo];
-        int hi;
-        hi = (lo >>> 16);
-        if (hi != 0) {
-            lo = hi;
-            w += 16;
-        }
-        hi = (lo >>> 8);
-        if (hi != 0) {
-            lo = hi;
-            w += 8;
-        }
-        w += byteBitWidths[lo];
-        //assert(w == ceil_lg2(i + 1));
-        return w;
-    }
-
-    /** Create an array of successive differences.
-     *  If min==max, accept any and all 32-bit overflow.
-     *  Otherwise, avoid 32-bit overflow, and reduce all differences
-     *  to a value in the given range, by adding or subtracting
-     *  multiples of the range cardinality (max-min+1).
-     *  Also, the values are assumed to be in the range [0..(max-min)].
-     */
-    static int[] makeDeltas(int[] values, int start, int end,
-                            int min, int max) {
-        assert(max >= min);
-        int count = end-start;
-        int[] deltas = new int[count];
-        int state = 0;
-        if (min == max) {
-            for (int i = 0; i < count; i++) {
-                int value = values[start+i];
-                deltas[i] = value - state;
-                state = value;
-            }
-        } else {
-            for (int i = 0; i < count; i++) {
-                int value = values[start+i];
-                assert(value >= 0 && value+min <= max);
-                int delta = value - state;
-                assert(delta == (long)value - (long)state); // no overflow
-                state = value;
-                // Reduce delta values to the required range.
-                delta = reduceToSignedRange(delta, min, max);
-                deltas[i] = delta;
-            }
-        }
-        return deltas;
-    }
-
-    boolean canRepresent(int minValue, int maxValue) {
-        assert(minValue <= maxValue);
-        if (del > 0) {
-            if (isSubrange()) {
-                // We will force the values to reduce to the right subrange.
-                return canRepresentUnsigned(maxValue)
-                    && canRepresentUnsigned(minValue);
-            } else {
-                // Huge range; delta values must assume full 32-bit range.
-                return isFullRange();
-            }
-        }
-        else
-            // final values must be representable
-            return canRepresentSigned(maxValue)
-                && canRepresentSigned(minValue);
-    }
-
-    boolean canRepresent(int[] values, int start, int end) {
-        int len = end-start;
-        if (len == 0)       return true;
-        if (isFullRange())  return true;
-        // Calculate max, min:
-        int lmax = values[start];
-        int lmin = lmax;
-        for (int i = 1; i < len; i++) {
-            int value = values[start+i];
-            if (lmax < value)  lmax = value;
-            if (lmin > value)  lmin = value;
-        }
-        return canRepresent(lmin, lmax);
-    }
-
-    public double getBitLength(int value) {  // implements BitMetric
-        return (double) getLength(value) * 8;
-    }
-
-    /** How many bytes are in the coding of this value?
-     *  Returns Integer.MAX_VALUE if the value has no coding.
-     *  The coding must not be a delta coding, since there is no
-     *  definite size for a single value apart from its context.
-     */
-    public int getLength(int value) {
-        if (isDelta() && isSubrange()) {
-            if (!canRepresentUnsigned(value))
-                return Integer.MAX_VALUE;
-            value = reduceToSignedRange(value);
-        }
-        if (value >= 0) {
-            for (int n = 0; n < B; n++) {
-                if (value <= byteMax[n])  return n+1;
-            }
-        } else {
-            for (int n = 0; n < B; n++) {
-                if (value >= byteMin[n])  return n+1;
-            }
-        }
-        return Integer.MAX_VALUE;
-    }
-
-    public int getLength(int[] values, int start, int end) {
-        int len = end-start;
-        if (B == 1)  return len;
-        if (L == 0)  return len * B;
-        if (isDelta()) {
-            int[] deltas;
-            if (!isSubrange())
-                deltas = makeDeltas(values, start, end, 0, 0);
-            else
-                deltas = makeDeltas(values, start, end, min, max);
-            //return Coding.of(B, H, S).getLength(deltas, 0, len);
-            values = deltas;
-            start = 0;
-        }
-        int sum = len;  // at least 1 byte per
-        // add extra bytes for extra-long values
-        for (int n = 1; n <= B; n++) {
-            // what is the coding interval [min..max] for n bytes?
-            int lmax = byteMax[n-1];
-            int lmin = byteMin[n-1];
-            int longer = 0;  // count of guys longer than n bytes
-            for (int i = 0; i < len; i++) {
-                int value = values[start+i];
-                if (value >= 0) {
-                    if (value > lmax)  longer++;
-                } else {
-                    if (value < lmin)  longer++;
-                }
-            }
-            if (longer == 0)  break;  // no more passes needed
-            if (n == B)  return Integer.MAX_VALUE;  // cannot represent!
-            sum += longer;
-        }
-        return sum;
-    }
-
-    public byte[] getMetaCoding(Coding dflt) {
-        if (dflt == this)  return new byte[]{ (byte) Constants._meta_default };
-        int canonicalIndex = BandStructure.indexOf(this);
-        if (canonicalIndex > 0)
-            return new byte[]{ (byte) canonicalIndex };
-        return new byte[]{
-            (byte)Constants._meta_arb,
-            (byte)(del + 2*S + 8*(B-1)),
-            (byte)(H-1)
-        };
-    }
-    public static int parseMetaCoding(byte[] bytes, int pos, Coding dflt, CodingMethod res[]) {
-        int op = bytes[pos++] & 0xFF;
-        if (Constants._meta_canon_min <= op && op <= Constants._meta_canon_max) {
-            Coding c = BandStructure.codingForIndex(op);
-            assert(c != null);
-            res[0] = c;
-            return pos;
-        }
-        if (op == Constants._meta_arb) {
-            int dsb = bytes[pos++] & 0xFF;
-            int H_1 = bytes[pos++] & 0xFF;
-            int del = dsb % 2;
-            int S = (dsb / 2) % 4;
-            int B = (dsb / 8)+1;
-            int H = H_1+1;
-            if (!((1 <= B && B <= B_MAX) &&
-                  (0 <= S && S <= S_MAX) &&
-                  (1 <= H && H <= H_MAX) &&
-                  (0 <= del && del <= 1))
-                || (B == 1 && H != 256)
-                || (B == 5 && H == 256)) {
-                throw new RuntimeException("Bad arb. coding: ("+B+","+H+","+S+","+del);
-            }
-            res[0] = Coding.of(B, H, S, del);
-            return pos;
-        }
-        return pos-1;  // backup
-    }
-
-
-    public String keyString() {
-        return "("+B+","+H+","+S+","+del+")";
-    }
-
-    public String toString() {
-        String str = "Coding"+keyString();
-        // If -ea, print out more informative strings!
-        //assert((str = stringForDebug()) != null);
-        return str;
-    }
-
-    static boolean verboseStringForDebug = false;
-    String stringForDebug() {
-        String minS = (min == Integer.MIN_VALUE ? "min" : ""+min);
-        String maxS = (max == Integer.MAX_VALUE ? "max" : ""+max);
-        String str = keyString()+" L="+L+" r=["+minS+","+maxS+"]";
-        if (isSubrange())
-            str += " subrange";
-        else if (!isFullRange())
-            str += " MIDRANGE";
-        if (verboseStringForDebug) {
-            str += " {";
-            int prev_range = 0;
-            for (int n = 1; n <= B; n++) {
-                int range_n = saturate32((long)byteMax[n-1] - byteMin[n-1] + 1);
-                assert(range_n == saturate32(codeRangeLong(B, H, n)));
-                range_n -= prev_range;
-                prev_range = range_n;
-                String rngS = (range_n == Integer.MAX_VALUE ? "max" : ""+range_n);
-                str += " #"+n+"="+rngS;
-            }
-            str += " }";
-        }
-        return str;
-    }
-}
diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/CodingChooser.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/CodingChooser.java
deleted file mode 100644
index 829b60a4f..000000000
--- a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/CodingChooser.java
+++ /dev/null
@@ -1,1489 +0,0 @@
-/*
- * Copyright (c) 2002, 2010, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package net.fabricmc.shade.com.sun.java.util.jar.pack;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Random;
-import java.util.Set;
-import java.util.zip.Deflater;
-import java.util.zip.DeflaterOutputStream;
-
-/**
- * Heuristic chooser of basic encodings.
- * Runs "zip" to measure the apparent information content after coding.
- * @author John Rose
- */
-class CodingChooser {
-    int verbose;
-    int effort;
-    boolean optUseHistogram = true;
-    boolean optUsePopulationCoding = true;
-    boolean optUseAdaptiveCoding = true;
-    boolean disablePopCoding;
-    boolean disableRunCoding;
-    boolean topLevel = true;
-
-    // Derived from effort; >1 (<1) means try more (less) experiments
-    // when looking to beat a best score.
-    double fuzz;
-
-    Coding[] allCodingChoices;
-    Choice[] choices;
-    ByteArrayOutputStream context;
-    CodingChooser popHelper;
-    CodingChooser runHelper;
-
-    Random stress;  // If not null, stress mode oracle.
-
-    // Element in sorted set of coding choices:
-    static
-    class Choice {
-        final Coding coding;
-        final int index;       // index in choices
-        final int[] distance;  // cache of distance
-        Choice(Coding coding, int index, int[] distance) {
-            this.coding   = coding;
-            this.index    = index;
-            this.distance = distance;
-        }
-        // These variables are reset and reused:
-        int searchOrder; // order in which it is checked
-        int minDistance; // min distance from already-checked choices
-        int zipSize;     // size of encoding in sample, zipped output
-        int byteSize;    // size of encoding in sample (debug only)
-        int histSize;    // size of encoding, according to histogram
-
-        void reset() {
-            searchOrder = Integer.MAX_VALUE;
-            minDistance = Integer.MAX_VALUE;
-            zipSize = byteSize = histSize = -1;
-        }
-
-        boolean isExtra() {
-            return index < 0;
-        }
-
-        public String toString() {
-            return stringForDebug();
-        }
-
-        private String stringForDebug() {
-            String s = "";
-            if (searchOrder < Integer.MAX_VALUE)
-                s += " so: "+searchOrder;
-            if (minDistance < Integer.MAX_VALUE)
-                s += " md: "+minDistance;
-            if (zipSize > 0)
-                s += " zs: "+zipSize;
-            if (byteSize > 0)
-                s += " bs: "+byteSize;
-            if (histSize > 0)
-                s += " hs: "+histSize;
-            return "Choice["+index+"] "+s+" "+coding;
-        }
-    }
-
-    CodingChooser(int effort, Coding[] allCodingChoices) {
-        PropMap p200 = Utils.currentPropMap();
-        if (p200 != null) {
-            this.verbose
-                = Math.max(p200.getInteger(Utils.DEBUG_VERBOSE),
-                           p200.getInteger(Utils.COM_PREFIX+"verbose.coding"));
-            this.optUseHistogram
-                = !p200.getBoolean(Utils.COM_PREFIX+"no.histogram");
-            this.optUsePopulationCoding
-                = !p200.getBoolean(Utils.COM_PREFIX+"no.population.coding");
-            this.optUseAdaptiveCoding
-                = !p200.getBoolean(Utils.COM_PREFIX+"no.adaptive.coding");
-            int lstress
-                = p200.getInteger(Utils.COM_PREFIX+"stress.coding");
-            if (lstress != 0)
-                this.stress = new Random(lstress);
-        }
-
-        this.effort = effort;
-        // The following line "makes sense" but is too much
-        // work for a simple heuristic.
-        //if (effort > 5)  zipDef.setLevel(effort);
-
-        this.allCodingChoices = allCodingChoices;
-
-        // If effort = 9, look carefully at any solution
-        // whose initial metrics are within 1% of the best
-        // so far.  If effort = 1, look carefully only at
-        // solutions whose initial metrics promise a 1% win.
-        this.fuzz = 1 + (0.0025 * (effort-MID_EFFORT));
-
-        int nc = 0;
-        for (int i = 0; i < allCodingChoices.length; i++) {
-            if (allCodingChoices[i] == null)  continue;
-            nc++;
-        }
-        choices = new Choice[nc];
-        nc = 0;
-        for (int i = 0; i < allCodingChoices.length; i++) {
-            if (allCodingChoices[i] == null)  continue;
-            int[] distance = new int[choices.length];
-            choices[nc++] = new Choice(allCodingChoices[i], i, distance);
-        }
-        for (int i = 0; i < choices.length; i++) {
-            Coding ci = choices[i].coding;
-            assert(ci.distanceFrom(ci) == 0);
-            for (int j = 0; j < i; j++) {
-                Coding cj = choices[j].coding;
-                int dij = ci.distanceFrom(cj);
-                assert(dij > 0);
-                assert(dij == cj.distanceFrom(ci));
-                choices[i].distance[j] = dij;
-                choices[j].distance[i] = dij;
-            }
-        }
-    }
-
-    Choice makeExtraChoice(Coding coding) {
-        int[] distance = new int[choices.length];
-        for (int i = 0; i < distance.length; i++) {
-            Coding ci = choices[i].coding;
-            int dij = coding.distanceFrom(ci);
-            assert(dij > 0);
-            assert(dij == ci.distanceFrom(coding));
-            distance[i] = dij;
-        }
-        Choice c = new Choice(coding, -1, distance);
-        c.reset();
-        return c;
-    }
-
-    ByteArrayOutputStream getContext() {
-        if (context == null)
-            context = new ByteArrayOutputStream(1 << 16);
-        return context;
-    }
-
-    // These variables are reset and reused:
-    private int[] values;
-    private int start, end;  // slice of values
-    private int[] deltas;
-    private int min, max;
-    private Histogram vHist;
-    private Histogram dHist;
-    private int searchOrder;
-    private Choice regularChoice;
-    private Choice bestChoice;
-    private CodingMethod bestMethod;
-    private int bestByteSize;
-    private int bestZipSize;
-    private int targetSize;   // fuzzed target byte size
-
-    private void reset(int[] values, int start, int end) {
-        this.values = values;
-        this.start = start;
-        this.end = end;
-        this.deltas = null;
-        this.min = Integer.MAX_VALUE;
-        this.max = Integer.MIN_VALUE;
-        this.vHist = null;
-        this.dHist = null;
-        this.searchOrder = 0;
-        this.regularChoice = null;
-        this.bestChoice = null;
-        this.bestMethod = null;
-        this.bestZipSize = Integer.MAX_VALUE;
-        this.bestByteSize = Integer.MAX_VALUE;
-        this.targetSize = Integer.MAX_VALUE;
-    }
-
-    public static final int MIN_EFFORT = 1;
-    public static final int MID_EFFORT = 5;
-    public static final int MAX_EFFORT = 9;
-
-    public static final int POP_EFFORT = MID_EFFORT-1;
-    public static final int RUN_EFFORT = MID_EFFORT-2;
-
-    public static final int BYTE_SIZE = 0;
-    public static final int ZIP_SIZE = 1;
-
-    CodingMethod choose(int[] values, int start, int end, Coding regular, int[] sizes) {
-        // Save the value array
-        reset(values, start, end);
-
-        if (effort <= MIN_EFFORT || start >= end) {
-            if (sizes != null) {
-                int[] computed = computeSizePrivate(regular);
-                sizes[BYTE_SIZE] = computed[BYTE_SIZE];
-                sizes[ZIP_SIZE]  = computed[ZIP_SIZE];
-            }
-            return regular;
-        }
-
-        if (optUseHistogram) {
-            getValueHistogram();
-            getDeltaHistogram();
-        }
-
-        for (int i = start; i < end; i++) {
-            int val = values[i];
-            if (min > val)  min = val;
-            if (max < val)  max = val;
-        }
-
-        // Find all the preset choices that might be worth looking at:
-        int numChoices = markUsableChoices(regular);
-
-        if (stress != null) {
-            // Make a random choice.
-            int rand = stress.nextInt(numChoices*2 + 4);
-            CodingMethod coding = null;
-            for (int i = 0; i < choices.length; i++) {
-                Choice c = choices[i];
-                if (c.searchOrder >= 0 && rand-- == 0) {
-                    coding = c.coding;
-                    break;
-                }
-            }
-            if (coding == null) {
-                if ((rand & 7) != 0) {
-                    coding = regular;
-                } else {
-                    // Pick a totally random coding 6% of the time.
-                    coding = stressCoding(min, max);
-                }
-            }
-            if (!disablePopCoding
-                && optUsePopulationCoding
-                && effort >= POP_EFFORT) {
-                coding = stressPopCoding(coding);
-            }
-            if (!disableRunCoding
-                && optUseAdaptiveCoding
-                && effort >= RUN_EFFORT) {
-                coding = stressAdaptiveCoding(coding);
-            }
-            return coding;
-        }
-
-        double searchScale = 1.0;
-        for (int x = effort; x < MAX_EFFORT; x++) {
-            searchScale /= 1.414;  // every 2 effort points doubles work
-        }
-        int searchOrderLimit = (int)Math.ceil( numChoices * searchScale );
-
-        // Start by evaluating the "regular" choice.
-        bestChoice = regularChoice;
-        evaluate(regularChoice);
-        int maxd = updateDistances(regularChoice);
-
-        // save these first-cut numbers for later
-        int zipSize1 = bestZipSize;
-        int byteSize1 = bestByteSize;
-
-        if (regularChoice.coding == regular && topLevel) {
-            // Give credit for being the default; no band header is needed.
-            // Rather than increasing every other size value by the band
-            // header amount, we decrement this one metric, to give it an edge.
-            // Decreasing zipSize by a byte length is conservatively correct,
-            // especially considering that the escape byte is not likely to
-            // zip well with other bytes in the band.
-            int X = BandStructure.encodeEscapeValue(Constants._meta_canon_max, regular);
-            if (regular.canRepresentSigned(X)) {
-                int Xlen = regular.getLength(X);  // band coding header
-                //regularChoice.histSize -= Xlen; // keep exact byteSize
-                //regularChoice.byteSize -= Xlen; // keep exact byteSize
-                regularChoice.zipSize -= Xlen;
-                bestByteSize = regularChoice.byteSize;
-                bestZipSize = regularChoice.zipSize;
-            }
-        }
-
-        int dscale = 1;
-        // Continually select a new choice to evaluate.
-        while (searchOrder < searchOrderLimit) {
-            Choice nextChoice;
-            if (dscale > maxd)  dscale = 1;  // cycle dscale values!
-            int dhi = maxd / dscale;
-            int dlo = maxd / (dscale *= 2) + 1;
-            nextChoice = findChoiceNear(bestChoice, dhi, dlo);
-            if (nextChoice == null)  continue;
-            assert(nextChoice.coding.canRepresent(min, max));
-            evaluate(nextChoice);
-            int nextMaxd = updateDistances(nextChoice);
-            if (nextChoice == bestChoice) {
-                maxd = nextMaxd;
-                if (verbose > 5)  Utils.log.info("maxd = "+maxd);
-            }
-        }
-
-        // Record best "plain coding" choice.
-        Coding plainBest = bestChoice.coding;
-        assert(plainBest == bestMethod);
-
-        if (verbose > 2) {
-            Utils.log.info("chooser: plain result="+bestChoice+" after "+bestChoice.searchOrder+" rounds, "+(regularChoice.zipSize-bestZipSize)+" fewer bytes than regular "+regular);
-        }
-        bestChoice = null;
-
-        if (!disablePopCoding
-            && optUsePopulationCoding
-            && effort >= POP_EFFORT
-            && bestMethod instanceof Coding) {
-            tryPopulationCoding(plainBest);
-        }
-
-        if (!disableRunCoding
-            && optUseAdaptiveCoding
-            && effort >= RUN_EFFORT
-            && bestMethod instanceof Coding) {
-            tryAdaptiveCoding(plainBest);
-        }
-
-        // Pass back the requested information:
-        if (sizes != null) {
-            sizes[BYTE_SIZE] = bestByteSize;
-            sizes[ZIP_SIZE]  = bestZipSize;
-        }
-        if (verbose > 1) {
-            Utils.log.info("chooser: result="+bestMethod+" "+
-                             (zipSize1-bestZipSize)+
-                             " fewer bytes than regular "+regular+
-                             "; win="+pct(zipSize1-bestZipSize, zipSize1));
-        }
-        CodingMethod lbestMethod = this.bestMethod;
-        reset(null, 0, 0);  // for GC
-        return lbestMethod;
-    }
-    CodingMethod choose(int[] values, int start, int end, Coding regular) {
-        return choose(values, start, end, regular, null);
-    }
-    CodingMethod choose(int[] values, Coding regular, int[] sizes) {
-        return choose(values, 0, values.length, regular, sizes);
-    }
-    CodingMethod choose(int[] values, Coding regular) {
-        return choose(values, 0, values.length, regular, null);
-    }
-
-    private int markUsableChoices(Coding regular) {
-        int numChoices = 0;
-        for (int i = 0; i < choices.length; i++) {
-            Choice c = choices[i];
-            c.reset();
-            if (!c.coding.canRepresent(min, max)) {
-                // Mark as already visited:
-                c.searchOrder = -1;
-                if (verbose > 1 && c.coding == regular) {
-                    Utils.log.info("regular coding cannot represent ["+min+".."+max+"]: "+regular);
-                }
-                continue;
-            }
-            if (c.coding == regular)
-                regularChoice = c;
-            numChoices++;
-        }
-        if (regularChoice == null && regular.canRepresent(min, max)) {
-            regularChoice = makeExtraChoice(regular);
-            if (verbose > 1) {
-                Utils.log.info("*** regular choice is extra: "+regularChoice.coding);
-            }
-        }
-        if (regularChoice == null) {
-            for (int i = 0; i < choices.length; i++) {
-                Choice c = choices[i];
-                if (c.searchOrder != -1) {
-                    regularChoice = c;  // arbitrary pick
-                    break;
-                }
-            }
-            if (verbose > 1) {
-                Utils.log.info("*** regular choice does not apply "+regular);
-                Utils.log.info("    using instead "+regularChoice.coding);
-            }
-        }
-        if (verbose > 2) {
-            Utils.log.info("chooser: #choices="+numChoices+" ["+min+".."+max+"]");
-            if (verbose > 4) {
-                for (int i = 0; i < choices.length; i++) {
-                    Choice c = choices[i];
-                    if (c.searchOrder >= 0)
-                        Utils.log.info("  "+c);
-                }
-            }
-        }
-        return numChoices;
-    }
-
-    // Find an arbitrary choice at least dlo away from a previously
-    // evaluated choices, and at most dhi.  Try also to regulate its
-    // min distance to all previously evaluated choices, in this range.
-    private Choice findChoiceNear(Choice near, int dhi, int dlo) {
-        if (verbose > 5)
-            Utils.log.info("findChoice "+dhi+".."+dlo+" near: "+near);
-        int[] distance = near.distance;
-        Choice found = null;
-        for (int i = 0; i < choices.length; i++) {
-            Choice c = choices[i];
-            if (c.searchOrder < searchOrder)
-                continue;  // already searched
-            // Distance from "near" guy must be in bounds:
-            if (distance[i] >= dlo && distance[i] <= dhi) {
-                // Try also to keep min-distance from other guys in bounds:
-                if (c.minDistance >= dlo && c.minDistance <= dhi) {
-                    if (verbose > 5)
-                        Utils.log.info("findChoice => good "+c);
-                    return c;
-                }
-                found = c;
-            }
-        }
-        if (verbose > 5)
-            Utils.log.info("findChoice => found "+found);
-        return found;
-    }
-
-    private void evaluate(Choice c) {
-        assert(c.searchOrder == Integer.MAX_VALUE);
-        c.searchOrder = searchOrder++;
-        boolean mustComputeSize;
-        if (c == bestChoice || c.isExtra()) {
-            mustComputeSize = true;
-        } else if (optUseHistogram) {
-            Histogram hist = getHistogram(c.coding.isDelta());
-            c.histSize = (int)Math.ceil(hist.getBitLength(c.coding) / 8);
-            c.byteSize = c.histSize;
-            mustComputeSize = (c.byteSize <= targetSize);
-        } else {
-            mustComputeSize = true;
-        }
-        if (mustComputeSize) {
-            int[] sizes = computeSizePrivate(c.coding);
-            c.byteSize = sizes[BYTE_SIZE];
-            c.zipSize  = sizes[ZIP_SIZE];
-            if (noteSizes(c.coding, c.byteSize, c.zipSize))
-                bestChoice = c;
-        }
-        if (c.histSize >= 0) {
-            assert(c.byteSize == c.histSize);  // models should agree
-        }
-        if (verbose > 4) {
-            Utils.log.info("evaluated "+c);
-        }
-    }
-
-    private boolean noteSizes(CodingMethod c, int byteSize, int zipSize) {
-        assert(zipSize > 0 && byteSize > 0);
-        boolean better = (zipSize < bestZipSize);
-        if (verbose > 3)
-            Utils.log.info("computed size "+c+" "+byteSize+"/zs="+zipSize+
-                             ((better && bestMethod != null)?
-                              (" better by "+
-                               pct(bestZipSize - zipSize, zipSize)): ""));
-        if (better) {
-            bestMethod = c;
-            bestZipSize = zipSize;
-            bestByteSize = byteSize;
-            targetSize = (int)(byteSize * fuzz);
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-
-    private int updateDistances(Choice c) {
-        // update all minDistance values in still unevaluated choices
-        int[] distance = c.distance;
-        int maxd = 0;  // how far is c from everybody else?
-        for (int i = 0; i < choices.length; i++) {
-            Choice c2 = choices[i];
-            if (c2.searchOrder < searchOrder)
-                continue;
-            int d = distance[i];
-            if (verbose > 5)
-                Utils.log.info("evaluate dist "+d+" to "+c2);
-            int mind = c2.minDistance;
-            if (mind > d)
-                c2.minDistance = mind = d;
-            if (maxd < d)
-                maxd = d;
-        }
-        // Now maxd has the distance of the farthest outlier
-        // from all evaluated choices.
-        if (verbose > 5)
-            Utils.log.info("evaluate maxd => "+maxd);
-        return maxd;
-    }
-
-    // Compute the coded size of a sequence of values.
-    // The first int is the size in uncompressed bytes.
-    // The second is an estimate of the compressed size of these bytes.
-    public void computeSize(CodingMethod c, int[] values, int start, int end, int[] sizes) {
-        if (end <= start) {
-            sizes[BYTE_SIZE] = sizes[ZIP_SIZE] = 0;
-            return;
-        }
-        try {
-            resetData();
-            c.writeArrayTo(byteSizer, values, start, end);
-            sizes[BYTE_SIZE] = getByteSize();
-            sizes[ZIP_SIZE] = getZipSize();
-        } catch (IOException ee) {
-            throw new RuntimeException(ee); // cannot happen
-        }
-    }
-    public void computeSize(CodingMethod c, int[] values, int[] sizes) {
-        computeSize(c, values, 0, values.length, sizes);
-    }
-    public int[] computeSize(CodingMethod c, int[] values, int start, int end) {
-        int[] sizes = { 0, 0 };
-        computeSize(c, values, start, end, sizes);
-        return sizes;
-    }
-    public int[] computeSize(CodingMethod c, int[] values) {
-        return computeSize(c, values, 0, values.length);
-    }
-    // This version uses the implicit local arguments
-    private int[] computeSizePrivate(CodingMethod c) {
-        int[] sizes = { 0, 0 };
-        computeSize(c, values, start, end, sizes);
-        return sizes;
-    }
-    public int computeByteSize(CodingMethod cm, int[] values, int start, int end) {
-        int len = end-start;
-        if (len < 0) {
-            return 0;
-        }
-        if (cm instanceof Coding) {
-            Coding c = (Coding) cm;
-            int size = c.getLength(values, start, end);
-            int size2;
-            assert(size == (size2=countBytesToSizer(cm, values, start, end)))
-                : (cm+" : "+size+" != "+size2);
-            return size;
-        }
-        return countBytesToSizer(cm, values, start, end);
-    }
-    private int countBytesToSizer(CodingMethod cm, int[] values, int start, int end) {
-        try {
-            byteOnlySizer.reset();
-            cm.writeArrayTo(byteOnlySizer, values, start, end);
-            return byteOnlySizer.getSize();
-        } catch (IOException ee) {
-            throw new RuntimeException(ee); // cannot happen
-        }
-    }
-
-    int[] getDeltas(int min, int max) {
-        if ((min|max) != 0)
-            return Coding.makeDeltas(values, start, end, min, max);
-        if (deltas == null) {
-            deltas = Coding.makeDeltas(values, start, end, 0, 0);
-        }
-        return deltas;
-    }
-    Histogram getValueHistogram() {
-        if (vHist == null) {
-            vHist = new Histogram(values, start, end);
-            if (verbose > 3) {
-                vHist.print("vHist", System.out);
-            } else if (verbose > 1) {
-                vHist.print("vHist", null, System.out);
-            }
-        }
-        return vHist;
-    }
-    Histogram getDeltaHistogram() {
-        if (dHist == null) {
-            dHist = new Histogram(getDeltas(0, 0));
-            if (verbose > 3) {
-                dHist.print("dHist", System.out);
-            } else if (verbose > 1) {
-                dHist.print("dHist", null, System.out);
-            }
-        }
-        return dHist;
-    }
-    Histogram getHistogram(boolean isDelta) {
-        return isDelta ? getDeltaHistogram(): getValueHistogram();
-    }
-
-    private void tryPopulationCoding(Coding plainCoding) {
-        // assert(plainCoding.canRepresent(min, max));
-        Histogram hist = getValueHistogram();
-        // Start with "reasonable" default codings.
-        final int approxL = 64;
-        Coding favoredCoding = plainCoding.getValueCoding();
-        Coding tokenCoding = BandStructure.UNSIGNED5.setL(approxL);
-        Coding unfavoredCoding = plainCoding.getValueCoding();
-        // There's going to be a band header.  Estimate conservatively large.
-        final int BAND_HEADER = 4;
-        // Keep a running model of the predicted sizes of the F/T/U sequences.
-        int currentFSize;
-        int currentTSize;
-        int currentUSize;
-        // Start by assuming a degenerate favored-value length of 0,
-        // which looks like a bunch of zero tokens followed by the
-        // original sequence.
-        // The {F} list ends with a repeated F value; find worst case:
-        currentFSize =
-            BAND_HEADER + Math.max(favoredCoding.getLength(min),
-                                   favoredCoding.getLength(max));
-        // The {T} list starts out a bunch of zeros, each of length 1.
-        final int ZERO_LEN = tokenCoding.getLength(0);
-        currentTSize = ZERO_LEN * (end-start);
-        // The {U} list starts out a copy of the plainCoding:
-        currentUSize = (int) Math.ceil(hist.getBitLength(unfavoredCoding) / 8);
-
-        int bestPopSize = (currentFSize + currentTSize + currentUSize);
-        int bestPopFVC  = 0;
-
-        // Record all the values, in decreasing order of favor.
-        int[] allFavoredValues = new int[1+hist.getTotalLength()];
-        //int[] allPopSizes    = new int[1+hist.getTotalLength()];
-
-        // What sizes are "interesting"?
-        int targetLowFVC = -1;
-        int targetHighFVC = -1;
-
-        // For each length, adjust the currentXSize model, and look for a win.
-        int[][] matrix = hist.getMatrix();
-        int mrow = -1;
-        int mcol = 1;
-        int mrowFreq = 0;
-        for (int fvcount = 1; fvcount <= hist.getTotalLength(); fvcount++) {
-            // The {F} list gets an additional member.
-            // Take it from the end of the current matrix row.
-            // (It's the end, so that we get larger favored values first.)
-            if (mcol == 1) {
-                mrow += 1;
-                mrowFreq = matrix[mrow][0];
-                mcol = matrix[mrow].length;
-            }
-            int thisValue = matrix[mrow][--mcol];
-            allFavoredValues[fvcount] = thisValue;
-            int thisVLen = favoredCoding.getLength(thisValue);
-            currentFSize += thisVLen;
-            // The token list replaces occurrences of zero with a new token:
-            int thisVCount = mrowFreq;
-            int thisToken = fvcount;
-            currentTSize += (tokenCoding.getLength(thisToken)
-                             - ZERO_LEN) * thisVCount;
-            // The unfavored list loses occurrences of the newly favored value.
-            // (This is the whole point of the exercise!)
-            currentUSize -= thisVLen * thisVCount;
-            int currentSize = (currentFSize + currentTSize + currentUSize);
-            //allPopSizes[fvcount] = currentSize;
-            if (bestPopSize > currentSize) {
-                if (currentSize <= targetSize) {
-                    targetHighFVC = fvcount;
-                    if (targetLowFVC < 0)
-                        targetLowFVC = fvcount;
-                    if (verbose > 4)
-                        Utils.log.info("better pop-size at fvc="+fvcount+
-                                         " by "+pct(bestPopSize-currentSize,
-                                                    bestPopSize));
-                }
-                bestPopSize = currentSize;
-                bestPopFVC = fvcount;
-            }
-        }
-        if (targetLowFVC < 0) {
-            if (verbose > 1) {
-                // Complete loss.
-                if (verbose > 1)
-                    Utils.log.info("no good pop-size; best was "+
-                                     bestPopSize+" at "+bestPopFVC+
-                                     " worse by "+
-                                     pct(bestPopSize-bestByteSize,
-                                         bestByteSize));
-            }
-            return;
-        }
-        if (verbose > 1)
-            Utils.log.info("initial best pop-size at fvc="+bestPopFVC+
-                             " in ["+targetLowFVC+".."+targetHighFVC+"]"+
-                             " by "+pct(bestByteSize-bestPopSize,
-                                        bestByteSize));
-        int oldZipSize = bestZipSize;
-        // Now close onto a specific coding, testing more rigorously
-        // with the zipSize metric.
-        // Questions to decide:
-        //   1. How many favored values?
-        //   2. What token coding (TC)?
-        //   3. Sort favored values by value within length brackets?
-        //   4. What favored coding?
-        //   5. What unfavored coding?
-        // Steps 1/2/3 are interdependent, and may be iterated.
-        // Steps 4 and 5 may be decided independently afterward.
-        int[] LValuesCoded = PopulationCoding.LValuesCoded;
-        List bestFits = new ArrayList<>();
-        List fullFits = new ArrayList<>();
-        List longFits = new ArrayList<>();
-        final int PACK_TO_MAX_S = 1;
-        if (bestPopFVC <= 255) {
-            bestFits.add(BandStructure.BYTE1);
-        } else {
-            int bestB = Coding.B_MAX;
-            boolean doFullAlso = (effort > POP_EFFORT);
-            if (doFullAlso)
-                fullFits.add(BandStructure.BYTE1.setS(PACK_TO_MAX_S));
-            for (int i = LValuesCoded.length-1; i >= 1; i--) {
-                int L = LValuesCoded[i];
-                Coding c0 = PopulationCoding.fitTokenCoding(targetLowFVC,  L);
-                Coding c1 = PopulationCoding.fitTokenCoding(bestPopFVC,    L);
-                Coding c3 = PopulationCoding.fitTokenCoding(targetHighFVC, L);
-                if (c1 != null) {
-                    if (!bestFits.contains(c1))
-                        bestFits.add(c1);
-                    if (bestB > c1.B())
-                        bestB = c1.B();
-                }
-                if (doFullAlso) {
-                    if (c3 == null)  c3 = c1;
-                    for (int B = c0.B(); B <= c3.B(); B++) {
-                        if (B == c1.B())  continue;
-                        if (B == 1)  continue;
-                        Coding c2 = c3.setB(B).setS(PACK_TO_MAX_S);
-                        if (!fullFits.contains(c2))
-                            fullFits.add(c2);
-                    }
-                }
-            }
-            // interleave all B greater than bestB with best and full fits
-            for (Iterator i = bestFits.iterator(); i.hasNext(); ) {
-                Coding c = i.next();
-                if (c.B() > bestB) {
-                    i.remove();
-                    longFits.add(0, c);
-                }
-            }
-        }
-        List allFits = new ArrayList<>();
-        for (Iterator i = bestFits.iterator(),
-                      j = fullFits.iterator(),
-                      k = longFits.iterator();
-             i.hasNext() || j.hasNext() || k.hasNext(); ) {
-            if (i.hasNext())  allFits.add(i.next());
-            if (j.hasNext())  allFits.add(j.next());
-            if (k.hasNext())  allFits.add(k.next());
-        }
-        bestFits.clear();
-        fullFits.clear();
-        longFits.clear();
-        int maxFits = allFits.size();
-        if (effort == POP_EFFORT)
-            maxFits = 2;
-        else if (maxFits > 4) {
-            maxFits -= 4;
-            maxFits = (maxFits * (effort-POP_EFFORT)
-                       ) / (MAX_EFFORT-POP_EFFORT);
-            maxFits += 4;
-        }
-        if (allFits.size() > maxFits) {
-            if (verbose > 4)
-                Utils.log.info("allFits before clip: "+allFits);
-            allFits.subList(maxFits, allFits.size()).clear();
-        }
-        if (verbose > 3)
-            Utils.log.info("allFits: "+allFits);
-        for (Coding tc : allFits) {
-            boolean packToMax = false;
-            if (tc.S() == PACK_TO_MAX_S) {
-                // Kludge:  setS(PACK_TO_MAX_S) means packToMax here.
-                packToMax = true;
-                tc = tc.setS(0);
-            }
-            int fVlen;
-            if (!packToMax) {
-                fVlen = bestPopFVC;
-                assert(tc.umax() >= fVlen);
-                assert(tc.B() == 1 || tc.setB(tc.B()-1).umax() < fVlen);
-            } else {
-                fVlen = Math.min(tc.umax(), targetHighFVC);
-                if (fVlen < targetLowFVC)
-                    continue;
-                if (fVlen == bestPopFVC)
-                    continue;  // redundant test
-            }
-            PopulationCoding pop = new PopulationCoding();
-            pop.setHistogram(hist);
-            pop.setL(tc.L());
-            pop.setFavoredValues(allFavoredValues, fVlen);
-            assert(pop.tokenCoding == tc);  // predict correctly
-            pop.resortFavoredValues();
-            int[] tcsizes =
-                computePopSizePrivate(pop,
-                                      favoredCoding, unfavoredCoding);
-            noteSizes(pop, tcsizes[BYTE_SIZE], BAND_HEADER+tcsizes[ZIP_SIZE]);
-        }
-        if (verbose > 3) {
-            Utils.log.info("measured best pop, size="+bestByteSize+
-                             "/zs="+bestZipSize+
-                             " better by "+
-                             pct(oldZipSize-bestZipSize, oldZipSize));
-            if (bestZipSize < oldZipSize) {
-                Utils.log.info(">>> POP WINS BY "+
-                                 (oldZipSize - bestZipSize));
-            }
-        }
-    }
-
-    private
-    int[] computePopSizePrivate(PopulationCoding pop,
-                                Coding favoredCoding,
-                                Coding unfavoredCoding) {
-        if (popHelper == null) {
-            popHelper = new CodingChooser(effort, allCodingChoices);
-            if (stress != null)
-                popHelper.addStressSeed(stress.nextInt());
-            popHelper.topLevel = false;
-            popHelper.verbose -= 1;
-            popHelper.disablePopCoding = true;
-            popHelper.disableRunCoding = this.disableRunCoding;
-            if (effort < MID_EFFORT)
-                // No nested run codings.
-                popHelper.disableRunCoding = true;
-        }
-        int fVlen = pop.fVlen;
-        if (verbose > 2) {
-            Utils.log.info("computePopSizePrivate fvlen="+fVlen+
-                             " tc="+pop.tokenCoding);
-            Utils.log.info("{ //BEGIN");
-        }
-
-        // Find good coding choices for the token and unfavored sequences.
-        int[] favoredValues = pop.fValues;
-        int[][] vals = pop.encodeValues(values, start, end);
-        int[] tokens = vals[0];
-        int[] unfavoredValues = vals[1];
-        if (verbose > 2)
-            Utils.log.info("-- refine on fv["+fVlen+"] fc="+favoredCoding);
-        pop.setFavoredCoding(popHelper.choose(favoredValues, 1, 1+fVlen, favoredCoding));
-        if (pop.tokenCoding instanceof Coding &&
-            (stress == null || stress.nextBoolean())) {
-            if (verbose > 2)
-                Utils.log.info("-- refine on tv["+tokens.length+"] tc="+pop.tokenCoding);
-            CodingMethod tc = popHelper.choose(tokens, (Coding) pop.tokenCoding);
-            if (tc != pop.tokenCoding) {
-                if (verbose > 2)
-                    Utils.log.info(">>> refined tc="+tc);
-                pop.setTokenCoding(tc);
-            }
-        }
-        if (unfavoredValues.length == 0)
-            pop.setUnfavoredCoding(null);
-        else {
-            if (verbose > 2)
-                Utils.log.info("-- refine on uv["+unfavoredValues.length+"] uc="+pop.unfavoredCoding);
-            pop.setUnfavoredCoding(popHelper.choose(unfavoredValues, unfavoredCoding));
-        }
-        if (verbose > 3) {
-            Utils.log.info("finish computePopSizePrivate fvlen="+fVlen+
-                             " fc="+pop.favoredCoding+
-                             " tc="+pop.tokenCoding+
-                             " uc="+pop.unfavoredCoding);
-            //pop.hist.print("pop-hist", null, System.out);
-            StringBuilder sb = new StringBuilder();
-            sb.append("fv = {");
-            for (int i = 1; i <= fVlen; i++) {
-                if ((i % 10) == 0)
-                    sb.append('\n');
-                sb.append(" ").append(favoredValues[i]);
-            }
-            sb.append('\n');
-            sb.append("}");
-            Utils.log.info(sb.toString());
-        }
-        if (verbose > 2) {
-            Utils.log.info("} //END");
-        }
-        if (stress != null) {
-            return null;  // do not bother with size computation
-        }
-        int[] sizes;
-        try {
-            resetData();
-            // Write the array of favored values.
-            pop.writeSequencesTo(byteSizer, tokens, unfavoredValues);
-            sizes = new int[] { getByteSize(), getZipSize() };
-        } catch (IOException ee) {
-            throw new RuntimeException(ee); // cannot happen
-        }
-        int[] checkSizes = null;
-        assert((checkSizes = computeSizePrivate(pop)) != null);
-        assert(checkSizes[BYTE_SIZE] == sizes[BYTE_SIZE])
-            : (checkSizes[BYTE_SIZE]+" != "+sizes[BYTE_SIZE]);
-        return sizes;
-    }
-
-    private void tryAdaptiveCoding(Coding plainCoding) {
-        int oldZipSize = bestZipSize;
-        // Scan the value sequence, determining whether an interesting
-        // run occupies too much space.  ("Too much" means, say 5% more
-        // than the average integer size of the band as a whole.)
-        // Try to find a better coding for those segments.
-        int   lstart  = this.start;
-        int   lend    = this.end;
-        int[] lvalues = this.values;
-        int len = lend-lstart;
-        if (plainCoding.isDelta()) {
-            lvalues = getDeltas(0,0); //%%% not quite right!
-            lstart = 0;
-            lend = lvalues.length;
-        }
-        int[] sizes = new int[len+1];
-        int fillp = 0;
-        int totalSize = 0;
-        for (int i = lstart; i < lend; i++) {
-            int val = lvalues[i];
-            sizes[fillp++] = totalSize;
-            int size = plainCoding.getLength(val);
-            assert(size < Integer.MAX_VALUE);
-            //System.out.println("len "+val+" = "+size);
-            totalSize += size;
-        }
-        sizes[fillp++] = totalSize;
-        assert(fillp == sizes.length);
-        double avgSize = (double)totalSize / len;
-        double sizeFuzz;
-        double sizeFuzz2;
-        double sizeFuzz3;
-        if (effort >= MID_EFFORT) {
-            if (effort > MID_EFFORT+1)
-                sizeFuzz = 1.001;
-            else
-                sizeFuzz = 1.003;
-        } else {
-            if (effort > RUN_EFFORT)
-                sizeFuzz = 1.01;
-            else
-                sizeFuzz = 1.03;
-        }
-        // for now:
-        sizeFuzz *= sizeFuzz; // double the thresh
-        sizeFuzz2 = (sizeFuzz*sizeFuzz);
-        sizeFuzz3 = (sizeFuzz*sizeFuzz*sizeFuzz);
-        // Find some mesh scales we like.
-        double[] dmeshes = new double[1 + (effort-RUN_EFFORT)];
-        double logLen = Math.log(len);
-        for (int i = 0; i < dmeshes.length; i++) {
-            dmeshes[i] = Math.exp(logLen*(i+1)/(dmeshes.length+1));
-        }
-        int[] meshes = new int[dmeshes.length];
-        int mfillp = 0;
-        for (int i = 0; i < dmeshes.length; i++) {
-            int m = (int)Math.round(dmeshes[i]);
-            m = AdaptiveCoding.getNextK(m-1);
-            if (m <= 0 || m >= len)  continue;
-            if (mfillp > 0 && m == meshes[mfillp-1])  continue;
-            meshes[mfillp++] = m;
-        }
-        meshes = BandStructure.realloc(meshes, mfillp);
-        // There's going to be a band header.  Estimate conservatively large.
-        final int BAND_HEADER = 4; // op, KB, A, B
-        // Threshold values for a "too big" mesh.
-        int[]    threshes = new int[meshes.length];
-        double[] fuzzes   = new double[meshes.length];
-        for (int i = 0; i < meshes.length; i++) {
-            int mesh = meshes[i];
-            double lfuzz;
-            if (mesh < 10)
-                lfuzz = sizeFuzz3;
-            else if (mesh < 100)
-                lfuzz = sizeFuzz2;
-            else
-                lfuzz = sizeFuzz;
-            fuzzes[i] = lfuzz;
-            threshes[i] = BAND_HEADER + (int)Math.ceil(mesh * avgSize * lfuzz);
-        }
-        if (verbose > 1) {
-            System.out.print("tryAdaptiveCoding ["+len+"]"+
-                             " avgS="+avgSize+" fuzz="+sizeFuzz+
-                             " meshes: {");
-            for (int i = 0; i < meshes.length; i++) {
-                System.out.print(" " + meshes[i] + "(" + threshes[i] + ")");
-            }
-            Utils.log.info(" }");
-        }
-        if (runHelper == null) {
-            runHelper = new CodingChooser(effort, allCodingChoices);
-            if (stress != null)
-                runHelper.addStressSeed(stress.nextInt());
-            runHelper.topLevel = false;
-            runHelper.verbose -= 1;
-            runHelper.disableRunCoding = true;
-            runHelper.disablePopCoding = this.disablePopCoding;
-            if (effort < MID_EFFORT)
-                // No nested pop codings.
-                runHelper.disablePopCoding = true;
-        }
-        for (int i = 0; i < len; i++) {
-            i = AdaptiveCoding.getNextK(i-1);
-            if (i > len)  i = len;
-            for (int j = meshes.length-1; j >= 0; j--) {
-                int mesh   = meshes[j];
-                int thresh = threshes[j];
-                if (i+mesh > len)  continue;
-                int size = sizes[i+mesh] - sizes[i];
-                if (size >= thresh) {
-                    // Found a size bulge.
-                    int bend  = i+mesh;
-                    int bsize = size;
-                    double bigSize = avgSize * fuzzes[j];
-                    while (bend < len && (bend-i) <= len/2) {
-                        int bend0 = bend;
-                        int bsize0 = bsize;
-                        bend += mesh;
-                        bend = i+AdaptiveCoding.getNextK(bend-i-1);
-                        if (bend < 0 || bend > len)
-                            bend = len;
-                        bsize = sizes[bend]-sizes[i];
-                        if (bsize < BAND_HEADER + (bend-i) * bigSize) {
-                            bsize = bsize0;
-                            bend = bend0;
-                            break;
-                        }
-                    }
-                    int nexti = bend;
-                    if (verbose > 2) {
-                        Utils.log.info("bulge at "+i+"["+(bend-i)+"] of "+
-                                         pct(bsize - avgSize*(bend-i),
-                                             avgSize*(bend-i)));
-                        Utils.log.info("{ //BEGIN");
-                    }
-                    CodingMethod begcm, midcm, endcm;
-                    midcm = runHelper.choose(this.values,
-                                             this.start+i,
-                                             this.start+bend,
-                                             plainCoding);
-                    if (midcm == plainCoding) {
-                        // No use working further.
-                        begcm = plainCoding;
-                        endcm = plainCoding;
-                    } else {
-                        begcm = runHelper.choose(this.values,
-                                                 this.start,
-                                                 this.start+i,
-                                                 plainCoding);
-                        endcm = runHelper.choose(this.values,
-                                                 this.start+bend,
-                                                 this.start+len,
-                                                 plainCoding);
-                    }
-                    if (verbose > 2)
-                        Utils.log.info("} //END");
-                    if (begcm == midcm && i > 0 &&
-                        AdaptiveCoding.isCodableLength(bend)) {
-                        i = 0;
-                    }
-                    if (midcm == endcm && bend < len) {
-                        bend = len;
-                    }
-                    if (begcm != plainCoding ||
-                        midcm != plainCoding ||
-                        endcm != plainCoding) {
-                        CodingMethod chain;
-                        int hlen = 0;
-                        if (bend == len) {
-                            chain = midcm;
-                        } else {
-                            chain = new AdaptiveCoding(bend-i, midcm, endcm);
-                            hlen += BAND_HEADER;
-                        }
-                        if (i > 0) {
-                            chain = new AdaptiveCoding(i, begcm, chain);
-                            hlen += BAND_HEADER;
-                        }
-                        int[] chainSize = computeSizePrivate(chain);
-                        noteSizes(chain,
-                                  chainSize[BYTE_SIZE],
-                                  chainSize[ZIP_SIZE]+hlen);
-                    }
-                    i = nexti;
-                    break;
-                }
-            }
-        }
-        if (verbose > 3) {
-            if (bestZipSize < oldZipSize) {
-                Utils.log.info(">>> RUN WINS BY "+
-                                 (oldZipSize - bestZipSize));
-            }
-        }
-    }
-
-    private static
-    String pct(double num, double den) {
-        return (Math.round((num / den)*10000)/100.0)+"%";
-    }
-
-    static
-    class Sizer extends OutputStream {
-        final OutputStream out;  // if non-null, copy output here also
-        Sizer(OutputStream out) {
-            this.out = out;
-        }
-        Sizer() {
-            this(null);
-        }
-        private int count;
-        public void write(int b) throws IOException {
-            count++;
-            if (out != null)  out.write(b);
-        }
-        public void write(byte b[], int off, int len) throws IOException {
-            count += len;
-            if (out != null)  out.write(b, off, len);
-        }
-        public void reset() {
-            count = 0;
-        }
-        public int getSize() { return count; }
-
-        public String toString() {
-            String str = super.toString();
-            // If -ea, print out more informative strings!
-            assert((str = stringForDebug()) != null);
-            return str;
-        }
-        String stringForDebug() {
-            return "";
-        }
-    }
-
-    private Sizer zipSizer  = new Sizer();
-    private Deflater zipDef = new Deflater();
-    private DeflaterOutputStream zipOut = new DeflaterOutputStream(zipSizer, zipDef);
-    private Sizer byteSizer = new Sizer(zipOut);
-    private Sizer byteOnlySizer = new Sizer();
-
-    private void resetData() {
-        flushData();
-        zipDef.reset();
-        if (context != null) {
-            // Prepend given salt to the test output.
-            try {
-                context.writeTo(byteSizer);
-            } catch (IOException ee) {
-                throw new RuntimeException(ee); // cannot happen
-            }
-        }
-        zipSizer.reset();
-        byteSizer.reset();
-    }
-    private void flushData() {
-        try {
-            zipOut.finish();
-        } catch (IOException ee) {
-            throw new RuntimeException(ee); // cannot happen
-        }
-    }
-    private int getByteSize() {
-        return byteSizer.getSize();
-    }
-    private int getZipSize() {
-        flushData();
-        return zipSizer.getSize();
-    }
-
-
-    /// Stress-test helpers.
-
-    void addStressSeed(int x) {
-        if (stress == null)  return;
-        stress.setSeed(x + ((long)stress.nextInt() << 32));
-    }
-
-    // Pick a random pop-coding.
-    private CodingMethod stressPopCoding(CodingMethod coding) {
-        assert(stress != null);  // this method is only for testing
-        // Don't turn it into a pop coding if it's already something special.
-        if (!(coding instanceof Coding))  return coding;
-        Coding valueCoding = ((Coding)coding).getValueCoding();
-        Histogram hist = getValueHistogram();
-        int fVlen = stressLen(hist.getTotalLength());
-        if (fVlen == 0)  return coding;
-        List popvals = new ArrayList<>();
-        if (stress.nextBoolean()) {
-            // Build the population from the value list.
-            Set popset = new HashSet<>();
-            for (int i = start; i < end; i++) {
-                if (popset.add(values[i]))  popvals.add(values[i]);
-            }
-        } else {
-            int[][] matrix = hist.getMatrix();
-            for (int mrow = 0; mrow < matrix.length; mrow++) {
-                int[] row = matrix[mrow];
-                for (int mcol = 1; mcol < row.length; mcol++) {
-                    popvals.add(row[mcol]);
-                }
-            }
-        }
-        int reorder = stress.nextInt();
-        if ((reorder & 7) <= 2) {
-            // Lose the order.
-            Collections.shuffle(popvals, stress);
-        } else {
-            // Keep the order, mostly.
-            if (((reorder >>>= 3) & 7) <= 2)  Collections.sort(popvals);
-            if (((reorder >>>= 3) & 7) <= 2)  Collections.reverse(popvals);
-            if (((reorder >>>= 3) & 7) <= 2)  Collections.rotate(popvals, stressLen(popvals.size()));
-        }
-        if (popvals.size() > fVlen) {
-            // Cut the list down.
-            if (((reorder >>>= 3) & 7) <= 2) {
-                // Cut at end.
-                popvals.subList(fVlen,   popvals.size()).clear();
-            } else {
-                // Cut at start.
-                popvals.subList(0, popvals.size()-fVlen).clear();
-            }
-        }
-        fVlen = popvals.size();
-        int[] fvals = new int[1+fVlen];
-        for (int i = 0; i < fVlen; i++) {
-            fvals[1+i] = (popvals.get(i)).intValue();
-        }
-        PopulationCoding pop = new PopulationCoding();
-        pop.setFavoredValues(fvals, fVlen);
-        int[] lvals = PopulationCoding.LValuesCoded;
-        for (int i = 0; i < lvals.length / 2; i++) {
-            int popl = lvals[stress.nextInt(lvals.length)];
-            if (popl < 0)  continue;
-            if (PopulationCoding.fitTokenCoding(fVlen, popl) != null) {
-                pop.setL(popl);
-                break;
-            }
-        }
-        if (pop.tokenCoding == null) {
-            int lmin = fvals[1], lmax = lmin;
-            for (int i = 2; i <= fVlen; i++) {
-                int val = fvals[i];
-                if (lmin > val)  lmin = val;
-                if (lmax < val)  lmax = val;
-            }
-            pop.tokenCoding = stressCoding(lmin, lmax);
-        }
-
-        computePopSizePrivate(pop, valueCoding, valueCoding);
-        return pop;
-    }
-
-    // Pick a random adaptive coding.
-    private CodingMethod stressAdaptiveCoding(CodingMethod coding) {
-        assert(stress != null);  // this method is only for testing
-        // Don't turn it into a run coding if it's already something special.
-        if (!(coding instanceof Coding))  return coding;
-        Coding plainCoding = (Coding)coding;
-        int len = end-start;
-        if (len < 2)  return coding;
-        // Decide how many spans we'll create.
-        int spanlen = stressLen(len-1)+1;
-        if (spanlen == len)  return coding;
-        try {
-            assert(!disableRunCoding);
-            disableRunCoding = true;  // temporary, while I decide spans
-            int[] allValues = values.clone();
-            CodingMethod result = null;
-            int scan  = this.end;
-            int lstart = this.start;
-            for (int split; scan > lstart; scan = split) {
-                int thisspan;
-                int rand = (scan - lstart < 100)? -1: stress.nextInt();
-                if ((rand & 7) != 0) {
-                    thisspan = (spanlen==1? spanlen: stressLen(spanlen-1)+1);
-                } else {
-                    // Every so often generate a value based on KX/KB format.
-                    int KX = (rand >>>= 3) & AdaptiveCoding.KX_MAX;
-                    int KB = (rand >>>= 3) & AdaptiveCoding.KB_MAX;
-                    for (;;) {
-                        thisspan = AdaptiveCoding.decodeK(KX, KB);
-                        if (thisspan <= scan - lstart)  break;
-                        // Try smaller and smaller codings:
-                        if (KB != AdaptiveCoding.KB_DEFAULT)
-                            KB = AdaptiveCoding.KB_DEFAULT;
-                        else
-                            KX -= 1;
-                    }
-                    //System.out.println("KX="+KX+" KB="+KB+" K="+thisspan);
-                    assert(AdaptiveCoding.isCodableLength(thisspan));
-                }
-                if (thisspan > scan - lstart)  thisspan = scan - lstart;
-                while (!AdaptiveCoding.isCodableLength(thisspan)) {
-                    --thisspan;
-                }
-                split = scan - thisspan;
-                assert(split < scan);
-                assert(split >= lstart);
-                // Choose a coding for the span [split..scan).
-                CodingMethod sc = choose(allValues, split, scan, plainCoding);
-                if (result == null) {
-                    result = sc;  // the caboose
-                } else {
-                    result = new AdaptiveCoding(scan-split, sc, result);
-                }
-            }
-            return result;
-        } finally {
-            disableRunCoding = false; // return to normal value
-        }
-    }
-
-    // Return a random value in [0..len], gently biased toward extremes.
-    private Coding stressCoding(int min, int max) {
-        assert(stress != null);  // this method is only for testing
-        for (int i = 0; i < 100; i++) {
-            Coding c = Coding.of(stress.nextInt(Coding.B_MAX)+1,
-                                 stress.nextInt(Coding.H_MAX)+1,
-                                 stress.nextInt(Coding.S_MAX+1));
-            if (c.B() == 1)  c = c.setH(256);
-            if (c.H() == 256 && c.B() >= 5)  c = c.setB(4);
-            if (stress.nextBoolean()) {
-                Coding dc = c.setD(1);
-                if (dc.canRepresent(min, max))  return dc;
-            }
-            if (c.canRepresent(min, max))  return c;
-        }
-        return BandStructure.UNSIGNED5;
-    }
-
-    // Return a random value in [0..len], gently biased toward extremes.
-    private int stressLen(int len) {
-        assert(stress != null);  // this method is only for testing
-        assert(len >= 0);
-        int rand = stress.nextInt(100);
-        if (rand < 20)
-            return Math.min(len/5, rand);
-        else if (rand < 40)
-            return len;
-        else
-            return stress.nextInt(len);
-    }
-
-    // For debug only.
-/*
-    public static
-    int[] readValuesFrom(InputStream instr) {
-        return readValuesFrom(new InputStreamReader(instr));
-    }
-    public static
-    int[] readValuesFrom(Reader inrdr) {
-        inrdr = new BufferedReader(inrdr);
-        final StreamTokenizer in = new StreamTokenizer(inrdr);
-        final int TT_NOTHING = -99;
-        in.commentChar('#');
-        return readValuesFrom(new Iterator() {
-            int token = TT_NOTHING;
-            private int getToken() {
-                if (token == TT_NOTHING) {
-                    try {
-                        token = in.nextToken();
-                        assert(token != TT_NOTHING);
-                    } catch (IOException ee) {
-                        throw new RuntimeException(ee);
-                    }
-                }
-                return token;
-            }
-            public boolean hasNext() {
-                return getToken() != StreamTokenizer.TT_EOF;
-            }
-            public Object next() {
-                int ntok = getToken();
-                token = TT_NOTHING;
-                switch (ntok) {
-                case StreamTokenizer.TT_EOF:
-                    throw new NoSuchElementException();
-                case StreamTokenizer.TT_NUMBER:
-                    return Integer.valueOf((int) in.nval);
-                default:
-                    assert(false);
-                    return null;
-                }
-            }
-            public void remove() {
-                throw new UnsupportedOperationException();
-            }
-        });
-    }
-    public static
-    int[] readValuesFrom(Iterator iter) {
-        return readValuesFrom(iter, 0);
-    }
-    public static
-    int[] readValuesFrom(Iterator iter, int initSize) {
-        int[] na = new int[Math.max(10, initSize)];
-        int np = 0;
-        while (iter.hasNext()) {
-            Integer val = (Integer) iter.next();
-            if (np == na.length) {
-                na = BandStructure.realloc(na);
-            }
-            na[np++] = val.intValue();
-        }
-        if (np != na.length) {
-            na = BandStructure.realloc(na, np);
-        }
-        return na;
-    }
-
-    public static
-    void main(String[] av) throws IOException {
-        int effort = MID_EFFORT;
-        int ap = 0;
-        if (ap < av.length && av[ap].equals("-e")) {
-            ap++;
-            effort = Integer.parseInt(av[ap++]);
-        }
-        int verbose = 1;
-        if (ap < av.length && av[ap].equals("-v")) {
-            ap++;
-            verbose = Integer.parseInt(av[ap++]);
-        }
-        Coding[] bcs = BandStructure.getBasicCodings();
-        CodingChooser cc = new CodingChooser(effort, bcs);
-        if (ap < av.length && av[ap].equals("-p")) {
-            ap++;
-            cc.optUsePopulationCoding = false;
-        }
-        if (ap < av.length && av[ap].equals("-a")) {
-            ap++;
-            cc.optUseAdaptiveCoding = false;
-        }
-        cc.verbose = verbose;
-        int[] values = readValuesFrom(System.in);
-        int[] sizes = {0,0};
-        CodingMethod cm = cc.choose(values, BandStructure.UNSIGNED5, sizes);
-        System.out.println("size: "+sizes[BYTE_SIZE]+"/zs="+sizes[ZIP_SIZE]);
-        System.out.println(cm);
-    }
-//*/
-
-}
diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/CodingMethod.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/CodingMethod.java
deleted file mode 100644
index 884885a97..000000000
--- a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/CodingMethod.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package net.fabricmc.shade.com.sun.java.util.jar.pack;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-/**
- * Interface for encoding and decoding int arrays using bytewise codes.
- * @author John Rose
- */
-interface CodingMethod {
-    // Read and write an array of ints from/to a stream.
-    public void readArrayFrom(InputStream in, int[] a, int start, int end) throws IOException;
-    public void writeArrayTo(OutputStream out, int[] a, int start, int end) throws IOException;
-
-    // how to express me in a band header?
-    public byte[] getMetaCoding(Coding dflt);
-}
diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/ConstantPool.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/ConstantPool.java
deleted file mode 100644
index 4d44494ed..000000000
--- a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/ConstantPool.java
+++ /dev/null
@@ -1,1657 +0,0 @@
-/*
- * Copyright (c) 2001, 2013, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package net.fabricmc.shade.com.sun.java.util.jar.pack;
-
-import java.util.AbstractList;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * Representation of constant pool entries and indexes.
- * @author John Rose
- */
-abstract
-class ConstantPool {
-    private ConstantPool() {}  // do not instantiate
-
-    static int verbose() {
-        return Utils.currentPropMap().getInteger(Utils.DEBUG_VERBOSE);
-    }
-
-    /** Factory for Utf8 string constants.
-     *  Used for well-known strings like "SourceFile", "", etc.
-     *  Also used to back up more complex constant pool entries, like Class.
-     */
-    public static synchronized Utf8Entry getUtf8Entry(String value) {
-        Map utf8Entries  = Utils.getTLGlobals().getUtf8Entries();
-        Utf8Entry e = utf8Entries.get(value);
-        if (e == null) {
-            e = new Utf8Entry(value);
-            utf8Entries.put(e.stringValue(), e);
-        }
-        return e;
-    }
-    /** Factory for Class constants. */
-    public static ClassEntry getClassEntry(String name) {
-        Map classEntries = Utils.getTLGlobals().getClassEntries();
-        ClassEntry e = classEntries.get(name);
-        if (e == null) {
-            e = new ClassEntry(getUtf8Entry(name));
-            assert(name.equals(e.stringValue()));
-            classEntries.put(e.stringValue(), e);
-        }
-        return e;
-    }
-    /** Factory for literal constants (String, Integer, etc.). */
-    public static LiteralEntry getLiteralEntry(Comparable value) {
-        Map literalEntries = Utils.getTLGlobals().getLiteralEntries();
-        LiteralEntry e = literalEntries.get(value);
-        if (e == null) {
-            if (value instanceof String)
-                e = new StringEntry(getUtf8Entry((String)value));
-            else
-                e = new NumberEntry((Number)value);
-            literalEntries.put(value, e);
-        }
-        return e;
-    }
-    /** Factory for literal constants (String, Integer, etc.). */
-    public static StringEntry getStringEntry(String value) {
-        return (StringEntry) getLiteralEntry(value);
-    }
-
-    /** Factory for signature (type) constants. */
-    public static SignatureEntry getSignatureEntry(String type) {
-        Map signatureEntries = Utils.getTLGlobals().getSignatureEntries();
-        SignatureEntry e = signatureEntries.get(type);
-        if (e == null) {
-            e = new SignatureEntry(type);
-            assert(e.stringValue().equals(type));
-            signatureEntries.put(type, e);
-        }
-        return e;
-    }
-    // Convenience overloading.
-    public static SignatureEntry getSignatureEntry(Utf8Entry formRef, ClassEntry[] classRefs) {
-        return getSignatureEntry(SignatureEntry.stringValueOf(formRef, classRefs));
-    }
-
-    /** Factory for descriptor (name-and-type) constants. */
-    public static DescriptorEntry getDescriptorEntry(Utf8Entry nameRef, SignatureEntry typeRef) {
-        Map descriptorEntries = Utils.getTLGlobals().getDescriptorEntries();
-        String key = DescriptorEntry.stringValueOf(nameRef, typeRef);
-        DescriptorEntry e = descriptorEntries.get(key);
-        if (e == null) {
-            e = new DescriptorEntry(nameRef, typeRef);
-            assert(e.stringValue().equals(key))
-                : (e.stringValue()+" != "+(key));
-            descriptorEntries.put(key, e);
-        }
-        return e;
-    }
-    // Convenience overloading.
-    public static DescriptorEntry getDescriptorEntry(Utf8Entry nameRef, Utf8Entry typeRef) {
-        return getDescriptorEntry(nameRef, getSignatureEntry(typeRef.stringValue()));
-    }
-
-    /** Factory for member reference constants. */
-    public static MemberEntry getMemberEntry(byte tag, ClassEntry classRef, DescriptorEntry descRef) {
-        Map memberEntries = Utils.getTLGlobals().getMemberEntries();
-        String key = MemberEntry.stringValueOf(tag, classRef, descRef);
-        MemberEntry e = memberEntries.get(key);
-        if (e == null) {
-            e = new MemberEntry(tag, classRef, descRef);
-            assert(e.stringValue().equals(key))
-                : (e.stringValue()+" != "+(key));
-            memberEntries.put(key, e);
-        }
-        return e;
-    }
-
-    /** Factory for MethodHandle constants. */
-    public static MethodHandleEntry getMethodHandleEntry(byte refKind, MemberEntry memRef) {
-        Map methodHandleEntries = Utils.getTLGlobals().getMethodHandleEntries();
-        String key = MethodHandleEntry.stringValueOf(refKind, memRef);
-        MethodHandleEntry e = methodHandleEntries.get(key);
-        if (e == null) {
-            e = new MethodHandleEntry(refKind, memRef);
-            assert(e.stringValue().equals(key));
-            methodHandleEntries.put(key, e);
-        }
-        return e;
-    }
-
-    /** Factory for MethodType constants. */
-    public static MethodTypeEntry getMethodTypeEntry(SignatureEntry sigRef) {
-        Map methodTypeEntries = Utils.getTLGlobals().getMethodTypeEntries();
-        String key = sigRef.stringValue();
-        MethodTypeEntry e = methodTypeEntries.get(key);
-        if (e == null) {
-            e = new MethodTypeEntry(sigRef);
-            assert(e.stringValue().equals(key));
-            methodTypeEntries.put(key, e);
-        }
-        return e;
-    }
-    public static MethodTypeEntry getMethodTypeEntry(Utf8Entry typeRef) {
-        return getMethodTypeEntry(getSignatureEntry(typeRef.stringValue()));
-    }
-
-    /** Factory for InvokeDynamic constants. */
-    public static InvokeDynamicEntry getInvokeDynamicEntry(BootstrapMethodEntry bssRef, DescriptorEntry descRef) {
-        Map invokeDynamicEntries = Utils.getTLGlobals().getInvokeDynamicEntries();
-        String key = InvokeDynamicEntry.stringValueOf(bssRef, descRef);
-        InvokeDynamicEntry e = invokeDynamicEntries.get(key);
-        if (e == null) {
-            e = new InvokeDynamicEntry(bssRef, descRef);
-            assert(e.stringValue().equals(key));
-            invokeDynamicEntries.put(key, e);
-        }
-        return e;
-    }
-
-    /** Factory for BootstrapMethod pseudo-constants. */
-    public static BootstrapMethodEntry getBootstrapMethodEntry(MethodHandleEntry bsmRef, Entry[] argRefs) {
-        Map bootstrapMethodEntries = Utils.getTLGlobals().getBootstrapMethodEntries();
-        String key = BootstrapMethodEntry.stringValueOf(bsmRef, argRefs);
-        BootstrapMethodEntry e = bootstrapMethodEntries.get(key);
-        if (e == null) {
-            e = new BootstrapMethodEntry(bsmRef, argRefs);
-            assert(e.stringValue().equals(key));
-            bootstrapMethodEntries.put(key, e);
-        }
-        return e;
-    }
-
-
-    /** Entries in the constant pool. */
-    public abstract static
-    class Entry implements Comparable {
-        protected final byte tag;       // a CONSTANT_foo code
-        protected int valueHash;        // cached hashCode
-
-        protected Entry(byte tag) {
-            this.tag = tag;
-        }
-
-        public final byte getTag() {
-            return tag;
-        }
-
-        public final boolean tagEquals(int tag) {
-            return getTag() == tag;
-        }
-
-        public Entry getRef(int i) {
-            return null;
-        }
-
-        public boolean eq(Entry that) {  // same reference
-            assert(that != null);
-            return this == that || this.equals(that);
-        }
-
-        // Equality of Entries is value-based.
-        public abstract boolean equals(Object o);
-        public final int hashCode() {
-            if (valueHash == 0) {
-                valueHash = computeValueHash();
-                if (valueHash == 0)  valueHash = 1;
-            }
-            return valueHash;
-        }
-        protected abstract int computeValueHash();
-
-        public abstract int compareTo(Object o);
-
-        protected int superCompareTo(Object o) {
-            Entry that = (Entry) o;
-
-            if (this.tag != that.tag) {
-                return TAG_ORDER[this.tag] - TAG_ORDER[that.tag];
-            }
-
-            return 0;  // subclasses must refine this
-        }
-
-        public final boolean isDoubleWord() {
-            return tag == Constants.CONSTANT_Double || tag == Constants.CONSTANT_Long;
-        }
-
-        public final boolean tagMatches(int matchTag) {
-            if (tag == matchTag)
-                return true;
-            byte[] allowedTags;
-            switch (matchTag) {
-                case Constants.CONSTANT_All:
-                    return true;
-                case Constants.CONSTANT_Signature:
-                    return tag == Constants.CONSTANT_Utf8;  // format check also?
-                case Constants.CONSTANT_LoadableValue:
-                    allowedTags = LOADABLE_VALUE_TAGS;
-                    break;
-                case Constants.CONSTANT_AnyMember:
-                    allowedTags = ANY_MEMBER_TAGS;
-                    break;
-                case Constants.CONSTANT_FieldSpecific:
-                    allowedTags = FIELD_SPECIFIC_TAGS;
-                    break;
-                default:
-                    return false;
-            }
-            for (byte b : allowedTags) {
-                if (b == tag)
-                    return true;
-            }
-            return false;
-        }
-
-        public String toString() {
-            String valuePrint = stringValue();
-            if (verbose() > 4) {
-                if (valueHash != 0)
-                    valuePrint += " hash="+valueHash;
-                valuePrint += " id="+System.identityHashCode(this);
-            }
-            return tagName(tag)+"="+valuePrint;
-        }
-        public abstract String stringValue();
-    }
-
-    public static
-    class Utf8Entry extends Entry {
-        final String value;
-
-        Utf8Entry(String value) {
-            super(Constants.CONSTANT_Utf8);
-            this.value = value.intern();
-            hashCode();  // force computation of valueHash
-        }
-        protected int computeValueHash() {
-            return value.hashCode();
-        }
-        public boolean equals(Object o) {
-            // Use reference equality of interned strings:
-            return (o != null && o.getClass() == Utf8Entry.class
-                    && ((Utf8Entry) o).value.equals(value));
-        }
-        public int compareTo(Object o) {
-            int x = superCompareTo(o);
-            if (x == 0) {
-                x = value.compareTo(((Utf8Entry)o).value);
-            }
-            return x;
-        }
-        public String stringValue() {
-            return value;
-        }
-    }
-
-    static boolean isMemberTag(byte tag) {
-        switch (tag) {
-        case Constants.CONSTANT_Fieldref:
-        case Constants.CONSTANT_Methodref:
-        case Constants.CONSTANT_InterfaceMethodref:
-            return true;
-        }
-        return false;
-    }
-
-    static byte numberTagOf(Number value) {
-        if (value instanceof Integer)  return Constants.CONSTANT_Integer;
-        if (value instanceof Float)    return Constants.CONSTANT_Float;
-        if (value instanceof Long)     return Constants.CONSTANT_Long;
-        if (value instanceof Double)   return Constants.CONSTANT_Double;
-        throw new RuntimeException("bad literal value "+value);
-    }
-
-    static boolean isRefKind(byte refKind) {
-        return (Constants.REF_getField <= refKind && refKind <= Constants.REF_invokeInterface);
-    }
-
-    public abstract static
-    class LiteralEntry extends Entry {
-        protected LiteralEntry(byte tag) {
-            super(tag);
-        }
-
-        public abstract Comparable literalValue();
-    }
-
-    public static
-    class NumberEntry extends LiteralEntry {
-        final Number value;
-        NumberEntry(Number value) {
-            super(numberTagOf(value));
-            this.value = value;
-            hashCode();  // force computation of valueHash
-        }
-        protected int computeValueHash() {
-            return value.hashCode();
-        }
-
-        public boolean equals(Object o) {
-            return (o != null && o.getClass() == NumberEntry.class
-                    && ((NumberEntry) o).value.equals(value));
-
-        }
-        public int compareTo(Object o) {
-            int x = superCompareTo(o);
-            if (x == 0) {
-                @SuppressWarnings("unchecked")
-                Comparable compValue = (Comparable)value;
-                x = compValue.compareTo(((NumberEntry)o).value);
-            }
-            return x;
-        }
-        public Number numberValue() {
-            return value;
-        }
-        public Comparable literalValue() {
-            return (Comparable) value;
-        }
-        public String stringValue() {
-            return value.toString();
-        }
-    }
-
-    public static
-    class StringEntry extends LiteralEntry {
-        final Utf8Entry ref;
-        public Entry getRef(int i) { return i == 0 ? ref : null; }
-
-        StringEntry(Entry ref) {
-            super(Constants.CONSTANT_String);
-            this.ref = (Utf8Entry) ref;
-            hashCode();  // force computation of valueHash
-        }
-        protected int computeValueHash() {
-            return ref.hashCode() + tag;
-        }
-        public boolean equals(Object o) {
-            return (o != null && o.getClass() == StringEntry.class &&
-                    ((StringEntry)o).ref.eq(ref));
-        }
-        public int compareTo(Object o) {
-            int x = superCompareTo(o);
-            if (x == 0) {
-                x = ref.compareTo(((StringEntry)o).ref);
-            }
-            return x;
-        }
-        public Comparable literalValue() {
-            return ref.stringValue();
-        }
-        public String stringValue() {
-            return ref.stringValue();
-        }
-    }
-
-    public static
-    class ClassEntry extends Entry {
-        final Utf8Entry ref;
-        public Entry getRef(int i) { return i == 0 ? ref : null; }
-
-        protected int computeValueHash() {
-            return ref.hashCode() + tag;
-        }
-        ClassEntry(Entry ref) {
-            super(Constants.CONSTANT_Class);
-            this.ref = (Utf8Entry) ref;
-            hashCode();  // force computation of valueHash
-        }
-        public boolean equals(Object o) {
-            return (o != null && o.getClass() == ClassEntry.class
-                    && ((ClassEntry) o).ref.eq(ref));
-        }
-        public int compareTo(Object o) {
-            int x = superCompareTo(o);
-            if (x == 0) {
-                x = ref.compareTo(((ClassEntry)o).ref);
-            }
-            return x;
-        }
-        public String stringValue() {
-            return ref.stringValue();
-        }
-    }
-
-    public static
-    class DescriptorEntry extends Entry {
-        final Utf8Entry      nameRef;
-        final SignatureEntry typeRef;
-        public Entry getRef(int i) {
-            if (i == 0)  return nameRef;
-            if (i == 1)  return typeRef;
-            return null;
-        }
-        DescriptorEntry(Entry nameRef, Entry typeRef) {
-            super(Constants.CONSTANT_NameandType);
-            if (typeRef instanceof Utf8Entry) {
-                typeRef = getSignatureEntry(typeRef.stringValue());
-            }
-            this.nameRef = (Utf8Entry) nameRef;
-            this.typeRef = (SignatureEntry) typeRef;
-            hashCode();  // force computation of valueHash
-        }
-        protected int computeValueHash() {
-            int hc2 = typeRef.hashCode();
-            return (nameRef.hashCode() + (hc2 << 8)) ^ hc2;
-        }
-        public boolean equals(Object o) {
-            if (o == null || o.getClass() != DescriptorEntry.class) {
-                return false;
-            }
-            DescriptorEntry that = (DescriptorEntry)o;
-            return this.nameRef.eq(that.nameRef)
-                && this.typeRef.eq(that.typeRef);
-        }
-        public int compareTo(Object o) {
-            int x = superCompareTo(o);
-            if (x == 0) {
-                DescriptorEntry that = (DescriptorEntry)o;
-                // Primary key is typeRef, not nameRef.
-                x = this.typeRef.compareTo(that.typeRef);
-                if (x == 0)
-                    x = this.nameRef.compareTo(that.nameRef);
-            }
-            return x;
-        }
-        public String stringValue() {
-            return stringValueOf(nameRef, typeRef);
-        }
-        static
-        String stringValueOf(Entry nameRef, Entry typeRef) {
-            return qualifiedStringValue(typeRef, nameRef);
-        }
-
-        public String prettyString() {
-            return nameRef.stringValue()+typeRef.prettyString();
-        }
-
-        public boolean isMethod() {
-            return typeRef.isMethod();
-        }
-
-        public byte getLiteralTag() {
-            return typeRef.getLiteralTag();
-        }
-    }
-
-    static String qualifiedStringValue(Entry e1, Entry e2) {
-        return qualifiedStringValue(e1.stringValue(), e2.stringValue());
-    }
-    static String qualifiedStringValue(String s1, String s234) {
-        // Qualification by dot must decompose uniquely.  Second string might already be qualified.
-        assert(s1.indexOf('.') < 0);
-        return s1+"."+s234;
-    }
-
-    public static
-    class MemberEntry extends Entry {
-        final ClassEntry classRef;
-        final DescriptorEntry descRef;
-        public Entry getRef(int i) {
-            if (i == 0)  return classRef;
-            if (i == 1)  return descRef;
-            return null;
-        }
-        protected int computeValueHash() {
-            int hc2 = descRef.hashCode();
-            return (classRef.hashCode() + (hc2 << 8)) ^ hc2;
-        }
-
-        MemberEntry(byte tag, ClassEntry classRef, DescriptorEntry descRef) {
-            super(tag);
-            assert(isMemberTag(tag));
-            this.classRef = classRef;
-            this.descRef  = descRef;
-            hashCode();  // force computation of valueHash
-        }
-        public boolean equals(Object o) {
-            if (o == null || o.getClass() != MemberEntry.class) {
-                return false;
-            }
-            MemberEntry that = (MemberEntry)o;
-            return this.classRef.eq(that.classRef)
-                && this.descRef.eq(that.descRef);
-        }
-        public int compareTo(Object o) {
-            int x = superCompareTo(o);
-            if (x == 0) {
-                MemberEntry that = (MemberEntry)o;
-                if (Utils.SORT_MEMBERS_DESCR_MAJOR)
-                    // descRef is transmitted as UDELTA5; sort it first?
-                    x = this.descRef.compareTo(that.descRef);
-                // Primary key is classRef.
-                if (x == 0)
-                    x = this.classRef.compareTo(that.classRef);
-                if (x == 0)
-                    x = this.descRef.compareTo(that.descRef);
-            }
-            return x;
-        }
-        public String stringValue() {
-            return stringValueOf(tag, classRef, descRef);
-        }
-        static
-        String stringValueOf(byte tag, ClassEntry classRef, DescriptorEntry descRef) {
-            assert(isMemberTag(tag));
-            String pfx;
-            switch (tag) {
-            case Constants.CONSTANT_Fieldref:            pfx = "Field:";   break;
-            case Constants.CONSTANT_Methodref:           pfx = "Method:";  break;
-            case Constants.CONSTANT_InterfaceMethodref:  pfx = "IMethod:"; break;
-            default:                           pfx = tag+"???";  break;
-            }
-            return pfx+qualifiedStringValue(classRef, descRef);
-        }
-
-        public boolean isMethod() {
-            return descRef.isMethod();
-        }
-    }
-
-    public static
-    class SignatureEntry extends Entry {
-        final Utf8Entry    formRef;
-        final ClassEntry[] classRefs;
-        String             value;
-        Utf8Entry          asUtf8Entry;
-        public Entry getRef(int i) {
-            if (i == 0)  return formRef;
-            return i-1 < classRefs.length ? classRefs[i-1] : null;
-        }
-        SignatureEntry(String value) {
-            super(Constants.CONSTANT_Signature);
-            value = value.intern();  // always do this
-            this.value = value;
-            String[] parts = structureSignature(value);
-            formRef = getUtf8Entry(parts[0]);
-            classRefs = new ClassEntry[parts.length-1];
-            for (int i = 1; i < parts.length; i++) {
-                classRefs[i - 1] = getClassEntry(parts[i]);
-            }
-            hashCode();  // force computation of valueHash
-        }
-        protected int computeValueHash() {
-            stringValue();  // force computation of value
-            return value.hashCode() + tag;
-        }
-
-        public Utf8Entry asUtf8Entry() {
-            if (asUtf8Entry == null) {
-                asUtf8Entry = getUtf8Entry(stringValue());
-            }
-            return asUtf8Entry;
-        }
-
-        public boolean equals(Object o) {
-            return (o != null && o.getClass() == SignatureEntry.class &&
-                    ((SignatureEntry)o).value.equals(value));
-        }
-        public int compareTo(Object o) {
-            int x = superCompareTo(o);
-            if (x == 0) {
-                SignatureEntry that = (SignatureEntry)o;
-                x = compareSignatures(this.value, that.value);
-            }
-            return x;
-        }
-        public String stringValue() {
-            if (value == null) {
-                value = stringValueOf(formRef, classRefs);
-            }
-            return value;
-        }
-        static
-        String stringValueOf(Utf8Entry formRef, ClassEntry[] classRefs) {
-            String[] parts = new String[1+classRefs.length];
-            parts[0] = formRef.stringValue();
-            for (int i = 1; i < parts.length; i++) {
-                parts[i] = classRefs[i - 1].stringValue();
-            }
-            return flattenSignature(parts).intern();
-        }
-
-        public int computeSize(boolean countDoublesTwice) {
-            String form = formRef.stringValue();
-            int min = 0;
-            int max = 1;
-            if (isMethod()) {
-                min = 1;
-                max = form.indexOf(')');
-            }
-            int size = 0;
-            for (int i = min; i < max; i++) {
-                switch (form.charAt(i)) {
-                    case 'D':
-                    case 'J':
-                        if (countDoublesTwice) {
-                            size++;
-                        }
-                        break;
-                    case '[':
-                        // Skip rest of array info.
-                        while (form.charAt(i) == '[') {
-                            ++i;
-                        }
-                        break;
-                    case ';':
-                        continue;
-                    default:
-                        assert (0 <= Constants.JAVA_SIGNATURE_CHARS.indexOf(form.charAt(i)));
-                        break;
-                }
-                size++;
-            }
-            return size;
-        }
-        public boolean isMethod() {
-            return formRef.stringValue().charAt(0) == '(';
-        }
-        public byte getLiteralTag() {
-            switch (formRef.stringValue().charAt(0)) {
-            case 'I': return Constants.CONSTANT_Integer;
-            case 'J': return Constants.CONSTANT_Long;
-            case 'F': return Constants.CONSTANT_Float;
-            case 'D': return Constants.CONSTANT_Double;
-            case 'B': case 'S': case 'C': case 'Z':
-                return Constants.CONSTANT_Integer;
-            case 'L':
-                /*
-                switch (classRefs[0].stringValue()) {
-                case "java/lang/String":
-                    return CONSTANT_String;
-                case "java/lang/invoke/MethodHandle":
-                    return CONSTANT_MethodHandle;
-                case "java/lang/invoke/MethodType":
-                    return CONSTANT_MethodType;
-                default:  // java/lang/Object, etc.
-                    return CONSTANT_LoadableValue;
-                }
-                */
-                return Constants.CONSTANT_String;  // JDK 7 ConstantValue limited to String
-            }
-            assert(false);
-            return Constants.CONSTANT_None;
-        }
-        public String prettyString() {
-            String s;
-            if (isMethod()) {
-                s = formRef.stringValue();
-                s = s.substring(0, 1+s.indexOf(')'));
-            } else {
-                s = "/" + formRef.stringValue();
-            }
-            int i;
-            while ((i = s.indexOf(';')) >= 0) {
-                s = s.substring(0, i) + s.substring(i + 1);
-            }
-            return s;
-        }
-    }
-
-    static int compareSignatures(String s1, String s2) {
-        return compareSignatures(s1, s2, null, null);
-    }
-    static int compareSignatures(String s1, String s2, String[] p1, String[] p2) {
-        final int S1_COMES_FIRST = -1;
-        final int S2_COMES_FIRST = +1;
-        char c1 = s1.charAt(0);
-        char c2 = s2.charAt(0);
-        // fields before methods (because there are fewer of them)
-        if (c1 != '(' && c2 == '(')  return S1_COMES_FIRST;
-        if (c2 != '(' && c1 == '(')  return S2_COMES_FIRST;
-        if (p1 == null)  p1 = structureSignature(s1);
-        if (p2 == null)  p2 = structureSignature(s2);
-        /*
-         // non-classes before classes (because there are fewer of them)
-         if (p1.length == 1 && p2.length > 1)  return S1_COMES_FIRST;
-         if (p2.length == 1 && p1.length > 1)  return S2_COMES_FIRST;
-         // all else being equal, use the same comparison as for Utf8 strings
-         return s1.compareTo(s2);
-         */
-        if (p1.length != p2.length)  return p1.length - p2.length;
-        int length = p1.length;
-        for (int i = length; --i >= 0; ) {
-            int res = p1[i].compareTo(p2[i]);
-            if (res != 0)  return res;
-        }
-        assert(s1.equals(s2));
-        return 0;
-    }
-
-    static int countClassParts(Utf8Entry formRef) {
-        int num = 0;
-        String s = formRef.stringValue();
-        for (int i = 0; i < s.length(); i++) {
-            if (s.charAt(i) == 'L')  ++num;
-        }
-        return num;
-    }
-
-    static String flattenSignature(String[] parts) {
-        String form = parts[0];
-        if (parts.length == 1)  return form;
-        int len = form.length();
-        for (int i = 1; i < parts.length; i++) {
-            len += parts[i].length();
-        }
-        char[] sig = new char[len];
-        int j = 0;
-        int k = 1;
-        for (int i = 0; i < form.length(); i++) {
-            char ch = form.charAt(i);
-            sig[j++] = ch;
-            if (ch == 'L') {
-                String cls = parts[k++];
-                cls.getChars(0, cls.length(), sig, j);
-                j += cls.length();
-                //sig[j++] = ';';
-            }
-        }
-        assert(j == len);
-        assert(k == parts.length);
-        return new String(sig);
-    }
-
-    private static int skipTo(char semi, String sig, int i) {
-        i = sig.indexOf(semi, i);
-        return (i >= 0) ? i : sig.length();
-    }
-
-    static String[] structureSignature(String sig) {
-        int firstl = sig.indexOf('L');
-        if (firstl < 0) {
-            String[] parts = { sig };
-            return parts;
-        }
-        // Segment the string like sig.split("L\\([^;<]*\\)").
-        // N.B.: Previous version of this code did a more complex match,
-        // to next ch < ' ' or ch in [';'..'@'].  The only important
-        // characters are ';' and '<', since they are part of the
-        // signature syntax.
-        // Examples:
-        //   "(Ljava/lang/Object;IJLLoo;)V" => {"(L;IJL;)V", "java/lang/Object", "Loo"}
-        //   "Ljava/util/List;" => {"L;", "java/util/List", "java/lang/String"}
-        char[] form = null;
-        String[] parts = null;
-        for (int pass = 0; pass <= 1; pass++) {
-            // pass 0 is a sizing pass, pass 1 packs the arrays
-            int formPtr = 0;
-            int partPtr = 1;
-            int nextsemi = 0, nextangl = 0;  // next ';' or '<', or zero, or sigLen
-            int lastj = 0;
-            for (int i = firstl + 1, j; i > 0; i = sig.indexOf('L', j) + 1) {
-                // sig[i-1] is 'L', while sig[j] will be the first ';' or '<' after it
-                // each part is in sig[i .. j-1]
-                if (nextsemi < i)  nextsemi = skipTo(';', sig, i);
-                if (nextangl < i)  nextangl = skipTo('<', sig, i);
-                j = (nextsemi < nextangl ? nextsemi : nextangl);
-                if (pass != 0) {
-                    sig.getChars(lastj, i, form, formPtr);
-                    parts[partPtr] = sig.substring(i, j);
-                }
-                formPtr += (i - lastj);
-                partPtr += 1;
-                lastj = j;
-            }
-            if (pass != 0) {
-                sig.getChars(lastj, sig.length(), form, formPtr);
-                break;
-            }
-            formPtr += (sig.length() - lastj);
-            form = new char[formPtr];
-            parts = new String[partPtr];
-        }
-        parts[0] = new String(form);
-        //assert(flattenSignature(parts).equals(sig));
-        return parts;
-    }
-
-    /** @since 1.7, JSR 292 */
-    public static
-    class MethodHandleEntry extends Entry {
-        final int refKind;
-        final MemberEntry memRef;
-        public Entry getRef(int i) { return i == 0 ? memRef : null; }
-
-        protected int computeValueHash() {
-            int hc2 = refKind;
-            return (memRef.hashCode() + (hc2 << 8)) ^ hc2;
-        }
-
-        MethodHandleEntry(byte refKind, MemberEntry memRef) {
-            super(Constants.CONSTANT_MethodHandle);
-            assert(isRefKind(refKind));
-            this.refKind = refKind;
-            this.memRef  = memRef;
-            hashCode();  // force computation of valueHash
-        }
-        public boolean equals(Object o) {
-            if (o == null || o.getClass() != MethodHandleEntry.class) {
-                return false;
-            }
-            MethodHandleEntry that = (MethodHandleEntry)o;
-            return this.refKind == that.refKind
-                && this.memRef.eq(that.memRef);
-        }
-        public int compareTo(Object o) {
-            int x = superCompareTo(o);
-            if (x == 0) {
-                MethodHandleEntry that = (MethodHandleEntry)o;
-                if (Utils.SORT_HANDLES_KIND_MAJOR)
-                    // Primary key could be refKind.
-                    x = this.refKind - that.refKind;
-                // Primary key is memRef, which is transmitted as UDELTA5.
-                if (x == 0)
-                    x = this.memRef.compareTo(that.memRef);
-                if (x == 0)
-                    x = this.refKind - that.refKind;
-            }
-            return x;
-        }
-        public static String stringValueOf(int refKind, MemberEntry memRef) {
-            return refKindName(refKind)+":"+memRef.stringValue();
-        }
-        public String stringValue() {
-            return stringValueOf(refKind, memRef);
-        }
-    }
-
-    /** @since 1.7, JSR 292 */
-    public static
-    class MethodTypeEntry extends Entry {
-        final SignatureEntry typeRef;
-        public Entry getRef(int i) { return i == 0 ? typeRef : null; }
-
-        protected int computeValueHash() {
-            return typeRef.hashCode() + tag;
-        }
-
-        MethodTypeEntry(SignatureEntry typeRef) {
-            super(Constants.CONSTANT_MethodType);
-            this.typeRef  = typeRef;
-            hashCode();  // force computation of valueHash
-        }
-        public boolean equals(Object o) {
-            if (o == null || o.getClass() != MethodTypeEntry.class) {
-                return false;
-            }
-            MethodTypeEntry that = (MethodTypeEntry)o;
-            return this.typeRef.eq(that.typeRef);
-        }
-        public int compareTo(Object o) {
-            int x = superCompareTo(o);
-            if (x == 0) {
-                MethodTypeEntry that = (MethodTypeEntry)o;
-                x = this.typeRef.compareTo(that.typeRef);
-            }
-            return x;
-        }
-        public String stringValue() {
-            return typeRef.stringValue();
-        }
-    }
-
-    /** @since 1.7, JSR 292 */
-    public static
-    class InvokeDynamicEntry extends Entry {
-        final BootstrapMethodEntry bssRef;
-        final DescriptorEntry descRef;
-        public Entry getRef(int i) {
-            if (i == 0)  return bssRef;
-            if (i == 1)  return descRef;
-            return null;
-        }
-        protected int computeValueHash() {
-            int hc2 = descRef.hashCode();
-            return (bssRef.hashCode() + (hc2 << 8)) ^ hc2;
-        }
-
-        InvokeDynamicEntry(BootstrapMethodEntry bssRef, DescriptorEntry descRef) {
-            super(Constants.CONSTANT_InvokeDynamic);
-            this.bssRef  = bssRef;
-            this.descRef = descRef;
-            hashCode();  // force computation of valueHash
-        }
-        public boolean equals(Object o) {
-            if (o == null || o.getClass() != InvokeDynamicEntry.class) {
-                return false;
-            }
-            InvokeDynamicEntry that = (InvokeDynamicEntry)o;
-            return this.bssRef.eq(that.bssRef)
-                && this.descRef.eq(that.descRef);
-        }
-        public int compareTo(Object o) {
-            int x = superCompareTo(o);
-            if (x == 0) {
-                InvokeDynamicEntry that = (InvokeDynamicEntry)o;
-                if (Utils.SORT_INDY_BSS_MAJOR)
-                    // Primary key could be bsmRef.
-                    x = this.bssRef.compareTo(that.bssRef);
-                // Primary key is descriptor, which is transmitted as UDELTA5.
-                if (x == 0)
-                    x = this.descRef.compareTo(that.descRef);
-                if (x == 0)
-                    x = this.bssRef.compareTo(that.bssRef);
-            }
-            return x;
-        }
-        public String stringValue() {
-            return stringValueOf(bssRef, descRef);
-        }
-        static
-        String stringValueOf(BootstrapMethodEntry bssRef, DescriptorEntry descRef) {
-            return "Indy:"+bssRef.stringValue()+"."+descRef.stringValue();
-        }
-    }
-
-    /** @since 1.7, JSR 292 */
-    public static
-    class BootstrapMethodEntry extends Entry {
-        final MethodHandleEntry bsmRef;
-        final Entry[] argRefs;
-        public Entry getRef(int i) {
-            if (i == 0)  return bsmRef;
-            if (i-1 < argRefs.length)  return argRefs[i-1];
-            return null;
-        }
-        protected int computeValueHash() {
-            int hc2 = bsmRef.hashCode();
-            return (Arrays.hashCode(argRefs) + (hc2 << 8)) ^ hc2;
-        }
-
-        BootstrapMethodEntry(MethodHandleEntry bsmRef, Entry[] argRefs) {
-            super(Constants.CONSTANT_BootstrapMethod);
-            this.bsmRef  = bsmRef;
-            this.argRefs = argRefs.clone();
-            hashCode();  // force computation of valueHash
-        }
-        public boolean equals(Object o) {
-            if (o == null || o.getClass() != BootstrapMethodEntry.class) {
-                return false;
-            }
-            BootstrapMethodEntry that = (BootstrapMethodEntry)o;
-            return this.bsmRef.eq(that.bsmRef)
-                && Arrays.equals(this.argRefs, that.argRefs);
-        }
-        public int compareTo(Object o) {
-            int x = superCompareTo(o);
-            if (x == 0) {
-                BootstrapMethodEntry that = (BootstrapMethodEntry)o;
-                if (Utils.SORT_BSS_BSM_MAJOR)
-                    // Primary key is bsmRef.
-                    x = this.bsmRef.compareTo(that.bsmRef);
-                // Primary key is args array length, which is transmitted as UDELTA5.
-                if (x == 0)
-                    x = compareArgArrays(this.argRefs, that.argRefs);
-                if (x == 0)
-                    x = this.bsmRef.compareTo(that.bsmRef);
-            }
-            return x;
-        }
-        public String stringValue() {
-            return stringValueOf(bsmRef, argRefs);
-        }
-        static
-        String stringValueOf(MethodHandleEntry bsmRef, Entry[] argRefs) {
-            StringBuilder sb = new StringBuilder(bsmRef.stringValue());
-            // Arguments are formatted as "" instead of "[foo,bar,baz]".
-            // This ensures there will be no confusion if "[,]" appear inside of names.
-            char nextSep = '<';
-            boolean didOne = false;
-            for (Entry argRef : argRefs) {
-                sb.append(nextSep).append(argRef.stringValue());
-                nextSep = ';';
-            }
-            if (nextSep == '<')  sb.append(nextSep);
-            sb.append('>');
-            return sb.toString();
-        }
-        static
-        int compareArgArrays(Entry[] a1, Entry[] a2) {
-            int x = a1.length - a2.length;
-            if (x != 0)  return x;
-            for (int i = 0; i < a1.length; i++) {
-                x = a1[i].compareTo(a2[i]);
-                if (x != 0)  break;
-            }
-            return x;
-        }
-    }
-
-    // Handy constants:
-    protected static final Entry[] noRefs = {};
-    protected static final ClassEntry[] noClassRefs = {};
-
-    /** An Index is a mapping between CP entries and small integers. */
-    public static final
-    class Index extends AbstractList {
-        protected String debugName;
-        protected Entry[] cpMap;
-        protected boolean flattenSigs;
-        protected Entry[] getMap() {
-            return cpMap;
-        }
-        protected Index(String debugName) {
-            this.debugName = debugName;
-        }
-        protected Index(String debugName, Entry[] cpMap) {
-            this(debugName);
-            setMap(cpMap);
-        }
-        protected void setMap(Entry[] cpMap) {
-            clearIndex();
-            this.cpMap = cpMap;
-        }
-        protected Index(String debugName, Collection cpMapList) {
-            this(debugName);
-            setMap(cpMapList);
-        }
-        protected void setMap(Collection cpMapList) {
-            cpMap = new Entry[cpMapList.size()];
-            cpMapList.toArray(cpMap);
-            setMap(cpMap);
-        }
-        public int size() {
-            return cpMap.length;
-        }
-        public Entry get(int i) {
-            return cpMap[i];
-        }
-        public Entry getEntry(int i) {
-            // same as get(), with covariant return type
-            return cpMap[i];
-        }
-
-        // Find index of e in cpMap, or return -1 if none.
-        //
-        // As a special hack, if flattenSigs, signatures are
-        // treated as equivalent entries of cpMap.  This is wrong
-        // from a Collection point of view, because contains()
-        // reports true for signatures, but the iterator()
-        // never produces them!
-        private int findIndexOf(Entry e) {
-            if (indexKey == null) {
-                initializeIndex();
-            }
-            int probe = findIndexLocation(e);
-            if (indexKey[probe] != e) {
-                if (flattenSigs && e.tag == Constants.CONSTANT_Signature) {
-                    SignatureEntry se = (SignatureEntry) e;
-                    return findIndexOf(se.asUtf8Entry());
-                }
-                return -1;
-            }
-            int index = indexValue[probe];
-            assert(e.equals(cpMap[index]));
-            return index;
-        }
-        public boolean contains(Entry e) {
-            return findIndexOf(e) >= 0;
-        }
-        // Find index of e in cpMap.  Should not return -1.
-        public int indexOf(Entry e) {
-            int index = findIndexOf(e);
-            if (index < 0 && verbose() > 0) {
-                System.out.println("not found: "+e);
-                System.out.println("       in: "+this.dumpString());
-                Thread.dumpStack();
-            }
-            assert(index >= 0);
-            return index;
-        }
-        public int lastIndexOf(Entry e) {
-            return indexOf(e);
-        }
-
-        public boolean assertIsSorted() {
-            for (int i = 1; i < cpMap.length; i++) {
-                if (cpMap[i-1].compareTo(cpMap[i]) > 0) {
-                    System.out.println("Not sorted at "+(i-1)+"/"+i+": "+this.dumpString());
-                    return false;
-                }
-            }
-            return true;
-        }
-
-        // internal hash table
-        protected Entry[] indexKey;
-        protected int[]   indexValue;
-        protected void clearIndex() {
-            indexKey   = null;
-            indexValue = null;
-        }
-        private int findIndexLocation(Entry e) {
-            int size   = indexKey.length;
-            int hash   = e.hashCode();
-            int probe  = hash & (size - 1);
-            int stride = ((hash >>> 8) | 1) & (size - 1);
-            for (;;) {
-                Entry e1 = indexKey[probe];
-                if (e1 == e || e1 == null)
-                    return probe;
-                probe += stride;
-                if (probe >= size)  probe -= size;
-            }
-        }
-        private void initializeIndex() {
-            if (verbose() > 2)
-                System.out.println("initialize Index "+debugName+" ["+size()+"]");
-            int hsize0 = (int)((cpMap.length + 10) * 1.5);
-            int hsize = 1;
-            while (hsize < hsize0) {
-                hsize <<= 1;
-            }
-            indexKey   = new Entry[hsize];
-            indexValue = new int[hsize];
-            for (int i = 0; i < cpMap.length; i++) {
-                Entry e = cpMap[i];
-                if (e == null)  continue;
-                int probe = findIndexLocation(e);
-                assert(indexKey[probe] == null);  // e has unique index
-                indexKey[probe] = e;
-                indexValue[probe] = i;
-            }
-        }
-        public Entry[] toArray(Entry[] a) {
-            int sz = size();
-            if (a.length < sz)  return super.toArray(a);
-            System.arraycopy(cpMap, 0, a, 0, sz);
-            if (a.length > sz)  a[sz] = null;
-            return a;
-        }
-        public Entry[] toArray() {
-            return toArray(new Entry[size()]);
-        }
-        public Object clone() {
-            return new Index(debugName, cpMap.clone());
-        }
-        public String toString() {
-            return "Index "+debugName+" ["+size()+"]";
-        }
-        public String dumpString() {
-            String s = toString();
-            s += " {\n";
-            for (int i = 0; i < cpMap.length; i++) {
-                s += "    "+i+": "+cpMap[i]+"\n";
-            }
-            s += "}";
-            return s;
-        }
-    }
-
-    // Index methods.
-
-    public static
-    Index makeIndex(String debugName, Entry[] cpMap) {
-        return new Index(debugName, cpMap);
-    }
-
-    public static
-    Index makeIndex(String debugName, Collection cpMapList) {
-        return new Index(debugName, cpMapList);
-    }
-
-    /** Sort this index (destructively) into canonical order. */
-    public static
-    void sort(Index ix) {
-        // %%% Should move this into class Index.
-        ix.clearIndex();
-        Arrays.sort(ix.cpMap);
-        if (verbose() > 2)
-            System.out.println("sorted "+ix.dumpString());
-    }
-
-    /** Return a set of indexes partitioning these entries.
-     *  The keys array must of length this.size(), and marks entries.
-     *  The result array is as long as one plus the largest key value.
-     *  Entries with a negative key are dropped from the partition.
-     */
-    public static
-    Index[] partition(Index ix, int[] keys) {
-        // %%% Should move this into class Index.
-        List> parts = new ArrayList<>();
-        Entry[] cpMap = ix.cpMap;
-        assert(keys.length == cpMap.length);
-        for (int i = 0; i < keys.length; i++) {
-            int key = keys[i];
-            if (key < 0)  continue;
-            while (key >= parts.size()) {
-                parts.add(null);
-            }
-            List part = parts.get(key);
-            if (part == null) {
-                parts.set(key, part = new ArrayList<>());
-            }
-            part.add(cpMap[i]);
-        }
-        Index[] indexes = new Index[parts.size()];
-        for (int key = 0; key < indexes.length; key++) {
-            List part = parts.get(key);
-            if (part == null)  continue;
-            indexes[key] = new Index(ix.debugName+"/part#"+key, part);
-            assert(indexes[key].indexOf(part.get(0)) == 0);
-        }
-        return indexes;
-    }
-    public static
-    Index[] partitionByTag(Index ix) {
-        // Partition by tag.
-        Entry[] cpMap = ix.cpMap;
-        int[] keys = new int[cpMap.length];
-        for (int i = 0; i < keys.length; i++) {
-            Entry e = cpMap[i];
-            keys[i] = (e == null)? -1: e.tag;
-        }
-        Index[] byTag = partition(ix, keys);
-        for (int tag = 0; tag < byTag.length; tag++) {
-            if (byTag[tag] == null)  continue;
-            byTag[tag].debugName = tagName(tag);
-        }
-        if (byTag.length < Constants.CONSTANT_Limit) {
-            Index[] longer = new Index[Constants.CONSTANT_Limit];
-            System.arraycopy(byTag, 0, longer, 0, byTag.length);
-            byTag = longer;
-        }
-        return byTag;
-    }
-
-    /** Coherent group of constant pool indexes. */
-    public static
-    class IndexGroup {
-        private Index[] indexByTag = new Index[Constants.CONSTANT_Limit];
-        private Index[] indexByTagGroup;
-        private int[]   untypedFirstIndexByTag;
-        private int     totalSizeQQ;
-        private Index[][] indexByTagAndClass;
-
-        /** Index of all CP entries of all types, in definition order. */
-        private Index makeTagGroupIndex(byte tagGroupTag, byte[] tagsInGroup) {
-            if (indexByTagGroup == null)
-                indexByTagGroup = new Index[Constants.CONSTANT_GroupLimit - Constants.CONSTANT_GroupFirst];
-            int which = tagGroupTag - Constants.CONSTANT_GroupFirst;
-            assert(indexByTagGroup[which] == null);
-            int fillp = 0;
-            Entry[] cpMap = null;
-            for (int pass = 1; pass <= 2; pass++) {
-                untypedIndexOf(null);  // warm up untypedFirstIndexByTag
-                for (byte tag : tagsInGroup) {
-                    Index ix = indexByTag[tag];
-                    if (ix == null)  continue;
-                    int ixLen = ix.cpMap.length;
-                    if (ixLen == 0)  continue;
-                    assert(tagGroupTag == Constants.CONSTANT_All
-                            ? fillp == untypedFirstIndexByTag[tag]
-                            : fillp  < untypedFirstIndexByTag[tag]);
-                    if (cpMap != null) {
-                        assert(cpMap[fillp] == null);
-                        assert(cpMap[fillp+ixLen-1] == null);
-                        System.arraycopy(ix.cpMap, 0, cpMap, fillp, ixLen);
-                    }
-                    fillp += ixLen;
-                }
-                if (cpMap == null) {
-                    assert(pass == 1);
-                    // get ready for pass 2
-                    cpMap = new Entry[fillp];
-                    fillp = 0;
-                }
-            }
-            indexByTagGroup[which] = new Index(tagName(tagGroupTag), cpMap);
-            return indexByTagGroup[which];
-        }
-
-        public int untypedIndexOf(Entry e) {
-            if (untypedFirstIndexByTag == null) {
-                untypedFirstIndexByTag = new int[Constants.CONSTANT_Limit+1];
-                int fillp = 0;
-                for (int i = 0; i < TAGS_IN_ORDER.length; i++) {
-                    byte tag = TAGS_IN_ORDER[i];
-                    Index ix = indexByTag[tag];
-                    if (ix == null)  continue;
-                    int ixLen = ix.cpMap.length;
-                    untypedFirstIndexByTag[tag] = fillp;
-                    fillp += ixLen;
-                }
-                untypedFirstIndexByTag[Constants.CONSTANT_Limit] = fillp;
-            }
-            if (e == null)  return -1;
-            int tag = e.tag;
-            Index ix = indexByTag[tag];
-            if (ix == null)  return -1;
-            int idx = ix.findIndexOf(e);
-            if (idx >= 0)
-                idx += untypedFirstIndexByTag[tag];
-            return idx;
-        }
-
-        public void initIndexByTag(byte tag, Index ix) {
-            assert(indexByTag[tag] == null);  // do not init twice
-            Entry[] cpMap = ix.cpMap;
-            for (int i = 0; i < cpMap.length; i++) {
-                // It must be a homogeneous Entry set.
-                assert(cpMap[i].tag == tag);
-            }
-            if (tag == Constants.CONSTANT_Utf8) {
-                // Special case:  First Utf8 must always be empty string.
-                assert(cpMap.length == 0 || cpMap[0].stringValue().equals(""));
-            }
-            indexByTag[tag] = ix;
-            // decache indexes derived from this one:
-            untypedFirstIndexByTag = null;
-            indexByTagGroup = null;
-            if (indexByTagAndClass != null)
-                indexByTagAndClass[tag] = null;
-        }
-
-        /** Index of all CP entries of a given tag. */
-        public Index getIndexByTag(byte tag) {
-            if (tag >= Constants.CONSTANT_GroupFirst)
-                return getIndexByTagGroup(tag);
-            Index ix = indexByTag[tag];
-            if (ix == null) {
-                // Make an empty one by default.
-                ix = new Index(tagName(tag), new Entry[0]);
-                indexByTag[tag] = ix;
-            }
-            return ix;
-        }
-
-        private Index getIndexByTagGroup(byte tag) {
-            // pool groups:
-            if (indexByTagGroup != null) {
-                Index ix = indexByTagGroup[tag - Constants.CONSTANT_GroupFirst];
-                if (ix != null)  return ix;
-            }
-            switch (tag) {
-            case Constants.CONSTANT_All:
-                return makeTagGroupIndex(Constants.CONSTANT_All, TAGS_IN_ORDER);
-            case Constants.CONSTANT_LoadableValue:
-                    return makeTagGroupIndex(Constants.CONSTANT_LoadableValue, LOADABLE_VALUE_TAGS);
-            case Constants.CONSTANT_AnyMember:
-                return makeTagGroupIndex(Constants.CONSTANT_AnyMember, ANY_MEMBER_TAGS);
-            case Constants.CONSTANT_FieldSpecific:
-                // This one does not have any fixed index, since it is context-specific.
-                return null;
-            }
-            throw new AssertionError("bad tag group "+tag);
-        }
-
-        /** Index of all CP entries of a given tag and class. */
-        public Index getMemberIndex(byte tag, ClassEntry classRef) {
-            if (classRef == null)
-                throw new RuntimeException("missing class reference for " + tagName(tag));
-            if (indexByTagAndClass == null)
-                indexByTagAndClass = new Index[Constants.CONSTANT_Limit][];
-            Index allClasses =  getIndexByTag(Constants.CONSTANT_Class);
-            Index[] perClassIndexes = indexByTagAndClass[tag];
-            if (perClassIndexes == null) {
-                // Create the partition now.
-                // Divide up all entries of the given tag according to their class.
-                Index allMembers = getIndexByTag(tag);
-                int[] whichClasses = new int[allMembers.size()];
-                for (int i = 0; i < whichClasses.length; i++) {
-                    MemberEntry e = (MemberEntry) allMembers.get(i);
-                    int whichClass = allClasses.indexOf(e.classRef);
-                    whichClasses[i] = whichClass;
-                }
-                perClassIndexes = partition(allMembers, whichClasses);
-                for (int i = 0; i < perClassIndexes.length; i++) {
-                    assert (perClassIndexes[i] == null ||
-                            perClassIndexes[i].assertIsSorted());
-                }
-                indexByTagAndClass[tag] = perClassIndexes;
-            }
-            int whichClass = allClasses.indexOf(classRef);
-            return perClassIndexes[whichClass];
-        }
-
-        // Given the sequence of all methods of the given name and class,
-        // produce the ordinal of this particular given overloading.
-        public int getOverloadingIndex(MemberEntry methodRef) {
-            Index ix = getMemberIndex(methodRef.tag, methodRef.classRef);
-            Utf8Entry nameRef = methodRef.descRef.nameRef;
-            int ord = 0;
-            for (int i = 0; i < ix.cpMap.length; i++) {
-                MemberEntry e = (MemberEntry) ix.cpMap[i];
-                if (e.equals(methodRef))
-                    return ord;
-                if (e.descRef.nameRef.equals(nameRef))
-                    // Found a different overloading.  Increment the ordinal.
-                    ord++;
-            }
-            throw new RuntimeException("should not reach here");
-        }
-
-        // Inverse of getOverloadingIndex
-        public MemberEntry getOverloadingForIndex(byte tag, ClassEntry classRef, String name, int which) {
-            assert(name.equals(name.intern()));
-            Index ix = getMemberIndex(tag, classRef);
-            int ord = 0;
-            for (int i = 0; i < ix.cpMap.length; i++) {
-                MemberEntry e = (MemberEntry) ix.cpMap[i];
-                if (e.descRef.nameRef.stringValue().equals(name)) {
-                    if (ord == which)  return e;
-                    ord++;
-                }
-            }
-            throw new RuntimeException("should not reach here");
-        }
-
-        public boolean haveNumbers() {
-            for (byte tag : NUMBER_TAGS) {
-                if (getIndexByTag(tag).size() > 0)  return true;
-            }
-            return false;
-        }
-
-        public boolean haveExtraTags() {
-            for (byte tag : EXTRA_TAGS) {
-                if (getIndexByTag(tag).size() > 0)  return true;
-            }
-            return false;
-        }
-
-    }
-
-    /** Close the set cpRefs under the getRef(*) relation.
-     *  Also, if flattenSigs, replace all signatures in cpRefs
-     *  by their equivalent Utf8s.
-     *  Also, discard null from cpRefs.
-     */
-    public static void completeReferencesIn(Set cpRefs, boolean flattenSigs) {
-         completeReferencesIn(cpRefs, flattenSigs, null);
-    }
-
-    public static
-    void completeReferencesIn(Set cpRefs, boolean flattenSigs,
-                              Listbsms) {
-        cpRefs.remove(null);
-        for (ListIterator work =
-                 new ArrayList<>(cpRefs).listIterator(cpRefs.size());
-             work.hasPrevious(); ) {
-            Entry e = work.previous();
-            work.remove();          // pop stack
-            assert(e != null);
-            if (flattenSigs && e.tag == Constants.CONSTANT_Signature) {
-                SignatureEntry se = (SignatureEntry) e;
-                Utf8Entry      ue = se.asUtf8Entry();
-                // Totally replace e by se.
-                cpRefs.remove(se);
-                cpRefs.add(ue);
-                e = ue;   // do not descend into the sig
-            }
-            if (bsms != null && e.tag == Constants.CONSTANT_BootstrapMethod) {
-                BootstrapMethodEntry bsm = (BootstrapMethodEntry)e;
-                cpRefs.remove(bsm);
-                // move it away to the side table where it belongs
-                if (!bsms.contains(bsm))
-                    bsms.add(bsm);
-                // fall through to recursively add refs for this entry
-            }
-            // Recursively add the refs of e to cpRefs:
-            for (int i = 0; ; i++) {
-                Entry re = e.getRef(i);
-                if (re == null)
-                    break;          // no more refs in e
-                if (cpRefs.add(re)) // output the ref
-                    work.add(re);   // push stack, if a new ref
-            }
-        }
-    }
-
-    static double percent(int num, int den) {
-        return (int)((10000.0*num)/den + 0.5) / 100.0;
-    }
-
-    public static String tagName(int tag) {
-        switch (tag) {
-            case Constants.CONSTANT_Utf8:                 return "Utf8";
-            case Constants.CONSTANT_Integer:              return "Integer";
-            case Constants.CONSTANT_Float:                return "Float";
-            case Constants.CONSTANT_Long:                 return "Long";
-            case Constants.CONSTANT_Double:               return "Double";
-            case Constants.CONSTANT_Class:                return "Class";
-            case Constants.CONSTANT_String:               return "String";
-            case Constants.CONSTANT_Fieldref:             return "Fieldref";
-            case Constants.CONSTANT_Methodref:            return "Methodref";
-            case Constants.CONSTANT_InterfaceMethodref:   return "InterfaceMethodref";
-            case Constants.CONSTANT_NameandType:          return "NameandType";
-            case Constants.CONSTANT_MethodHandle:         return "MethodHandle";
-            case Constants.CONSTANT_MethodType:           return "MethodType";
-            case Constants.CONSTANT_InvokeDynamic:        return "InvokeDynamic";
-
-                // pseudo-tags:
-            case Constants.CONSTANT_All:                  return "**All";
-            case Constants.CONSTANT_None:                 return "**None";
-            case Constants.CONSTANT_LoadableValue:        return "**LoadableValue";
-            case Constants.CONSTANT_AnyMember:            return "**AnyMember";
-            case Constants.CONSTANT_FieldSpecific:        return "*FieldSpecific";
-            case Constants.CONSTANT_Signature:            return "*Signature";
-            case Constants.CONSTANT_BootstrapMethod:      return "*BootstrapMethod";
-        }
-        return "tag#"+tag;
-    }
-
-    public static String refKindName(int refKind) {
-        switch (refKind) {
-            case Constants.REF_getField:                  return "getField";
-            case Constants.REF_getStatic:                 return "getStatic";
-            case Constants.REF_putField:                  return "putField";
-            case Constants.REF_putStatic:                 return "putStatic";
-            case Constants.REF_invokeVirtual:             return "invokeVirtual";
-            case Constants.REF_invokeStatic:              return "invokeStatic";
-            case Constants.REF_invokeSpecial:             return "invokeSpecial";
-            case Constants.REF_newInvokeSpecial:          return "newInvokeSpecial";
-            case Constants.REF_invokeInterface:           return "invokeInterface";
-        }
-        return "refKind#"+refKind;
-    }
-
-    // archive constant pool definition order
-    static final byte TAGS_IN_ORDER[] = {
-        Constants.CONSTANT_Utf8,
-        Constants.CONSTANT_Integer,           // cp_Int
-        Constants.CONSTANT_Float,
-        Constants.CONSTANT_Long,
-        Constants.CONSTANT_Double,
-        Constants.CONSTANT_String,            // note that String=8 precedes Class=7
-        Constants.CONSTANT_Class,
-        Constants.CONSTANT_Signature,
-        Constants.CONSTANT_NameandType,       // cp_Descr
-        Constants.CONSTANT_Fieldref,          // cp_Field
-        Constants.CONSTANT_Methodref,         // cp_Method
-        Constants.CONSTANT_InterfaceMethodref, // cp_Imethod
-
-        // Constants defined in JDK 7 and later:
-        Constants.CONSTANT_MethodHandle,
-        Constants.CONSTANT_MethodType,
-        Constants.CONSTANT_BootstrapMethod,  // pseudo-tag, really stored in a class attribute
-        Constants.CONSTANT_InvokeDynamic
-    };
-    static final byte TAG_ORDER[];
-    static {
-        TAG_ORDER = new byte[Constants.CONSTANT_Limit];
-        for (int i = 0; i < TAGS_IN_ORDER.length; i++) {
-            TAG_ORDER[TAGS_IN_ORDER[i]] = (byte)(i+1);
-        }
-        /*
-        System.out.println("TAG_ORDER[] = {");
-        for (int i = 0; i < TAG_ORDER.length; i++)
-            System.out.println("  "+TAG_ORDER[i]+",");
-        System.out.println("};");
-        */
-    }
-    static final byte[] NUMBER_TAGS = {
-        Constants.CONSTANT_Integer, Constants.CONSTANT_Float, Constants.CONSTANT_Long, Constants.CONSTANT_Double
-    };
-    static final byte[] EXTRA_TAGS = {
-        Constants.CONSTANT_MethodHandle, Constants.CONSTANT_MethodType,
-        Constants.CONSTANT_BootstrapMethod, // pseudo-tag
-        Constants.CONSTANT_InvokeDynamic
-    };
-    static final byte[] LOADABLE_VALUE_TAGS = { // for CONSTANT_LoadableValue
-        Constants.CONSTANT_Integer, Constants.CONSTANT_Float, Constants.CONSTANT_Long, Constants.CONSTANT_Double,
-        Constants.CONSTANT_String, Constants.CONSTANT_Class,
-        Constants.CONSTANT_MethodHandle, Constants.CONSTANT_MethodType
-    };
-    static final byte[] ANY_MEMBER_TAGS = { // for CONSTANT_AnyMember
-        Constants.CONSTANT_Fieldref, Constants.CONSTANT_Methodref, Constants.CONSTANT_InterfaceMethodref
-    };
-    static final byte[] FIELD_SPECIFIC_TAGS = { // for CONSTANT_FieldSpecific
-        Constants.CONSTANT_Integer, Constants.CONSTANT_Float, Constants.CONSTANT_Long, Constants.CONSTANT_Double,
-        Constants.CONSTANT_String
-    };
-    static {
-        assert(
-            verifyTagOrder(TAGS_IN_ORDER) &&
-            verifyTagOrder(NUMBER_TAGS) &&
-            verifyTagOrder(EXTRA_TAGS) &&
-            verifyTagOrder(LOADABLE_VALUE_TAGS) &&
-            verifyTagOrder(ANY_MEMBER_TAGS) &&
-            verifyTagOrder(FIELD_SPECIFIC_TAGS)
-        );
-    }
-    private static boolean verifyTagOrder(byte[] tags) {
-        int prev = -1;
-        for (byte tag : tags) {
-            int next = TAG_ORDER[tag];
-            assert(next > 0) : "tag not found: "+tag;
-            assert(TAGS_IN_ORDER[next-1] == tag) : "tag repeated: "+tag+" => "+next+" => "+TAGS_IN_ORDER[next-1];
-            assert(prev < next) : "tags not in order: "+Arrays.toString(tags)+" at "+tag;
-            prev = next;
-        }
-        return true;
-    }
-}
diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Constants.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Constants.java
deleted file mode 100644
index 33d07c7a5..000000000
--- a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Constants.java
+++ /dev/null
@@ -1,503 +0,0 @@
-/*
- * Copyright (c) 2001, 2017, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package net.fabricmc.shade.com.sun.java.util.jar.pack;
-
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Shared constants
- * @author John Rose
- */
-class Constants {
-
-    private Constants(){}
-
-    public static final int JAVA_MAGIC = 0xCAFEBABE;
-
-    /*
-        Java Class Version numbers history
-        1.0 to 1.3.X 45,3
-        1.4 to 1.4.X 46,0
-        1.5 to 1.5.X 49,0
-        1.6 to 1.6.X 50,0
-        1.7 to 1.7.X 51,0
-        1.8 to 1.8.X 52,0
-        1.9 to 1.9.X 53,0
-        1.10 to 1.10.X 54,0
-        1.11 to 1.11.X 55,0
-    */
-
-    public static final Package.Version JAVA_MIN_CLASS_VERSION =
-            Package.Version.of(45, 03);
-
-    public static final Package.Version JAVA5_MAX_CLASS_VERSION =
-            Package.Version.of(49, 00);
-
-    public static final Package.Version JAVA6_MAX_CLASS_VERSION =
-            Package.Version.of(50, 00);
-
-    public static final Package.Version JAVA7_MAX_CLASS_VERSION =
-            Package.Version.of(51, 00);
-
-    public static final Package.Version JAVA8_MAX_CLASS_VERSION =
-            Package.Version.of(52, 00);
-
-    public static final Package.Version JAVA9_MAX_CLASS_VERSION =
-            Package.Version.of(53, 00);
-
-    public static final Package.Version JAVA10_MAX_CLASS_VERSION =
-            Package.Version.of(54, 00);
-
-    public static final Package.Version JAVA11_MAX_CLASS_VERSION =
-            Package.Version.of(55, 00);
-
-    public static final int JAVA_PACKAGE_MAGIC = 0xCAFED00D;
-
-    public static final Package.Version JAVA5_PACKAGE_VERSION =
-            Package.Version.of(150, 7);
-
-    public static final Package.Version JAVA6_PACKAGE_VERSION =
-            Package.Version.of(160, 1);
-
-    public static final Package.Version JAVA7_PACKAGE_VERSION =
-            Package.Version.of(170, 1);
-
-    public static final Package.Version JAVA8_PACKAGE_VERSION =
-            Package.Version.of(171, 0);
-
-    // upper limit, should point to the latest class version
-    public static final Package.Version JAVA_MAX_CLASS_VERSION =
-            JAVA11_MAX_CLASS_VERSION;
-
-    // upper limit should point to the latest package version, for version info!.
-    public static final Package.Version MAX_PACKAGE_VERSION =
-            JAVA7_PACKAGE_VERSION;
-
-    public static final int CONSTANT_POOL_INDEX_LIMIT  = 0x10000;
-    public static final int CONSTANT_POOL_NARROW_LIMIT = 0x00100;
-
-    public static final String JAVA_SIGNATURE_CHARS = "BSCIJFDZLV([";
-
-    public static final byte CONSTANT_Utf8 = 1;
-    public static final byte CONSTANT_unused2 = 2;  // unused, was Unicode
-    public static final byte CONSTANT_Integer = 3;
-    public static final byte CONSTANT_Float = 4;
-    public static final byte CONSTANT_Long = 5;
-    public static final byte CONSTANT_Double = 6;
-    public static final byte CONSTANT_Class = 7;
-    public static final byte CONSTANT_String = 8;
-    public static final byte CONSTANT_Fieldref = 9;
-    public static final byte CONSTANT_Methodref = 10;
-    public static final byte CONSTANT_InterfaceMethodref = 11;
-    public static final byte CONSTANT_NameandType = 12;
-    public static final byte CONSTANT_unused13 = 13;
-    public static final byte CONSTANT_unused14 = 14;
-    public static final byte CONSTANT_MethodHandle = 15;
-    public static final byte CONSTANT_MethodType = 16;
-    public static final byte CONSTANT_unused17 = 17;  // unused
-    public static final byte CONSTANT_InvokeDynamic = 18;
-
-    // pseudo-constants:
-    public static final byte CONSTANT_None = 0;
-    public static final byte CONSTANT_Signature = CONSTANT_unused13;
-    public static final byte CONSTANT_BootstrapMethod = CONSTANT_unused17; // used only in InvokeDynamic constants
-    public static final byte CONSTANT_Limit = 19;
-
-    public static final byte CONSTANT_All = 50;  // combined global map
-    public static final byte CONSTANT_LoadableValue = 51; // used for 'KL' and qldc operands
-    public static final byte CONSTANT_AnyMember = 52; // union of refs to field or (interface) method
-    public static final byte CONSTANT_FieldSpecific = 53; // used only for 'KQ' ConstantValue attrs
-    public static final byte CONSTANT_GroupFirst = CONSTANT_All;
-    public static final byte CONSTANT_GroupLimit = CONSTANT_FieldSpecific+1;
-
-    // CONSTANT_MethodHandle reference kinds
-    public static final byte REF_getField = 1;
-    public static final byte REF_getStatic = 2;
-    public static final byte REF_putField = 3;
-    public static final byte REF_putStatic = 4;
-    public static final byte REF_invokeVirtual = 5;
-    public static final byte REF_invokeStatic = 6;
-    public static final byte REF_invokeSpecial = 7;
-    public static final byte REF_newInvokeSpecial = 8;
-    public static final byte REF_invokeInterface = 9;
-
-    // pseudo-access bits
-    public static final int ACC_IC_LONG_FORM   = (1<<16); //for ic_flags
-
-    // attribute "context types"
-    public static final int ATTR_CONTEXT_CLASS  = 0;
-    public static final int ATTR_CONTEXT_FIELD  = 1;
-    public static final int ATTR_CONTEXT_METHOD = 2;
-    public static final int ATTR_CONTEXT_CODE   = 3;
-    public static final int ATTR_CONTEXT_LIMIT  = 4;
-    public static final String[] ATTR_CONTEXT_NAME
-        = { "class", "field", "method", "code" };
-
-    // predefined attr bits
-    public static final int
-        X_ATTR_OVERFLOW = 16,
-        CLASS_ATTR_SourceFile = 17,
-        METHOD_ATTR_Code = 17,
-        FIELD_ATTR_ConstantValue = 17,
-        CLASS_ATTR_EnclosingMethod = 18,
-        METHOD_ATTR_Exceptions = 18,
-        X_ATTR_Signature = 19,
-        X_ATTR_Deprecated = 20,
-        X_ATTR_RuntimeVisibleAnnotations = 21,
-        X_ATTR_RuntimeInvisibleAnnotations = 22,
-        METHOD_ATTR_RuntimeVisibleParameterAnnotations = 23,
-        CLASS_ATTR_InnerClasses = 23,
-        METHOD_ATTR_RuntimeInvisibleParameterAnnotations = 24,
-        CLASS_ATTR_ClassFile_version = 24,
-        METHOD_ATTR_AnnotationDefault = 25,
-        METHOD_ATTR_MethodParameters = 26,           // JDK8
-        X_ATTR_RuntimeVisibleTypeAnnotations = 27,   // JDK8
-        X_ATTR_RuntimeInvisibleTypeAnnotations = 28, // JDK8
-        CODE_ATTR_StackMapTable = 0,  // new in Java 6
-        CODE_ATTR_LineNumberTable = 1,
-        CODE_ATTR_LocalVariableTable = 2,
-        CODE_ATTR_LocalVariableTypeTable = 3;
-
-    // File option bits, from LSB in ascending bit position.
-    public static final int FO_DEFLATE_HINT           = 1<<0;
-    public static final int FO_IS_CLASS_STUB          = 1<<1;
-
-    // Archive option bits, from LSB in ascending bit position:
-    public static final int AO_HAVE_SPECIAL_FORMATS   = 1<<0;
-    public static final int AO_HAVE_CP_NUMBERS        = 1<<1;
-    public static final int AO_HAVE_ALL_CODE_FLAGS    = 1<<2;
-    public static final int AO_HAVE_CP_EXTRAS         = 1<<3;
-    public static final int AO_HAVE_FILE_HEADERS      = 1<<4;
-    public static final int AO_DEFLATE_HINT           = 1<<5;
-    public static final int AO_HAVE_FILE_MODTIME      = 1<<6;
-    public static final int AO_HAVE_FILE_OPTIONS      = 1<<7;
-    public static final int AO_HAVE_FILE_SIZE_HI      = 1<<8;
-    public static final int AO_HAVE_CLASS_FLAGS_HI    = 1<<9;
-    public static final int AO_HAVE_FIELD_FLAGS_HI    = 1<<10;
-    public static final int AO_HAVE_METHOD_FLAGS_HI   = 1<<11;
-    public static final int AO_HAVE_CODE_FLAGS_HI     = 1<<12;
-    public static final int AO_UNUSED_MBZ          = (-1)<<13;  // option bits reserved for future use
-
-    public static final int LG_AO_HAVE_XXX_FLAGS_HI   = 9;
-
-    // visitRefs modes:
-    static final int VRM_CLASSIC = 0;
-    static final int VRM_PACKAGE = 1;
-
-    public static final int NO_MODTIME = 0;  // null modtime value
-
-    // some comstantly empty containers
-    public static final int[]        noInts = {};
-    public static final byte[]       noBytes = {};
-    public static final Object[]     noValues = {};
-    public static final String[]     noStrings = {};
-    public static final List emptyList = Arrays.asList(noValues);
-
-    // meta-coding
-    public static final int
-        _meta_default = 0,
-        _meta_canon_min = 1,
-        _meta_canon_max = 115,
-        _meta_arb = 116,
-        _meta_run = 117,
-        _meta_pop = 141,
-        _meta_limit = 189;
-
-    // bytecodes
-    public static final int
-        _nop                  =   0, // 0x00
-        _aconst_null          =   1, // 0x01
-        _iconst_m1            =   2, // 0x02
-        _iconst_0             =   3, // 0x03
-        _iconst_1             =   4, // 0x04
-        _iconst_2             =   5, // 0x05
-        _iconst_3             =   6, // 0x06
-        _iconst_4             =   7, // 0x07
-        _iconst_5             =   8, // 0x08
-        _lconst_0             =   9, // 0x09
-        _lconst_1             =  10, // 0x0a
-        _fconst_0             =  11, // 0x0b
-        _fconst_1             =  12, // 0x0c
-        _fconst_2             =  13, // 0x0d
-        _dconst_0             =  14, // 0x0e
-        _dconst_1             =  15, // 0x0f
-        _bipush               =  16, // 0x10
-        _sipush               =  17, // 0x11
-        _ldc                  =  18, // 0x12
-        _ldc_w                =  19, // 0x13
-        _ldc2_w               =  20, // 0x14
-        _iload                =  21, // 0x15
-        _lload                =  22, // 0x16
-        _fload                =  23, // 0x17
-        _dload                =  24, // 0x18
-        _aload                =  25, // 0x19
-        _iload_0              =  26, // 0x1a
-        _iload_1              =  27, // 0x1b
-        _iload_2              =  28, // 0x1c
-        _iload_3              =  29, // 0x1d
-        _lload_0              =  30, // 0x1e
-        _lload_1              =  31, // 0x1f
-        _lload_2              =  32, // 0x20
-        _lload_3              =  33, // 0x21
-        _fload_0              =  34, // 0x22
-        _fload_1              =  35, // 0x23
-        _fload_2              =  36, // 0x24
-        _fload_3              =  37, // 0x25
-        _dload_0              =  38, // 0x26
-        _dload_1              =  39, // 0x27
-        _dload_2              =  40, // 0x28
-        _dload_3              =  41, // 0x29
-        _aload_0              =  42, // 0x2a
-        _aload_1              =  43, // 0x2b
-        _aload_2              =  44, // 0x2c
-        _aload_3              =  45, // 0x2d
-        _iaload               =  46, // 0x2e
-        _laload               =  47, // 0x2f
-        _faload               =  48, // 0x30
-        _daload               =  49, // 0x31
-        _aaload               =  50, // 0x32
-        _baload               =  51, // 0x33
-        _caload               =  52, // 0x34
-        _saload               =  53, // 0x35
-        _istore               =  54, // 0x36
-        _lstore               =  55, // 0x37
-        _fstore               =  56, // 0x38
-        _dstore               =  57, // 0x39
-        _astore               =  58, // 0x3a
-        _istore_0             =  59, // 0x3b
-        _istore_1             =  60, // 0x3c
-        _istore_2             =  61, // 0x3d
-        _istore_3             =  62, // 0x3e
-        _lstore_0             =  63, // 0x3f
-        _lstore_1             =  64, // 0x40
-        _lstore_2             =  65, // 0x41
-        _lstore_3             =  66, // 0x42
-        _fstore_0             =  67, // 0x43
-        _fstore_1             =  68, // 0x44
-        _fstore_2             =  69, // 0x45
-        _fstore_3             =  70, // 0x46
-        _dstore_0             =  71, // 0x47
-        _dstore_1             =  72, // 0x48
-        _dstore_2             =  73, // 0x49
-        _dstore_3             =  74, // 0x4a
-        _astore_0             =  75, // 0x4b
-        _astore_1             =  76, // 0x4c
-        _astore_2             =  77, // 0x4d
-        _astore_3             =  78, // 0x4e
-        _iastore              =  79, // 0x4f
-        _lastore              =  80, // 0x50
-        _fastore              =  81, // 0x51
-        _dastore              =  82, // 0x52
-        _aastore              =  83, // 0x53
-        _bastore              =  84, // 0x54
-        _castore              =  85, // 0x55
-        _sastore              =  86, // 0x56
-        _pop                  =  87, // 0x57
-        _pop2                 =  88, // 0x58
-        _dup                  =  89, // 0x59
-        _dup_x1               =  90, // 0x5a
-        _dup_x2               =  91, // 0x5b
-        _dup2                 =  92, // 0x5c
-        _dup2_x1              =  93, // 0x5d
-        _dup2_x2              =  94, // 0x5e
-        _swap                 =  95, // 0x5f
-        _iadd                 =  96, // 0x60
-        _ladd                 =  97, // 0x61
-        _fadd                 =  98, // 0x62
-        _dadd                 =  99, // 0x63
-        _isub                 = 100, // 0x64
-        _lsub                 = 101, // 0x65
-        _fsub                 = 102, // 0x66
-        _dsub                 = 103, // 0x67
-        _imul                 = 104, // 0x68
-        _lmul                 = 105, // 0x69
-        _fmul                 = 106, // 0x6a
-        _dmul                 = 107, // 0x6b
-        _idiv                 = 108, // 0x6c
-        _ldiv                 = 109, // 0x6d
-        _fdiv                 = 110, // 0x6e
-        _ddiv                 = 111, // 0x6f
-        _irem                 = 112, // 0x70
-        _lrem                 = 113, // 0x71
-        _frem                 = 114, // 0x72
-        _drem                 = 115, // 0x73
-        _ineg                 = 116, // 0x74
-        _lneg                 = 117, // 0x75
-        _fneg                 = 118, // 0x76
-        _dneg                 = 119, // 0x77
-        _ishl                 = 120, // 0x78
-        _lshl                 = 121, // 0x79
-        _ishr                 = 122, // 0x7a
-        _lshr                 = 123, // 0x7b
-        _iushr                = 124, // 0x7c
-        _lushr                = 125, // 0x7d
-        _iand                 = 126, // 0x7e
-        _land                 = 127, // 0x7f
-        _ior                  = 128, // 0x80
-        _lor                  = 129, // 0x81
-        _ixor                 = 130, // 0x82
-        _lxor                 = 131, // 0x83
-        _iinc                 = 132, // 0x84
-        _i2l                  = 133, // 0x85
-        _i2f                  = 134, // 0x86
-        _i2d                  = 135, // 0x87
-        _l2i                  = 136, // 0x88
-        _l2f                  = 137, // 0x89
-        _l2d                  = 138, // 0x8a
-        _f2i                  = 139, // 0x8b
-        _f2l                  = 140, // 0x8c
-        _f2d                  = 141, // 0x8d
-        _d2i                  = 142, // 0x8e
-        _d2l                  = 143, // 0x8f
-        _d2f                  = 144, // 0x90
-        _i2b                  = 145, // 0x91
-        _i2c                  = 146, // 0x92
-        _i2s                  = 147, // 0x93
-        _lcmp                 = 148, // 0x94
-        _fcmpl                = 149, // 0x95
-        _fcmpg                = 150, // 0x96
-        _dcmpl                = 151, // 0x97
-        _dcmpg                = 152, // 0x98
-        _ifeq                 = 153, // 0x99
-        _ifne                 = 154, // 0x9a
-        _iflt                 = 155, // 0x9b
-        _ifge                 = 156, // 0x9c
-        _ifgt                 = 157, // 0x9d
-        _ifle                 = 158, // 0x9e
-        _if_icmpeq            = 159, // 0x9f
-        _if_icmpne            = 160, // 0xa0
-        _if_icmplt            = 161, // 0xa1
-        _if_icmpge            = 162, // 0xa2
-        _if_icmpgt            = 163, // 0xa3
-        _if_icmple            = 164, // 0xa4
-        _if_acmpeq            = 165, // 0xa5
-        _if_acmpne            = 166, // 0xa6
-        _goto                 = 167, // 0xa7
-        _jsr                  = 168, // 0xa8
-        _ret                  = 169, // 0xa9
-        _tableswitch          = 170, // 0xaa
-        _lookupswitch         = 171, // 0xab
-        _ireturn              = 172, // 0xac
-        _lreturn              = 173, // 0xad
-        _freturn              = 174, // 0xae
-        _dreturn              = 175, // 0xaf
-        _areturn              = 176, // 0xb0
-        _return               = 177, // 0xb1
-        _getstatic            = 178, // 0xb2
-        _putstatic            = 179, // 0xb3
-        _getfield             = 180, // 0xb4
-        _putfield             = 181, // 0xb5
-        _invokevirtual        = 182, // 0xb6
-        _invokespecial        = 183, // 0xb7
-        _invokestatic         = 184, // 0xb8
-        _invokeinterface      = 185, // 0xb9
-        _invokedynamic        = 186, // 0xba
-        _new                  = 187, // 0xbb
-        _newarray             = 188, // 0xbc
-        _anewarray            = 189, // 0xbd
-        _arraylength          = 190, // 0xbe
-        _athrow               = 191, // 0xbf
-        _checkcast            = 192, // 0xc0
-        _instanceof           = 193, // 0xc1
-        _monitorenter         = 194, // 0xc2
-        _monitorexit          = 195, // 0xc3
-        _wide                 = 196, // 0xc4
-        _multianewarray       = 197, // 0xc5
-        _ifnull               = 198, // 0xc6
-        _ifnonnull            = 199, // 0xc7
-        _goto_w               = 200, // 0xc8
-        _jsr_w                = 201, // 0xc9
-        _bytecode_limit       = 202; // 0xca
-
-    // End marker, used to terminate bytecode sequences:
-    public static final int _end_marker = 255;
-    // Escapes:
-    public static final int _byte_escape = 254;
-    public static final int _ref_escape = 253;
-
-    // Self-relative pseudo-opcodes for better compression.
-    // A "linker op" is a bytecode which links to a class member.
-    // (But in what follows, "invokeinterface" ops are excluded.)
-    //
-    // A "self linker op" is a variant bytecode which works only
-    // with the current class or its super.  Because the number of
-    // possible targets is small, it admits a more compact encoding.
-    // Self linker ops are allowed to absorb a previous "aload_0" op.
-    // There are (7 * 4) self linker ops (super or not, aload_0 or not).
-    //
-    // For simplicity, we define the full symmetric set of variants.
-    // However, some of them are relatively useless.
-    // Self linker ops are enabled by Pack.selfCallVariants (true).
-    public static final int _first_linker_op = _getstatic;
-    public static final int _last_linker_op  = _invokestatic;
-    public static final int _num_linker_ops  = (_last_linker_op - _first_linker_op) + 1;
-    public static final int _self_linker_op  = _bytecode_limit;
-    public static final int _self_linker_aload_flag = 1*_num_linker_ops;
-    public static final int _self_linker_super_flag = 2*_num_linker_ops;
-    public static final int _self_linker_limit = _self_linker_op + 4*_num_linker_ops;
-    // An "invoke init" op is a variant of invokespecial which works
-    // only with the method name "".  There are variants which
-    // link to the current class, the super class, or the class of the
-    // immediately previous "newinstance" op.  There are 3 of these ops.
-    // They all take method signature references as operands.
-    // Invoke init ops are enabled by Pack.initCallVariants (true).
-    public static final int _invokeinit_op = _self_linker_limit;
-    public static final int _invokeinit_self_option = 0;
-    public static final int _invokeinit_super_option = 1;
-    public static final int _invokeinit_new_option = 2;
-    public static final int _invokeinit_limit = _invokeinit_op+3;
-
-    public static final int _pseudo_instruction_limit = _invokeinit_limit;
-    // linker variant limit == 202+(7*4)+3 == 233
-
-    // Ldc variants support strongly typed references to constants.
-    // This lets us index constant pool entries completely according to tag,
-    // which is a great simplification.
-    // Ldc variants gain us only 0.007% improvement in compression ratio,
-    // but they simplify the file format greatly.
-    public static final int _xldc_op = _invokeinit_limit;
-    public static final int _sldc = _ldc;  // previously named _aldc
-    public static final int _cldc = _xldc_op+0;
-    public static final int _ildc = _xldc_op+1;
-    public static final int _fldc = _xldc_op+2;
-    public static final int _sldc_w = _ldc_w;  // previously named _aldc_w
-    public static final int _cldc_w = _xldc_op+3;
-    public static final int _ildc_w = _xldc_op+4;
-    public static final int _fldc_w = _xldc_op+5;
-    public static final int _lldc2_w = _ldc2_w;
-    public static final int _dldc2_w = _xldc_op+6;
-    // anything other than primitive, string, or class must be handled with qldc:
-    public static final int _qldc   = _xldc_op+7;
-    public static final int _qldc_w = _xldc_op+8;
-    public static final int _xldc_limit = _xldc_op+9;
-
-    // handling of InterfaceMethodRef
-    public static final int _invoke_int_op = _xldc_limit;
-    public static final int _invokespecial_int = _invoke_int_op+0;
-    public static final int _invokestatic_int = _invoke_int_op+1;
-    public static final int _invoke_int_limit = _invoke_int_op+2;
-}
diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Driver.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Driver.java
deleted file mode 100644
index 1681f507d..000000000
--- a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Driver.java
+++ /dev/null
@@ -1,749 +0,0 @@
-/*
- * Copyright (c) 2003, 2019, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package net.fabricmc.shade.com.sun.java.util.jar.pack;
-
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.PrintStream;
-import java.nio.charset.StandardCharsets;
-import java.text.MessageFormat;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.Map;
-import java.util.Properties;
-import java.util.ResourceBundle;
-import java.util.SortedMap;
-import java.util.TreeMap;
-import java.util.jar.JarFile;
-import java.util.jar.JarOutputStream;
-import net.fabricmc.shade.java.util.jar.Pack200;
-import java.util.zip.GZIPInputStream;
-import java.util.zip.GZIPOutputStream;
-
-/** Command line interface for Pack200.
- */
-
-@SuppressWarnings({"removal"})
-class Driver {
-    private static final ResourceBundle RESOURCE =
-        ResourceBundle.getBundle("com.sun.java.util.jar.pack.DriverResource");
-    private static boolean suppressDeprecateMsg = false;
-
-    public static void main(String[] ava) throws IOException {
-        List av = new ArrayList<>(Arrays.asList(ava));
-
-        boolean doPack   = true;
-        boolean doUnpack = false;
-        boolean doRepack = false;
-        boolean doZip = true;
-        suppressDeprecateMsg = av.remove("-XDsuppress-tool-removal-message");
-        String logFile = null;
-        String verboseProp = Utils.DEBUG_VERBOSE;
-
-        {
-            // Non-standard, undocumented "--unpack" switch enables unpack mode.
-            String arg0 = av.isEmpty() ? "" : av.get(0);
-            switch (arg0) {
-                case "--pack":
-                av.remove(0);
-                    break;
-                case "--unpack":
-                av.remove(0);
-                doPack = false;
-                doUnpack = true;
-                    break;
-            }
-        }
-
-        if (!suppressDeprecateMsg) {
-            printDeprecateWarning(doPack, System.out);
-        }
-
-        // Collect engine properties here:
-        Map engProps = new HashMap<>();
-        engProps.put(verboseProp, System.getProperty(verboseProp));
-
-        String optionMap;
-        String[] propTable;
-        if (doPack) {
-            optionMap = PACK200_OPTION_MAP;
-            propTable = PACK200_PROPERTY_TO_OPTION;
-        } else {
-            optionMap = UNPACK200_OPTION_MAP;
-            propTable = UNPACK200_PROPERTY_TO_OPTION;
-        }
-
-        // Collect argument properties here:
-        Map avProps = new HashMap<>();
-        try {
-            for (;;) {
-                String state = parseCommandOptions(av, optionMap, avProps);
-                // Translate command line options to Pack200 properties:
-            eachOpt:
-                for (Iterator opti = avProps.keySet().iterator();
-                     opti.hasNext(); ) {
-                    String opt = opti.next();
-                    String prop = null;
-                    for (int i = 0; i < propTable.length; i += 2) {
-                        if (opt.equals(propTable[1+i])) {
-                            prop = propTable[0+i];
-                            break;
-                        }
-                    }
-                    if (prop != null) {
-                        String val = avProps.get(opt);
-                        opti.remove();  // remove opt from avProps
-                        if (!prop.endsWith(".")) {
-                            // Normal string or boolean.
-                            if (!(opt.equals("--verbose")
-                                  || opt.endsWith("="))) {
-                                // Normal boolean; convert to T/F.
-                                boolean flag = (val != null);
-                                if (opt.startsWith("--no-"))
-                                    flag = !flag;
-                                val = flag? "true": "false";
-                            }
-                            engProps.put(prop, val);
-                        } else if (prop.contains(".attribute.")) {
-                            for (String val1 : val.split("\0")) {
-                                String[] val2 = val1.split("=", 2);
-                                engProps.put(prop+val2[0], val2[1]);
-                            }
-                        } else {
-                            // Collection property: pack.pass.file.cli.NNN
-                            int idx = 1;
-                            for (String val1 : val.split("\0")) {
-                                String prop1;
-                                do {
-                                    prop1 = prop+"cli."+(idx++);
-                                } while (engProps.containsKey(prop1));
-                                engProps.put(prop1, val1);
-                            }
-                        }
-                    }
-                }
-
-                // See if there is any other action to take.
-                if ("--config-file=".equals(state)) {
-                    String propFile = av.remove(0);
-                    Properties fileProps = new Properties();
-                    try (InputStream propIn = new FileInputStream(propFile)) {
-                        fileProps.load(propIn);
-                    }
-                    if (engProps.get(verboseProp) != null)
-                        fileProps.list(System.out);
-                    for (Map.Entry me : fileProps.entrySet()) {
-                        engProps.put((String) me.getKey(), (String) me.getValue());
-                    }
-                } else if ("--version".equals(state)) {
-                        System.out.println(MessageFormat.format(RESOURCE.getString(DriverResource.VERSION),
-                                                                Driver.class.getName(), "1.31, 07/05/05"));
-                    return;
-                } else if ("--help".equals(state)) {
-                    printUsage(doPack, true, System.out);
-                    System.exit(0);
-                    return;
-                } else {
-                    break;
-                }
-            }
-        } catch (IllegalArgumentException ee) {
-                System.err.println(MessageFormat.format(RESOURCE.getString(DriverResource.BAD_ARGUMENT), ee));
-            printUsage(doPack, false, System.err);
-            System.exit(2);
-            return;
-        }
-
-        // Deal with remaining non-engine properties:
-        for (String opt : avProps.keySet()) {
-            String val = avProps.get(opt);
-            switch (opt) {
-                case "--repack":
-                    doRepack = true;
-                    break;
-                case "--no-gzip":
-                    doZip = (val == null);
-                    break;
-                case "--log-file=":
-                    logFile = val;
-                    break;
-                default:
-                    throw new InternalError(MessageFormat.format(
-                            RESOURCE.getString(DriverResource.BAD_OPTION),
-                            opt, avProps.get(opt)));
-            }
-        }
-
-        if (logFile != null && !logFile.equals("")) {
-            if (logFile.equals("-")) {
-                System.setErr(System.out);
-            } else {
-                OutputStream log = new FileOutputStream(logFile);
-                //log = new BufferedOutputStream(out);
-                System.setErr(new PrintStream(log));
-            }
-        }
-
-        boolean verbose = (engProps.get(verboseProp) != null);
-
-        String packfile = "";
-        if (!av.isEmpty())
-            packfile = av.remove(0);
-
-        String jarfile = "";
-        if (!av.isEmpty())
-            jarfile = av.remove(0);
-
-        String newfile = "";  // output JAR file if --repack
-        String bakfile = "";  // temporary backup of input JAR
-        String tmpfile = "";  // temporary file to be deleted
-        if (doRepack) {
-            // The first argument is the target JAR file.
-            // (Note:  *.pac is nonstandard, but may be necessary
-            // if a host OS truncates file extensions.)
-            if (packfile.toLowerCase().endsWith(".pack") ||
-                packfile.toLowerCase().endsWith(".pac") ||
-                packfile.toLowerCase().endsWith(".gz")) {
-                System.err.println(MessageFormat.format(
-                        RESOURCE.getString(DriverResource.BAD_REPACK_OUTPUT),
-                        packfile));
-                printUsage(doPack, false, System.err);
-                System.exit(2);
-            }
-            newfile = packfile;
-            // The optional second argument is the source JAR file.
-            if (jarfile.equals("")) {
-                // If only one file is given, it is the only JAR.
-                // It serves as both input and output.
-                jarfile = newfile;
-            }
-            tmpfile = createTempFile(newfile, ".pack").getPath();
-            packfile = tmpfile;
-            doZip = false;  // no need to zip the temporary file
-        }
-
-        if (!av.isEmpty()
-            // Accept jarfiles ending with .jar or .zip.
-            // Accept jarfile of "-" (stdout), but only if unpacking.
-            || !(jarfile.toLowerCase().endsWith(".jar")
-                 || jarfile.toLowerCase().endsWith(".zip")
-                 || (jarfile.equals("-") && !doPack))) {
-            printUsage(doPack, false, System.err);
-            System.exit(2);
-            return;
-        }
-
-        if (doRepack)
-            doPack = doUnpack = true;
-        else if (doPack)
-            doUnpack = false;
-
-        Pack200.Packer jpack = Pack200.newPacker();
-        Pack200.Unpacker junpack = Pack200.newUnpacker();
-
-        jpack.properties().putAll(engProps);
-        junpack.properties().putAll(engProps);
-        if (doRepack && newfile.equals(jarfile)) {
-            String zipc = getZipComment(jarfile);
-            if (verbose && !zipc.isEmpty())
-                System.out.println(MessageFormat.format(RESOURCE.getString(DriverResource.DETECTED_ZIP_COMMENT), zipc));
-            if (zipc.indexOf(Utils.PACK_ZIP_ARCHIVE_MARKER_COMMENT) >= 0) {
-                    System.out.println(MessageFormat.format(RESOURCE.getString(DriverResource.SKIP_FOR_REPACKED), jarfile));
-                        doPack = false;
-                        doUnpack = false;
-                        doRepack = false;
-            }
-        }
-
-        try {
-
-            if (doPack) {
-                // Mode = Pack.
-                JarFile in = new JarFile(new File(jarfile));
-                OutputStream out;
-                // Packfile must be -, *.gz, *.pack, or *.pac.
-                if (packfile.equals("-")) {
-                    out = System.out;
-                    // Send warnings, etc., to stderr instead of stdout.
-                    System.setOut(System.err);
-                } else if (doZip) {
-                    if (!packfile.endsWith(".gz")) {
-                    System.err.println(MessageFormat.format(RESOURCE.getString(DriverResource.WRITE_PACK_FILE), packfile));
-                        printUsage(doPack, false, System.err);
-                        System.exit(2);
-                    }
-                    out = new FileOutputStream(packfile);
-                    out = new BufferedOutputStream(out);
-                    out = new GZIPOutputStream(out);
-                } else {
-                    if (!packfile.toLowerCase().endsWith(".pack") &&
-                            !packfile.toLowerCase().endsWith(".pac")) {
-                        System.err.println(MessageFormat.format(RESOURCE.getString(DriverResource.WRITE_PACKGZ_FILE),packfile));
-                        printUsage(doPack, false, System.err);
-                        System.exit(2);
-                    }
-                    out = new FileOutputStream(packfile);
-                    out = new BufferedOutputStream(out);
-                }
-                jpack.pack(in, out);
-                //in.close();  // p200 closes in but not out
-                out.close();
-            }
-
-            if (doRepack && newfile.equals(jarfile)) {
-                // If the source and destination are the same,
-                // we will move the input JAR aside while regenerating it.
-                // This allows us to restore it if something goes wrong.
-                File bakf = createTempFile(jarfile, ".bak");
-                // On Windows target must be deleted see 4017593
-                bakf.delete();
-                boolean okBackup = new File(jarfile).renameTo(bakf);
-                if (!okBackup) {
-                        throw new Error(MessageFormat.format(RESOURCE.getString(DriverResource.SKIP_FOR_MOVE_FAILED),bakfile));
-                } else {
-                    // Open jarfile recovery bracket.
-                    bakfile = bakf.getPath();
-                }
-            }
-
-            if (doUnpack) {
-                // Mode = Unpack.
-                InputStream in;
-                if (packfile.equals("-"))
-                    in = System.in;
-                else
-                    in = new FileInputStream(new File(packfile));
-                BufferedInputStream inBuf = new BufferedInputStream(in);
-                in = inBuf;
-                if (Utils.isGZIPMagic(Utils.readMagic(inBuf))) {
-                    in = new GZIPInputStream(in);
-                }
-                String outfile = newfile.equals("")? jarfile: newfile;
-                OutputStream fileOut;
-                if (outfile.equals("-"))
-                    fileOut = System.out;
-                else
-                    fileOut = new FileOutputStream(outfile);
-                fileOut = new BufferedOutputStream(fileOut);
-                try (JarOutputStream out = new JarOutputStream(fileOut)) {
-                    junpack.unpack(in, out);
-                    // p200 closes in but not out
-                }
-                // At this point, we have a good jarfile (or newfile, if -r)
-            }
-
-            if (!bakfile.equals("")) {
-                        // On success, abort jarfile recovery bracket.
-                        new File(bakfile).delete();
-                        bakfile = "";
-            }
-
-        } finally {
-            // Close jarfile recovery bracket.
-            if (!bakfile.equals("")) {
-                File jarFile = new File(jarfile);
-                jarFile.delete(); // Win32 requires this, see above
-                new File(bakfile).renameTo(jarFile);
-            }
-            // In all cases, delete temporary *.pack.
-            if (!tmpfile.equals(""))
-                new File(tmpfile).delete();
-        }
-    }
-
-    private static
-    File createTempFile(String basefile, String suffix) throws IOException {
-        File base = new File(basefile);
-        String prefix = base.getName();
-        if (prefix.length() < 3)  prefix += "tmp";
-
-        File where = (base.getParentFile() == null && suffix.equals(".bak"))
-                ? new File(".").getAbsoluteFile()
-                : base.getParentFile();
-
-        Path tmpfile = (where == null)
-                ? Files.createTempFile(prefix, suffix)
-                : Files.createTempFile(where.toPath(), prefix, suffix);
-
-        return tmpfile.toFile();
-    }
-
-    private static
-    void printDeprecateWarning(boolean doPack, PrintStream out) {
-        String prog = doPack ? "pack200" : "unpack200";
-        out.println(MessageFormat.format(RESOURCE.getString(DriverResource.DEPRECATED), prog));
-    }
-
-    private static
-    void printUsage(boolean doPack, boolean full, PrintStream out) {
-        String prog = doPack ? "pack200" : "unpack200";
-        String[] packUsage = (String[])RESOURCE.getObject(DriverResource.PACK_HELP);
-        String[] unpackUsage = (String[])RESOURCE.getObject(DriverResource.UNPACK_HELP);
-        String[] usage = doPack? packUsage: unpackUsage;
-        for (int i = 0; i < usage.length; i++) {
-            out.println(usage[i]);
-            if (!full) {
-            out.println(MessageFormat.format(RESOURCE.getString(DriverResource.MORE_INFO), prog));
-                break;
-            }
-        }
-        // Print a warning at the end
-        // The full help page is long, the beginning warning could be out of sight
-        if (full && !suppressDeprecateMsg) {
-            printDeprecateWarning(doPack, out);
-        }
-    }
-
-    private static
-        String getZipComment(String jarfile) throws IOException {
-        byte[] tail = new byte[1000];
-        long filelen = new File(jarfile).length();
-        if (filelen <= 0)  return "";
-        long skiplen = Math.max(0, filelen - tail.length);
-        try (InputStream in = new FileInputStream(new File(jarfile))) {
-            in.skip(skiplen);
-            in.read(tail);
-            for (int i = tail.length-4; i >= 0; i--) {
-                if (tail[i+0] == 'P' && tail[i+1] == 'K' &&
-                    tail[i+2] ==  5  && tail[i+3] ==  6) {
-                    // Skip sig4, disks4, entries4, clen4, coff4, cmt2
-                    i += 4+4+4+4+4+2;
-                    if (i < tail.length)
-                        return new String(tail, i, tail.length-i, StandardCharsets.UTF_8);
-                    return "";
-                }
-            }
-            return "";
-        }
-    }
-
-    private static final String PACK200_OPTION_MAP =
-        (""
-         +"--repack                 $ \n  -r +>- @--repack              $ \n"
-         +"--no-gzip                $ \n  -g +>- @--no-gzip             $ \n"
-         +"--strip-debug            $ \n  -G +>- @--strip-debug         $ \n"
-         +"--no-keep-file-order     $ \n  -O +>- @--no-keep-file-order  $ \n"
-         +"--segment-limit=      *> = \n  -S +>  @--segment-limit=      = \n"
-         +"--effort=             *> = \n  -E +>  @--effort=             = \n"
-         +"--deflate-hint=       *> = \n  -H +>  @--deflate-hint=       = \n"
-         +"--modification-time=  *> = \n  -m +>  @--modification-time=  = \n"
-         +"--pass-file=        *> &\0 \n  -P +>  @--pass-file=        &\0 \n"
-         +"--unknown-attribute=  *> = \n  -U +>  @--unknown-attribute=  = \n"
-         +"--class-attribute=  *> &\0 \n  -C +>  @--class-attribute=  &\0 \n"
-         +"--field-attribute=  *> &\0 \n  -F +>  @--field-attribute=  &\0 \n"
-         +"--method-attribute= *> &\0 \n  -M +>  @--method-attribute= &\0 \n"
-         +"--code-attribute=   *> &\0 \n  -D +>  @--code-attribute=   &\0 \n"
-         +"--config-file=      *>   . \n  -f +>  @--config-file=        . \n"
-
-         // Negative options as required by CLIP:
-         +"--no-strip-debug  !--strip-debug         \n"
-         +"--gzip            !--no-gzip             \n"
-         +"--keep-file-order !--no-keep-file-order  \n"
-
-         // Non-Standard Options
-         +"--verbose                $ \n  -v +>- @--verbose             $ \n"
-         +"--quiet        !--verbose  \n  -q +>- !--verbose               \n"
-         +"--log-file=           *> = \n  -l +>  @--log-file=           = \n"
-         //+"--java-option=      *> = \n  -J +>  @--java-option=        = \n"
-         +"--version                . \n  -V +>  @--version             . \n"
-         +"--help               . \n  -? +> @--help . \n  -h +> @--help . \n"
-
-         // Termination:
-         +"--           . \n"  // end option sequence here
-         +"-   +?    >- . \n"  // report error if -XXX present; else use stdout
-         );
-    // Note: Collection options use "\0" as a delimiter between arguments.
-
-    // For Java version of unpacker (used for testing only):
-    private static final String UNPACK200_OPTION_MAP =
-        (""
-         +"--deflate-hint=       *> = \n  -H +>  @--deflate-hint=       = \n"
-         +"--verbose                $ \n  -v +>- @--verbose             $ \n"
-         +"--quiet        !--verbose  \n  -q +>- !--verbose               \n"
-         +"--remove-pack-file       $ \n  -r +>- @--remove-pack-file    $ \n"
-         +"--log-file=           *> = \n  -l +>  @--log-file=           = \n"
-         +"--config-file=        *> . \n  -f +>  @--config-file=        . \n"
-
-         // Termination:
-         +"--           . \n"  // end option sequence here
-         +"-   +?    >- . \n"  // report error if -XXX present; else use stdin
-         +"--version                . \n  -V +>  @--version             . \n"
-         +"--help               . \n  -? +> @--help . \n  -h +> @--help . \n"
-         );
-
-    private static final String[] PACK200_PROPERTY_TO_OPTION = {
-        Pack200.Packer.SEGMENT_LIMIT, "--segment-limit=",
-        Pack200.Packer.KEEP_FILE_ORDER, "--no-keep-file-order",
-        Pack200.Packer.EFFORT, "--effort=",
-        Pack200.Packer.DEFLATE_HINT, "--deflate-hint=",
-        Pack200.Packer.MODIFICATION_TIME, "--modification-time=",
-        Pack200.Packer.PASS_FILE_PFX, "--pass-file=",
-        Pack200.Packer.UNKNOWN_ATTRIBUTE, "--unknown-attribute=",
-        Pack200.Packer.CLASS_ATTRIBUTE_PFX, "--class-attribute=",
-        Pack200.Packer.FIELD_ATTRIBUTE_PFX, "--field-attribute=",
-        Pack200.Packer.METHOD_ATTRIBUTE_PFX, "--method-attribute=",
-        Pack200.Packer.CODE_ATTRIBUTE_PFX, "--code-attribute=",
-        //Pack200.Packer.PROGRESS, "--progress=",
-        Utils.DEBUG_VERBOSE, "--verbose",
-        Utils.COM_PREFIX+"strip.debug", "--strip-debug",
-    };
-
-    private static final String[] UNPACK200_PROPERTY_TO_OPTION = {
-        Pack200.Unpacker.DEFLATE_HINT, "--deflate-hint=",
-        //Pack200.Unpacker.PROGRESS, "--progress=",
-        Utils.DEBUG_VERBOSE, "--verbose",
-        Utils.UNPACK_REMOVE_PACKFILE, "--remove-pack-file",
-    };
-
-    /*-*
-     * Remove a set of command-line options from args,
-     * storing them in the map in a canonicalized form.
-     * 

- * The options string is a newline-separated series of - * option processing specifiers. - */ - private static - String parseCommandOptions(List args, - String options, - Map properties) { - //System.out.println(args+" // "+properties); - - String resultString = null; - - // Convert options string into optLines dictionary. - TreeMap optmap = new TreeMap<>(); - loadOptmap: - for (String optline : options.split("\n")) { - String[] words = optline.split("\\p{Space}+"); - if (words.length == 0) continue loadOptmap; - String opt = words[0]; - words[0] = ""; // initial word is not a spec - if (opt.isEmpty() && words.length >= 1) { - opt = words[1]; // initial "word" is empty due to leading ' ' - words[1] = ""; - } - if (opt.length() == 0) continue loadOptmap; - String[] prevWords = optmap.put(opt, words); - if (prevWords != null) - throw new RuntimeException(MessageFormat.format(RESOURCE.getString(DriverResource.DUPLICATE_OPTION), optline.trim())); - } - - // State machine for parsing a command line. - ListIterator argp = args.listIterator(); - ListIterator pbp = new ArrayList().listIterator(); - doArgs: - for (;;) { - // One trip through this loop per argument. - // Multiple trips per option only if several options per argument. - String arg; - if (pbp.hasPrevious()) { - arg = pbp.previous(); - pbp.remove(); - } else if (argp.hasNext()) { - arg = argp.next(); - } else { - // No more arguments at all. - break doArgs; - } - tryOpt: - for (int optlen = arg.length(); ; optlen--) { - // One time through this loop for each matching arg prefix. - String opt; - // Match some prefix of the argument to a key in optmap. - findOpt: - for (;;) { - opt = arg.substring(0, optlen); - if (optmap.containsKey(opt)) break findOpt; - if (optlen == 0) break tryOpt; - // Decide on a smaller prefix to search for. - SortedMap pfxmap = optmap.headMap(opt); - // pfxmap.lastKey is no shorter than any prefix in optmap. - int len = pfxmap.isEmpty() ? 0 : pfxmap.lastKey().length(); - optlen = Math.min(len, optlen - 1); - opt = arg.substring(0, optlen); - // (Note: We could cut opt down to its common prefix with - // pfxmap.lastKey, but that wouldn't save many cycles.) - } - opt = opt.intern(); - assert(arg.startsWith(opt)); - assert(opt.length() == optlen); - String val = arg.substring(optlen); // arg == opt+val - - // Execute the option processing specs for this opt. - // If no actions are taken, then look for a shorter prefix. - boolean didAction = false; - boolean isError = false; - - int pbpMark = pbp.nextIndex(); // in case of backtracking - String[] specs = optmap.get(opt); - eachSpec: - for (String spec : specs) { - if (spec.length() == 0) continue eachSpec; - if (spec.startsWith("#")) break eachSpec; - int sidx = 0; - char specop = spec.charAt(sidx++); - - // Deal with '+'/'*' prefixes (spec conditions). - boolean ok; - switch (specop) { - case '+': - // + means we want an non-empty val suffix. - ok = !val.isEmpty(); - specop = spec.charAt(sidx++); - break; - case '*': - // * means we accept empty or non-empty - ok = true; - specop = spec.charAt(sidx++); - break; - default: - // No condition prefix means we require an exact - // match, as indicated by an empty val suffix. - ok = (val.length() == 0); - break; - } - if (!ok) continue eachSpec; - - String specarg = spec.substring(sidx); - switch (specop) { - case '.': // terminate the option sequence - resultString = specarg.isEmpty() ? opt : specarg.intern(); - break doArgs; - case '?': // abort the option sequence - resultString = specarg.isEmpty() ? arg : specarg.intern(); - isError = true; - break eachSpec; - case '@': // change the effective opt name - opt = specarg.intern(); - break; - case '>': // shift remaining arg val to next arg - pbp.add(specarg + val); // push a new argument - val = ""; - break; - case '!': // negation option - String negopt = specarg.isEmpty() ? opt : specarg.intern(); - properties.remove(negopt); - properties.put(negopt, null); // leave placeholder - didAction = true; - break; - case '$': // normal "boolean" option - String boolval; - if (!specarg.isEmpty()) { - // If there is a given spec token, store it. - boolval = specarg; - } else { - String old = properties.get(opt); - if (old == null || old.length() == 0) { - boolval = "1"; - } else { - // Increment any previous value as a numeral. - boolval = ""+(1+Integer.parseInt(old)); - } - } - properties.put(opt, boolval); - didAction = true; - break; - case '=': // "string" option - case '&': // "collection" option - // Read an option. - boolean append = (specop == '&'); - String strval; - if (pbp.hasPrevious()) { - strval = pbp.previous(); - pbp.remove(); - } else if (argp.hasNext()) { - strval = argp.next(); - } else { - resultString = arg + " ?"; - isError = true; - break eachSpec; - } - if (append) { - String old = properties.get(opt); - if (old != null) { - // Append new val to old with embedded delim. - String delim = specarg; - if (delim.length() == 0) delim = " "; - strval = old + specarg + strval; - } - } - properties.put(opt, strval); - didAction = true; - break; - default: - throw new RuntimeException(MessageFormat.format(RESOURCE.getString(DriverResource.BAD_SPEC),opt, spec)); - } - } - - // Done processing specs. - if (didAction && !isError) { - continue doArgs; - } - - // The specs should have done something, but did not. - while (pbp.nextIndex() > pbpMark) { - // Remove anything pushed during these specs. - pbp.previous(); - pbp.remove(); - } - - if (isError) { - throw new IllegalArgumentException(resultString); - } - - if (optlen == 0) { - // We cannot try a shorter matching option. - break tryOpt; - } - } - - // If we come here, there was no matching option. - // So, push back the argument, and return to caller. - pbp.add(arg); - break doArgs; - } - // Report number of arguments consumed. - args.subList(0, argp.nextIndex()).clear(); - // Report any unconsumed partial argument. - while (pbp.hasPrevious()) { - args.add(0, pbp.previous()); - } - //System.out.println(args+" // "+properties+" -> "+resultString); - return resultString; - } -} diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/DriverResource.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/DriverResource.java deleted file mode 100644 index fb86eaa07..000000000 --- a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/DriverResource.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (c) 2005, 2018, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package net.fabricmc.shade.com.sun.java.util.jar.pack; - -import java.util.ListResourceBundle; - -public class DriverResource extends ListResourceBundle { - - public static final String VERSION = "VERSION"; - public static final String BAD_ARGUMENT = "BAD_ARGUMENT"; - public static final String BAD_OPTION = "BAD_OPTION"; - public static final String BAD_REPACK_OUTPUT = "BAD_REPACK_OUTPUT"; - public static final String DETECTED_ZIP_COMMENT = "DETECTED_ZIP_COMMENT"; - public static final String SKIP_FOR_REPACKED = "SKIP_FOR_REPACKED"; - public static final String WRITE_PACK_FILE = "WRITE_PACK_FILE"; - public static final String WRITE_PACKGZ_FILE = "WRITE_PACKGZ_FILE"; - public static final String SKIP_FOR_MOVE_FAILED = "SKIP_FOR_MOVE_FAILED"; - public static final String PACK_HELP = "PACK_HELP"; - public static final String UNPACK_HELP = "UNPACK_HELP"; - public static final String MORE_INFO = "MORE_INFO"; - public static final String DUPLICATE_OPTION = "DUPLICATE_OPTION"; - public static final String BAD_SPEC = "BAD_SPEC"; - public static final String DEPRECATED = "DEPRECATED"; - - /* - * The following are the output of 'pack200' and 'unpack200' commands. - * Do not translate command arguments and words with a prefix of '-' or '--'. - */ - private static final Object[][] resource = { - {VERSION, "{0} version {1}"}, // parameter 0:class name;parameter 1: version value - {BAD_ARGUMENT, "Bad argument: {0}"}, - {BAD_OPTION, "Bad option: {0}={1}"}, // parameter 0:option name;parameter 1:option value - {BAD_REPACK_OUTPUT, "Bad --repack output: {0}"}, // parameter 0:filename - {DETECTED_ZIP_COMMENT, "Detected ZIP comment: {0}"}, // parameter 0:comment - {SKIP_FOR_REPACKED, "Skipping because already repacked: {0}"}, // parameter 0:filename - {WRITE_PACK_FILE, "To write a *.pack file, specify --no-gzip: {0}"}, // parameter 0:filename - {WRITE_PACKGZ_FILE, "To write a *.pack.gz file, specify --gzip: {0}"}, // parameter 0:filename - {SKIP_FOR_MOVE_FAILED, "Skipping unpack because move failed: {0}"}, // parameter 0:filename - {PACK_HELP, new String[] { - "Usage: pack200 [-opt... | --option=value]... x.pack[.gz] y.jar", - "", - "Packing Options", - " -r, --repack repack or normalize a jar, suitable for ", - " signing with jarsigner", - " -g, --no-gzip output a plain pack file, suitable to be", - " compressed with a file compression utility", - " --gzip (default) post compress the pack output", - " with gzip", - " -G, --strip-debug remove debugging attributes (SourceFile,", - " LineNumberTable, LocalVariableTable", - " and LocalVariableTypeTable) while packing", - " -O, --no-keep-file-order do not transmit file ordering information", - " --keep-file-order (default) preserve input file ordering", - " -S{N}, --segment-limit={N} limit segment sizes (default unlimited)", - " -E{N}, --effort={N} packing effort (default N=5)", - " -H{h}, --deflate-hint={h} transmit deflate hint: true, false,", - " or keep (default)", - " -m{V}, --modification-time={V} transmit modtimes: latest or keep (default)", - " -P{F}, --pass-file={F} transmit the given input element(s) unchanged", - " -U{a}, --unknown-attribute={a} unknown attribute action: error, strip,", - " or pass (default)", - " -C{N}={L}, --class-attribute={N}={L} (user-defined attribute)", - " -F{N}={L}, --field-attribute={N}={L} (user-defined attribute)", - " -M{N}={L}, --method-attribute={N}={L} (user-defined attribute)", - " -D{N}={L}, --code-attribute={N}={L} (user-defined attribute)", - " -f{F}, --config-file={F} read file F for Pack200.Packer properties", - " -v, --verbose increase program verbosity", - " -q, --quiet set verbosity to lowest level", - " -l{F}, --log-file={F} output to the given log file, ", - " or '-' for System.out", - " -?, -h, --help print this help message", - " -V, --version print program version", - " -J{X} pass option X to underlying Java VM", - "", - "Notes:", - " The -P, -C, -F, -M, and -D options accumulate.", - " Example attribute definition: -C SourceFile=RUH .", - " Config. file properties are defined by the Pack200 API.", - " For meaning of -S, -E, -H-, -m, -U values, see Pack200 API.", - " Layout definitions (like RUH) are defined by JSR 200.", - "", - "Repacking mode updates the JAR file with a pack/unpack cycle:", - " pack200 [-r|--repack] [-opt | --option=value]... [repackedy.jar] y.jar\n", - "", - "Exit Status:", - " 0 if successful, >0 if an error occurred" - } - }, - {UNPACK_HELP, new String[] { - "Usage: unpack200 [-opt... | --option=value]... x.pack[.gz] y.jar\n", - "", - "Unpacking Options", - " -H{h}, --deflate-hint={h} override transmitted deflate hint:", - " true, false, or keep (default)", - " -r, --remove-pack-file remove input file after unpacking", - " -v, --verbose increase program verbosity", - " -q, --quiet set verbosity to lowest level", - " -l{F}, --log-file={F} output to the given log file, or", - " '-' for System.out", - " -?, -h, --help print this help message", - " -V, --version print program version", - " -J{X} pass option X to underlying Java VM" - } - }, - {MORE_INFO, "(For more information, run {0} --help .)"}, // parameter 0:command name - {DUPLICATE_OPTION, "duplicate option: {0}"}, // parameter 0:option - {BAD_SPEC, "bad spec for {0}: {1}"}, // parameter 0:option;parameter 1:specifier - {DEPRECATED, "\nWarning: The {0} tool is deprecated, and is planned for removal in a future JDK release.\n"} // parameter 0:command name - }; - - protected Object[][] getContents() { - return resource; - } -} diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/DriverResource_ja.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/DriverResource_ja.java deleted file mode 100644 index 5c0f52323..000000000 --- a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/DriverResource_ja.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (c) 2005, 2018, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package net.fabricmc.shade.com.sun.java.util.jar.pack; - -import java.util.ListResourceBundle; - -public class DriverResource_ja extends ListResourceBundle { - - public static final String VERSION = "VERSION"; - public static final String BAD_ARGUMENT = "BAD_ARGUMENT"; - public static final String BAD_OPTION = "BAD_OPTION"; - public static final String BAD_REPACK_OUTPUT = "BAD_REPACK_OUTPUT"; - public static final String DETECTED_ZIP_COMMENT = "DETECTED_ZIP_COMMENT"; - public static final String SKIP_FOR_REPACKED = "SKIP_FOR_REPACKED"; - public static final String WRITE_PACK_FILE = "WRITE_PACK_FILE"; - public static final String WRITE_PACKGZ_FILE = "WRITE_PACKGZ_FILE"; - public static final String SKIP_FOR_MOVE_FAILED = "SKIP_FOR_MOVE_FAILED"; - public static final String PACK_HELP = "PACK_HELP"; - public static final String UNPACK_HELP = "UNPACK_HELP"; - public static final String MORE_INFO = "MORE_INFO"; - public static final String DUPLICATE_OPTION = "DUPLICATE_OPTION"; - public static final String BAD_SPEC = "BAD_SPEC"; - public static final String DEPRECATED = "DEPRECATED"; - - /* - * The following are the output of 'pack200' and 'unpack200' commands. - * Do not translate command arguments and words with a prefix of '-' or '--'. - */ - private static final Object[][] resource = { - {VERSION, "{0}\u30D0\u30FC\u30B8\u30E7\u30F3{1}"}, // parameter 0:class name;parameter 1: version value - {BAD_ARGUMENT, "\u7121\u52B9\u306A\u5F15\u6570: {0}"}, - {BAD_OPTION, "\u7121\u52B9\u306A\u30AA\u30D7\u30B7\u30E7\u30F3: {0}={1}"}, // parameter 0:option name;parameter 1:option value - {BAD_REPACK_OUTPUT, "\u7121\u52B9\u306A--repack\u51FA\u529B: {0}"}, // parameter 0:filename - {DETECTED_ZIP_COMMENT, "\u691C\u51FA\u3055\u308C\u305FZIP\u30B3\u30E1\u30F3\u30C8: {0}"}, // parameter 0:comment - {SKIP_FOR_REPACKED, "\u3059\u3067\u306B\u518D\u5727\u7E2E\u3055\u308C\u3066\u3044\u308B\u305F\u3081\u30B9\u30AD\u30C3\u30D7\u3057\u3066\u3044\u307E\u3059: {0}"}, // parameter 0:filename - {WRITE_PACK_FILE, "*.pack\u30D5\u30A1\u30A4\u30EB\u3092\u66F8\u304D\u8FBC\u3080\u306B\u306F\u3001--no-gzip\u3092\u6307\u5B9A\u3057\u307E\u3059: {0}"}, // parameter 0:filename - {WRITE_PACKGZ_FILE, "*.pack.gz\u30D5\u30A1\u30A4\u30EB\u3092\u66F8\u304D\u8FBC\u3080\u306B\u306F\u3001--gzip\u3092\u6307\u5B9A\u3057\u307E\u3059: {0}"}, // parameter 0:filename - {SKIP_FOR_MOVE_FAILED, "\u79FB\u52D5\u304C\u5931\u6557\u3057\u305F\u305F\u3081\u89E3\u51CD\u3092\u30B9\u30AD\u30C3\u30D7\u3057\u3066\u3044\u307E\u3059: {0}"}, // parameter 0:filename - {PACK_HELP, new String[] { - "\u4F7F\u7528\u65B9\u6CD5: pack200 [-opt... | --option=value]... x.pack[.gz] y.jar", - "", - "\u5727\u7E2E\u30AA\u30D7\u30B7\u30E7\u30F3", - " -r\u3001--repack jar\u3092\u518D\u5727\u7E2E\u307E\u305F\u306F\u6B63\u898F\u5316\u3059\u308B\u30AA\u30D7\u30B7\u30E7\u30F3\u3067\u3001", - " jarsigner\u306B\u3088\u308B\u7F72\u540D\u306B\u9069\u3057\u307E\u3059", - " -g\u3001--no-gzip \u30D7\u30EC\u30FC\u30F3\u306Apack\u30D5\u30A1\u30A4\u30EB\u3092\u51FA\u529B\u3059\u308B\u30AA\u30D7\u30B7\u30E7\u30F3\u3067\u3001", - " \u30D5\u30A1\u30A4\u30EB\u5727\u7E2E\u30E6\u30FC\u30C6\u30A3\u30EA\u30C6\u30A3\u306B\u3088\u308B\u5727\u7E2E\u306B\u9069\u3057\u307E\u3059", - " --gzip (\u30C7\u30D5\u30A9\u30EB\u30C8) pack\u51FA\u529B\u3092\u5F8C\u51E6\u7406\u3067\u5727\u7E2E\u3057\u307E\u3059", - " (gzip\u3092\u4F7F\u7528)", - " -G\u3001--strip-debug \u5727\u7E2E\u4E2D\u306B\u30C7\u30D0\u30C3\u30B0\u5C5E\u6027(SourceFile\u3001", - " LineNumberTable\u3001LocalVariableTable", - " \u3001LocalVariableTypeTable)\u3092\u524A\u9664\u3057\u307E\u3059", - " -O\u3001--no-keep-file-order \u30D5\u30A1\u30A4\u30EB\u306E\u9806\u5E8F\u4ED8\u3051\u60C5\u5831\u3092\u8EE2\u9001\u3057\u307E\u305B\u3093", - " --keep-file-order (\u30C7\u30D5\u30A9\u30EB\u30C8)\u5165\u529B\u30D5\u30A1\u30A4\u30EB\u306E\u9806\u5E8F\u4ED8\u3051\u3092\u4FDD\u6301\u3057\u307E\u3059", - " -S{N}\u3001--segment-limit={N} \u30BB\u30B0\u30E1\u30F3\u30C8\u30FB\u30B5\u30A4\u30BA\u3092\u5236\u9650\u3057\u307E\u3059(\u30C7\u30D5\u30A9\u30EB\u30C8\u306F\u7121\u5236\u9650)", - " -E{N}\u3001--effort={N} \u5727\u7E2E\u306E\u8A66\u884C(\u30C7\u30D5\u30A9\u30EB\u30C8N=5)", - " -H{h}\u3001--deflate-hint={h} \u30C7\u30D5\u30EC\u30FC\u30C8\u30FB\u30D2\u30F3\u30C8\u3092\u8EE2\u9001\u3057\u307E\u3059: true\u3001false", - " \u307E\u305F\u306Fkeep(\u30C7\u30D5\u30A9\u30EB\u30C8)", - " -m{V}\u3001--modification-time={V} \u5909\u66F4\u6642\u9593\u3092\u8EE2\u9001\u3057\u307E\u3059: latest\u307E\u305F\u306Fkeep(\u30C7\u30D5\u30A9\u30EB\u30C8)", - " -P{F}\u3001--pass-file={F} \u6307\u5B9A\u3055\u308C\u305F\u5165\u529B\u8981\u7D20\u3092\u305D\u306E\u307E\u307E\u8EE2\u9001\u3057\u307E\u3059", - " -U{a}\u3001--unknown-attribute={a} \u4E0D\u660E\u306E\u5C5E\u6027\u30A2\u30AF\u30B7\u30E7\u30F3: error\u3001strip", - " \u307E\u305F\u306Fpass(\u30C7\u30D5\u30A9\u30EB\u30C8)", - " -C{N}={L}\u3001--class-attribute={N}={L} (\u30E6\u30FC\u30B6\u30FC\u5B9A\u7FA9\u5C5E\u6027)", - " -F{N}={L}\u3001--field-attribute={N}={L} (\u30E6\u30FC\u30B6\u30FC\u5B9A\u7FA9\u5C5E\u6027)", - " -M{N}={L}\u3001--method-attribute={N}={L} (\u30E6\u30FC\u30B6\u30FC\u5B9A\u7FA9\u5C5E\u6027)", - " -D{N}={L}\u3001--code-attribute={N}={L} (\u30E6\u30FC\u30B6\u30FC\u5B9A\u7FA9\u5C5E\u6027)", - " -f{F}\u3001--config-file={F} Pack200.Packer\u30D7\u30ED\u30D1\u30C6\u30A3\u306B\u30D5\u30A1\u30A4\u30EBF\u3092\u8AAD\u307F\u8FBC\u307F\u307E\u3059", - " -v\u3001--verbose \u30D7\u30ED\u30B0\u30E9\u30E0\u306E\u5197\u9577\u6027\u3092\u9AD8\u3081\u307E\u3059", - " -q\u3001--quiet \u5197\u9577\u6027\u3092\u6700\u4F4E\u30EC\u30D9\u30EB\u306B\u8A2D\u5B9A\u3057\u307E\u3059", - " -l{F}\u3001--log-file={F} \u6307\u5B9A\u306E\u30ED\u30B0\u30FB\u30D5\u30A1\u30A4\u30EB\u307E\u305F\u306FSystem.out ", - " ('-'\u306E\u5834\u5408)\u306B\u51FA\u529B\u3057\u307E\u3059", - " -?\u3001-h\u3001--help \u3053\u306E\u30D8\u30EB\u30D7\u30FB\u30E1\u30C3\u30BB\u30FC\u30B8\u3092\u51FA\u529B\u3057\u307E\u3059", - " -V\u3001--version \u30D7\u30ED\u30B0\u30E9\u30E0\u306E\u30D0\u30FC\u30B8\u30E7\u30F3\u3092\u51FA\u529B\u3057\u307E\u3059", - " -J{X} \u30AA\u30D7\u30B7\u30E7\u30F3X\u3092\u57FA\u790E\u3068\u306A\u308BJava VM\u306B\u6E21\u3057\u307E\u3059", - "", - "\u6CE8:", - " -P\u3001-C\u3001-F\u3001-M\u304A\u3088\u3073-D\u30AA\u30D7\u30B7\u30E7\u30F3\u306F\u7D2F\u7A4D\u3055\u308C\u307E\u3059\u3002", - " \u5C5E\u6027\u5B9A\u7FA9\u306E\u4F8B: -C SourceFile=RUH .", - " Config.\u30D5\u30A1\u30A4\u30EB\u30FB\u30D7\u30ED\u30D1\u30C6\u30A3\u306F\u3001Pack200 API\u306B\u3088\u3063\u3066\u5B9A\u7FA9\u3055\u308C\u307E\u3059\u3002", - " -S\u3001-E\u3001-H\u3001-m\u3001-U\u306E\u5024\u306E\u610F\u5473\u306F\u3001Pack200 API\u3092\u53C2\u7167\u3057\u3066\u304F\u3060\u3055\u3044\u3002", - " \u30EC\u30A4\u30A2\u30A6\u30C8\u5B9A\u7FA9(RUH\u306A\u3069)\u306FJSR 200\u306B\u3088\u3063\u3066\u5B9A\u7FA9\u3055\u308C\u307E\u3059\u3002", - "", - "\u518D\u5727\u7E2E\u30E2\u30FC\u30C9\u3067\u306F\u3001JAR\u30D5\u30A1\u30A4\u30EB\u304C\u5727\u7E2E/\u89E3\u51CD\u30B5\u30A4\u30AF\u30EB\u3067\u66F4\u65B0\u3055\u308C\u307E\u3059:", - " pack200 [-r|--repack] [-opt | --option=value]... [repackedy.jar] y.jar\n", - "", - "\u7D42\u4E86\u30B9\u30C6\u30FC\u30BF\u30B9:", - " 0 (\u6210\u529F\u3057\u305F\u5834\u5408)\u3001>0 (\u30A8\u30E9\u30FC\u304C\u767A\u751F\u3057\u305F\u5834\u5408)" - } - }, - {UNPACK_HELP, new String[] { - "\u4F7F\u7528\u65B9\u6CD5: unpack200 [-opt... | --option=value]... x.pack[.gz] y.jar\n", - "", - "\u89E3\u51CD\u30AA\u30D7\u30B7\u30E7\u30F3", - " -H{h}\u3001--deflate-hint={h} \u8EE2\u9001\u3055\u308C\u305F\u30C7\u30D5\u30EC\u30FC\u30C8\u30FB\u30D2\u30F3\u30C8\u3092\u30AA\u30FC\u30D0\u30FC\u30E9\u30A4\u30C9\u3057\u307E\u3059:", - " true\u3001false\u307E\u305F\u306Fkeep(\u30C7\u30D5\u30A9\u30EB\u30C8)", - " -r\u3001--remove-pack-file \u89E3\u51CD\u5F8C\u306B\u5165\u529B\u30D5\u30A1\u30A4\u30EB\u3092\u524A\u9664\u3057\u307E\u3059", - " -v\u3001--verbose \u30D7\u30ED\u30B0\u30E9\u30E0\u306E\u5197\u9577\u6027\u3092\u9AD8\u3081\u307E\u3059", - " -q\u3001--quiet \u5197\u9577\u6027\u3092\u6700\u4F4E\u30EC\u30D9\u30EB\u306B\u8A2D\u5B9A\u3057\u307E\u3059", - " -l{F}\u3001--log-file={F} \u6307\u5B9A\u306E\u30ED\u30B0\u30FB\u30D5\u30A1\u30A4\u30EB\u307E\u305F\u306F", - " System.out ('-'\u306E\u5834\u5408)\u306B\u51FA\u529B\u3057\u307E\u3059", - " -?\u3001-h\u3001--help \u3053\u306E\u30D8\u30EB\u30D7\u30FB\u30E1\u30C3\u30BB\u30FC\u30B8\u3092\u51FA\u529B\u3057\u307E\u3059", - " -V\u3001--version \u30D7\u30ED\u30B0\u30E9\u30E0\u306E\u30D0\u30FC\u30B8\u30E7\u30F3\u3092\u51FA\u529B\u3057\u307E\u3059", - " -J{X} \u30AA\u30D7\u30B7\u30E7\u30F3X\u3092\u57FA\u790E\u3068\u306A\u308BJava VM\u306B\u6E21\u3057\u307E\u3059" - } - }, - {MORE_INFO, "(\u8A73\u7D30\u306F\u3001{0} --help\u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002)"}, // parameter 0:command name - {DUPLICATE_OPTION, "\u91CD\u8907\u30AA\u30D7\u30B7\u30E7\u30F3: {0}"}, // parameter 0:option - {BAD_SPEC, "{0}\u306E\u7121\u52B9\u306A\u4ED5\u69D8: {1}"}, // parameter 0:option;parameter 1:specifier - {DEPRECATED, "\n\u8B66\u544A: {0}\u30C4\u30FC\u30EB\u306F\u975E\u63A8\u5968\u3067\u3042\u308A\u3001\u4ECA\u5F8C\u306EJDK\u30EA\u30EA\u30FC\u30B9\u3067\u524A\u9664\u3055\u308C\u308B\u4E88\u5B9A\u3067\u3059\u3002\n"} // parameter 0:command name - }; - - protected Object[][] getContents() { - return resource; - } -} diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/DriverResource_zh_CN.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/DriverResource_zh_CN.java deleted file mode 100644 index 96b71846d..000000000 --- a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/DriverResource_zh_CN.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (c) 2005, 2018, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package net.fabricmc.shade.com.sun.java.util.jar.pack; - -import java.util.ListResourceBundle; - -public class DriverResource_zh_CN extends ListResourceBundle { - - public static final String VERSION = "VERSION"; - public static final String BAD_ARGUMENT = "BAD_ARGUMENT"; - public static final String BAD_OPTION = "BAD_OPTION"; - public static final String BAD_REPACK_OUTPUT = "BAD_REPACK_OUTPUT"; - public static final String DETECTED_ZIP_COMMENT = "DETECTED_ZIP_COMMENT"; - public static final String SKIP_FOR_REPACKED = "SKIP_FOR_REPACKED"; - public static final String WRITE_PACK_FILE = "WRITE_PACK_FILE"; - public static final String WRITE_PACKGZ_FILE = "WRITE_PACKGZ_FILE"; - public static final String SKIP_FOR_MOVE_FAILED = "SKIP_FOR_MOVE_FAILED"; - public static final String PACK_HELP = "PACK_HELP"; - public static final String UNPACK_HELP = "UNPACK_HELP"; - public static final String MORE_INFO = "MORE_INFO"; - public static final String DUPLICATE_OPTION = "DUPLICATE_OPTION"; - public static final String BAD_SPEC = "BAD_SPEC"; - public static final String DEPRECATED = "DEPRECATED"; - - /* - * The following are the output of 'pack200' and 'unpack200' commands. - * Do not translate command arguments and words with a prefix of '-' or '--'. - */ - private static final Object[][] resource = { - {VERSION, "{0}\u7248\u672C {1}"}, // parameter 0:class name;parameter 1: version value - {BAD_ARGUMENT, "\u9519\u8BEF\u53C2\u6570: {0}"}, - {BAD_OPTION, "\u9519\u8BEF\u9009\u9879: {0}={1}"}, // parameter 0:option name;parameter 1:option value - {BAD_REPACK_OUTPUT, "--repack \u8F93\u51FA\u9519\u8BEF: {0}"}, // parameter 0:filename - {DETECTED_ZIP_COMMENT, "\u68C0\u6D4B\u5230 ZIP \u6CE8\u91CA: {0}"}, // parameter 0:comment - {SKIP_FOR_REPACKED, "\u7531\u4E8E\u5DF2\u91CD\u65B0\u6253\u5305\u800C\u8DF3\u8FC7: {0}"}, // parameter 0:filename - {WRITE_PACK_FILE, "\u8981\u5199\u5165 *.pack \u6587\u4EF6, \u8BF7\u6307\u5B9A --no-gzip: {0}"}, // parameter 0:filename - {WRITE_PACKGZ_FILE, "\u8981\u5199\u5165 *.pack.gz \u6587\u4EF6, \u8BF7\u6307\u5B9A --gzip: {0}"}, // parameter 0:filename - {SKIP_FOR_MOVE_FAILED, "\u7531\u4E8E\u79FB\u52A8\u5931\u8D25\u800C\u8DF3\u8FC7\u89E3\u5305: {0}"}, // parameter 0:filename - {PACK_HELP, new String[] { - "\u7528\u6CD5: pack200 [-opt... | --option=value]... x.pack[.gz] y.jar", - "", - "\u6253\u5305\u9009\u9879", - " -r, --repack \u518D\u6B21\u6253\u5305\u6216\u89C4\u8303\u5316 jar, \u9002\u5408\u4E8E ", - " \u4F7F\u7528 jarsigner \u8FDB\u884C\u7B7E\u540D", - " -g, --no-gzip \u8F93\u51FA\u65E0\u683C\u5F0F\u7684\u5305\u6587\u4EF6, \u9002\u5408\u4E8E", - " \u4F7F\u7528\u6587\u4EF6\u538B\u7F29\u5B9E\u7528\u7A0B\u5E8F\u8FDB\u884C\u538B\u7F29", - " --gzip (\u9ED8\u8BA4\u503C) \u4F7F\u7528 gzip \u5BF9\u5305\u8F93\u51FA\u8FDB\u884C", - " \u538B\u7F29\u540E\u5904\u7406", - " -G, --strip-debug \u6253\u5305\u65F6\u5220\u9664\u8C03\u8BD5\u5C5E\u6027 (SourceFile,", - " LineNumberTable, LocalVariableTable", - " \u548C LocalVariableTypeTable)", - " -O, --no-keep-file-order \u4E0D\u4F20\u8F93\u6587\u4EF6\u6392\u5E8F\u4FE1\u606F", - " --keep-file-order (\u9ED8\u8BA4\u503C) \u4FDD\u7559\u8F93\u5165\u6587\u4EF6\u6392\u5E8F", - " -S{N}, --segment-limit={N} \u9650\u5236\u6BB5\u5927\u5C0F (\u9ED8\u8BA4\u4E3A\u65E0\u9650\u5236)", - " -E{N}, --effort={N} \u6253\u5305\u6548\u679C (\u9ED8\u8BA4\u503C N=5)", - " -H{h}, --deflate-hint={h} \u4F20\u8F93\u538B\u7F29\u63D0\u793A: true, false", - " \u6216 keep (\u9ED8\u8BA4\u503C)", - " -m{V}, --modification-time={V} \u4F20\u8F93 modtimes: latest \u6216 keep (\u9ED8\u8BA4\u503C)", - " -P{F}, --pass-file={F} \u4F20\u8F93\u672A\u66F4\u6539\u7684\u7ED9\u5B9A\u8F93\u5165\u5143\u7D20", - " -U{a}, --unknown-attribute={a} \u672A\u77E5\u5C5E\u6027\u64CD\u4F5C: error, strip", - " \u6216 pass (\u9ED8\u8BA4\u503C)", - " -C{N}={L}, --class-attribute={N}={L} (\u7528\u6237\u5B9A\u4E49\u7684\u5C5E\u6027)", - " -F{N}={L}, --field-attribute={N}={L} (\u7528\u6237\u5B9A\u4E49\u7684\u5C5E\u6027)", - " -M{N}={L}, --method-attribute={N}={L} (\u7528\u6237\u5B9A\u4E49\u7684\u5C5E\u6027)", - " -D{N}={L}, --code-attribute={N}={L} (\u7528\u6237\u5B9A\u4E49\u7684\u5C5E\u6027)", - " -f{F}, --config-file={F} \u8BFB\u53D6\u6587\u4EF6 F \u7684 Pack200.Packer \u5C5E\u6027", - " -v, --verbose \u63D0\u9AD8\u7A0B\u5E8F\u8BE6\u7EC6\u7A0B\u5EA6", - " -q, --quiet \u5C06\u8BE6\u7EC6\u7A0B\u5EA6\u8BBE\u7F6E\u4E3A\u6700\u4F4E\u7EA7\u522B", - " -l{F}, --log-file={F} \u8F93\u51FA\u5230\u7ED9\u5B9A\u65E5\u5FD7\u6587\u4EF6, ", - " \u6216\u5BF9\u4E8E System.out \u6307\u5B9A '-'", - " -?, -h, --help \u8F93\u51FA\u6B64\u5E2E\u52A9\u6D88\u606F", - " -V, --version \u8F93\u51FA\u7A0B\u5E8F\u7248\u672C", - " -J{X} \u5C06\u9009\u9879 X \u4F20\u9012\u7ED9\u57FA\u7840 Java VM", - "", - "\u6CE8:", - " -P, -C, -F, -M \u548C -D \u9009\u9879\u7D2F\u8BA1\u3002", - " \u793A\u4F8B\u5C5E\u6027\u5B9A\u4E49: -C SourceFile=RUH\u3002", - " Config. \u6587\u4EF6\u5C5E\u6027\u7531 Pack200 API \u5B9A\u4E49\u3002", - " \u6709\u5173 -S, -E, -H-, -m, -U \u503C\u7684\u542B\u4E49, \u8BF7\u53C2\u9605 Pack200 API\u3002", - " \u5E03\u5C40\u5B9A\u4E49 (\u4F8B\u5982 RUH) \u7531 JSR 200 \u5B9A\u4E49\u3002", - "", - "\u91CD\u65B0\u6253\u5305\u6A21\u5F0F\u901A\u8FC7\u6253\u5305/\u89E3\u5305\u5468\u671F\u66F4\u65B0 JAR \u6587\u4EF6:", - " pack200 [-r|--repack] [-opt | --option=value]... [repackedy.jar] y.jar\n", - "", - "\u9000\u51FA\u72B6\u6001:", - " \u5982\u679C\u6210\u529F\u5219\u4E3A 0; \u5982\u679C\u51FA\u73B0\u9519\u8BEF, \u5219\u4E3A\u5927\u4E8E 0 \u7684\u503C" - } - }, - {UNPACK_HELP, new String[] { - "\u7528\u6CD5: unpack200 [-opt... | --option=value]... x.pack[.gz] y.jar\n", - "", - "\u89E3\u5305\u9009\u9879", - " -H{h}, --deflate-hint={h} \u8986\u76D6\u5DF2\u4F20\u8F93\u7684\u538B\u7F29\u63D0\u793A:", - " true, false \u6216 keep (\u9ED8\u8BA4\u503C)", - " -r, --remove-pack-file \u89E3\u5305\u4E4B\u540E\u5220\u9664\u8F93\u5165\u6587\u4EF6", - " -v, --verbose \u63D0\u9AD8\u7A0B\u5E8F\u8BE6\u7EC6\u7A0B\u5EA6", - " -q, --quiet \u5C06\u8BE6\u7EC6\u7A0B\u5EA6\u8BBE\u7F6E\u4E3A\u6700\u4F4E\u7EA7\u522B", - " -l{F}, --log-file={F} \u8F93\u51FA\u5230\u7ED9\u5B9A\u65E5\u5FD7\u6587\u4EF6, \u6216", - " \u5BF9\u4E8E System.out \u6307\u5B9A '-'", - " -?, -h, --help \u8F93\u51FA\u6B64\u5E2E\u52A9\u6D88\u606F", - " -V, --version \u8F93\u51FA\u7A0B\u5E8F\u7248\u672C", - " -J{X} \u5C06\u9009\u9879 X \u4F20\u9012\u7ED9\u57FA\u7840 Java VM" - } - }, - {MORE_INFO, "(\u6709\u5173\u8BE6\u7EC6\u4FE1\u606F, \u8BF7\u8FD0\u884C {0} --help\u3002)"}, // parameter 0:command name - {DUPLICATE_OPTION, "\u91CD\u590D\u7684\u9009\u9879: {0}"}, // parameter 0:option - {BAD_SPEC, "{0}\u7684\u89C4\u8303\u9519\u8BEF: {1}"}, // parameter 0:option;parameter 1:specifier - {DEPRECATED, "\n\u8B66\u544A\uFF1A{0} \u5DE5\u5177\u5DF2\u8FC7\u65F6\uFF0C\u8BA1\u5212\u5728\u672A\u6765\u7684 JDK \u53D1\u884C\u7248\u4E2D\u5220\u9664\u3002\n"} // parameter 0:command name - }; - - protected Object[][] getContents() { - return resource; - } -} diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/FixedList.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/FixedList.java deleted file mode 100644 index 3e4b8e12f..000000000 --- a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/FixedList.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package net.fabricmc.shade.com.sun.java.util.jar.pack; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.ListIterator; - -/* - * @author ksrini - */ - -/* - * This class provides an ArrayList implementation which has a fixed size, - * thus all the operations which modifies the size have been rendered - * inoperative. This essentially allows us to use generified array - * lists in lieu of arrays. - */ -final class FixedList implements List { - - private final ArrayList flist; - - protected FixedList(int capacity) { - flist = new ArrayList<>(capacity); - // initialize the list to null - for (int i = 0 ; i < capacity ; i++) { - flist.add(null); - } - } - @Override - public int size() { - return flist.size(); - } - - @Override - public boolean isEmpty() { - return flist.isEmpty(); - } - - @Override - public boolean contains(Object o) { - return flist.contains(o); - } - - @Override - public Iterator iterator() { - return flist.iterator(); - } - - @Override - public Object[] toArray() { - return flist.toArray(); - } - - @Override - public T[] toArray(T[] a) { - return flist.toArray(a); - } - - @Override - public boolean add(E e) throws UnsupportedOperationException { - throw new UnsupportedOperationException("operation not permitted"); - } - - @Override - public boolean remove(Object o) throws UnsupportedOperationException { - throw new UnsupportedOperationException("operation not permitted"); - } - - @Override - public boolean containsAll(Collection c) { - return flist.containsAll(c); - } - - @Override - public boolean addAll(Collection c) throws UnsupportedOperationException { - throw new UnsupportedOperationException("operation not permitted"); - } - - @Override - public boolean addAll(int index, Collection c) throws UnsupportedOperationException { - throw new UnsupportedOperationException("operation not permitted"); - } - - @Override - public boolean removeAll(Collection c) throws UnsupportedOperationException { - throw new UnsupportedOperationException("operation not permitted"); - } - - @Override - public boolean retainAll(Collection c) throws UnsupportedOperationException { - throw new UnsupportedOperationException("operation not permitted"); - } - - @Override - public void clear() throws UnsupportedOperationException { - throw new UnsupportedOperationException("operation not permitted"); - } - - @Override - public E get(int index) { - return flist.get(index); - } - - @Override - public E set(int index, E element) { - return flist.set(index, element); - } - - @Override - public void add(int index, E element) throws UnsupportedOperationException { - throw new UnsupportedOperationException("operation not permitted"); - } - - @Override - public E remove(int index) throws UnsupportedOperationException { - throw new UnsupportedOperationException("operation not permitted"); - } - - @Override - public int indexOf(Object o) { - return flist.indexOf(o); - } - - @Override - public int lastIndexOf(Object o) { - return flist.lastIndexOf(o); - } - - @Override - public ListIterator listIterator() { - return flist.listIterator(); - } - - @Override - public ListIterator listIterator(int index) { - return flist.listIterator(index); - } - - @Override - public List subList(int fromIndex, int toIndex) { - return flist.subList(fromIndex, toIndex); - } - - @Override - public String toString() { - return "FixedList{" + "plist=" + flist + '}'; - } -} - diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Fixups.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Fixups.java deleted file mode 100644 index 726c94d9a..000000000 --- a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Fixups.java +++ /dev/null @@ -1,570 +0,0 @@ -/* - * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package net.fabricmc.shade.com.sun.java.util.jar.pack; - -import net.fabricmc.shade.com.sun.java.util.jar.pack.ConstantPool.Entry; -import java.util.AbstractCollection; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; -import java.util.Objects; - -/** - * Collection of relocatable constant pool references. - * It operates with respect to a particular byte array, - * and stores some of its state in the bytes themselves. - *

- * As a Collection, it can be iterated over, but it is not a List, - * since it does not natively support indexed access. - *

- * - * @author John Rose - */ -final class Fixups extends AbstractCollection { - byte[] bytes; // the subject of the relocations - int head; // desc locating first reloc - int tail; // desc locating last reloc - int size; // number of relocations - Entry[] entries; // [0..size-1] relocations - int[] bigDescs; // descs which cannot be stored in the bytes - - // A "desc" (descriptor) is a bit-encoded pair of a location - // and format. Every fixup occurs at a "desc". Until final - // patching, bytes addressed by descs may also be used to - // link this data structure together. If the bytes are missing, - // or if the "desc" is too large to encode in the bytes, - // it is kept in the bigDescs array. - - Fixups(byte[] bytes) { - this.bytes = bytes; - entries = new Entry[3]; - bigDescs = noBigDescs; - } - Fixups() { - // If there are no bytes, all descs are kept in bigDescs. - this((byte[])null); - } - Fixups(byte[] bytes, Collection fixups) { - this(bytes); - addAll(fixups); - } - Fixups(Collection fixups) { - this((byte[])null); - addAll(fixups); - } - - private static final int MINBIGSIZE = 1; - // cleverly share empty bigDescs: - private static final int[] noBigDescs = {MINBIGSIZE}; - - @Override - public int size() { - return size; - } - - public void trimToSize() { - if (size != entries.length) { - Entry[] oldEntries = entries; - entries = new Entry[size]; - System.arraycopy(oldEntries, 0, entries, 0, size); - } - int bigSize = bigDescs[BIGSIZE]; - if (bigSize == MINBIGSIZE) { - bigDescs = noBigDescs; - } else if (bigSize != bigDescs.length) { - int[] oldBigDescs = bigDescs; - bigDescs = new int[bigSize]; - System.arraycopy(oldBigDescs, 0, bigDescs, 0, bigSize); - } - } - - public void visitRefs(Collection refs) { - for (int i = 0; i < size; i++) { - refs.add(entries[i]); - } - } - - @Override - public void clear() { - if (bytes != null) { - // Clean the bytes: - for (Fixup fx : this) { - //System.out.println("clean "+fx); - storeIndex(fx.location(), fx.format(), 0); - } - } - size = 0; - if (bigDescs != noBigDescs) - bigDescs[BIGSIZE] = MINBIGSIZE; - // do not trim to size, however - } - - public byte[] getBytes() { - return bytes; - } - - public void setBytes(byte[] newBytes) { - if (bytes == newBytes) return; - ArrayList old = null; - assert((old = new ArrayList<>(this)) != null); - if (bytes == null || newBytes == null) { - // One or the other representations is deficient. - // Construct a checkpoint. - ArrayList save = new ArrayList<>(this); - clear(); - bytes = newBytes; - addAll(save); - } else { - // assume newBytes is some sort of bitwise copy of the old bytes - bytes = newBytes; - } - assert(old.equals(new ArrayList<>(this))); - } - - private static final int LOC_SHIFT = 1; - private static final int FMT_MASK = 0x1; - private static final byte UNUSED_BYTE = 0; - private static final byte OVERFLOW_BYTE = -1; - // fill pointer of bigDescs array is in element [0] - private static final int BIGSIZE = 0; - - // Format values: - private static final int U2_FORMAT = 0; - private static final int U1_FORMAT = 1; - - // Special values for the static methods. - private static final int SPECIAL_LOC = 0; - private static final int SPECIAL_FMT = U2_FORMAT; - - static int fmtLen(int fmt) { return 1+(fmt-U1_FORMAT)/(U2_FORMAT-U1_FORMAT); } - static int descLoc(int desc) { return desc >>> LOC_SHIFT; } - static int descFmt(int desc) { return desc & FMT_MASK; } - static int descEnd(int desc) { return descLoc(desc) + fmtLen(descFmt(desc)); } - static int makeDesc(int loc, int fmt) { - int desc = (loc << LOC_SHIFT) | fmt; - assert(descLoc(desc) == loc); - assert(descFmt(desc) == fmt); - return desc; - } - int fetchDesc(int loc, int fmt) { - byte b1 = bytes[loc]; - assert(b1 != OVERFLOW_BYTE); - int value; - if (fmt == U2_FORMAT) { - byte b2 = bytes[loc+1]; - value = ((b1 & 0xFF) << 8) + (b2 & 0xFF); - } else { - value = (b1 & 0xFF); - } - // Stored loc field is difference between its own loc and next loc. - return value + (loc << LOC_SHIFT); - } - boolean storeDesc(int loc, int fmt, int desc) { - if (bytes == null) - return false; - int value = desc - (loc << LOC_SHIFT); - byte b1, b2; - switch (fmt) { - case U2_FORMAT: - assert(bytes[loc+0] == UNUSED_BYTE); - assert(bytes[loc+1] == UNUSED_BYTE); - b1 = (byte)(value >> 8); - b2 = (byte)(value >> 0); - if (value == (value & 0xFFFF) && b1 != OVERFLOW_BYTE) { - bytes[loc+0] = b1; - bytes[loc+1] = b2; - assert(fetchDesc(loc, fmt) == desc); - return true; - } - break; - case U1_FORMAT: - assert(bytes[loc] == UNUSED_BYTE); - b1 = (byte)value; - if (value == (value & 0xFF) && b1 != OVERFLOW_BYTE) { - bytes[loc] = b1; - assert(fetchDesc(loc, fmt) == desc); - return true; - } - break; - default: assert(false); - } - // Failure. Caller must allocate a bigDesc. - bytes[loc] = OVERFLOW_BYTE; - assert(fmt==U1_FORMAT || (bytes[loc+1]=(byte)bigDescs[BIGSIZE])!=999); - return false; - } - void storeIndex(int loc, int fmt, int value) { - storeIndex(bytes, loc, fmt, value); - } - static - void storeIndex(byte[] bytes, int loc, int fmt, int value) { - switch (fmt) { - case U2_FORMAT: - assert(value == (value & 0xFFFF)) : (value); - bytes[loc+0] = (byte)(value >> 8); - bytes[loc+1] = (byte)(value >> 0); - break; - case U1_FORMAT: - assert(value == (value & 0xFF)) : (value); - bytes[loc] = (byte)value; - break; - default: assert(false); - } - } - - void addU1(int pc, Entry ref) { - add(pc, U1_FORMAT, ref); - } - - void addU2(int pc, Entry ref) { - add(pc, U2_FORMAT, ref); - } - - /** Simple and necessary tuple to present each fixup. */ - public static - class Fixup implements Comparable { - int desc; // location and format of reloc - Entry entry; // which entry to plug into the bytes - Fixup(int desc, Entry entry) { - this.desc = desc; - this.entry = entry; - } - public Fixup(int loc, int fmt, Entry entry) { - this.desc = makeDesc(loc, fmt); - this.entry = entry; - } - public int location() { return descLoc(desc); } - public int format() { return descFmt(desc); } - public Entry entry() { return entry; } - @Override - public int compareTo(Fixup that) { - // Ordering depends only on location. - return this.location() - that.location(); - } - @Override - public boolean equals(Object x) { - if (!(x instanceof Fixup)) return false; - Fixup that = (Fixup) x; - return this.desc == that.desc && this.entry == that.entry; - } - @Override - public int hashCode() { - int hash = 7; - hash = 59 * hash + this.desc; - hash = 59 * hash + Objects.hashCode(this.entry); - return hash; - } - @Override - public String toString() { - return "@"+location()+(format()==U1_FORMAT?".1":"")+"="+entry; - } - } - - private - class Itr implements Iterator { - int index = 0; // index into entries - int bigIndex = BIGSIZE+1; // index into bigDescs - int next = head; // desc pointing to next fixup - @Override - public boolean hasNext() { return index < size; } - @Override - public void remove() { throw new UnsupportedOperationException(); } - @Override - public Fixup next() { - int thisIndex = index; - return new Fixup(nextDesc(), entries[thisIndex]); - } - int nextDesc() { - index++; - int thisDesc = next; - if (index < size) { - // Fetch next desc eagerly, in case this fixup gets finalized. - int loc = descLoc(thisDesc); - int fmt = descFmt(thisDesc); - if (bytes != null && bytes[loc] != OVERFLOW_BYTE) { - next = fetchDesc(loc, fmt); - } else { - // The unused extra byte is "asserted" to be equal to BI. - // This helps keep the overflow descs in sync. - assert(fmt==U1_FORMAT || bytes == null || bytes[loc+1]==(byte)bigIndex); - next = bigDescs[bigIndex++]; - } - } - return thisDesc; - } - } - - @Override - public Iterator iterator() { - return new Itr(); - } - public void add(int location, int format, Entry entry) { - addDesc(makeDesc(location, format), entry); - } - @Override - public boolean add(Fixup f) { - addDesc(f.desc, f.entry); - return true; - } - - @Override - public boolean addAll(Collection c) { - if (c instanceof Fixups) { - // Use knowledge of Itr structure to avoid building little structs. - Fixups that = (Fixups) c; - if (that.size == 0) return false; - if (this.size == 0 && entries.length < that.size) - growEntries(that.size); // presize exactly - Entry[] thatEntries = that.entries; - for (Itr i = that.new Itr(); i.hasNext(); ) { - int ni = i.index; - addDesc(i.nextDesc(), thatEntries[ni]); - } - return true; - } else { - return super.addAll(c); - } - } - // Here is how things get added: - private void addDesc(int thisDesc, Entry entry) { - if (entries.length == size) - growEntries(size * 2); - entries[size] = entry; - if (size == 0) { - head = tail = thisDesc; - } else { - int prevDesc = tail; - // Store new desc in previous tail. - int prevLoc = descLoc(prevDesc); - int prevFmt = descFmt(prevDesc); - int prevLen = fmtLen(prevFmt); - int thisLoc = descLoc(thisDesc); - // The collection must go in ascending order, and not overlap. - if (thisLoc < prevLoc + prevLen) - badOverlap(thisLoc); - tail = thisDesc; - if (!storeDesc(prevLoc, prevFmt, thisDesc)) { - // overflow - int bigSize = bigDescs[BIGSIZE]; - if (bigDescs.length == bigSize) - growBigDescs(); - //System.out.println("bigDescs["+bigSize+"] = "+thisDesc); - bigDescs[bigSize++] = thisDesc; - bigDescs[BIGSIZE] = bigSize; - } - } - size += 1; - } - private void badOverlap(int thisLoc) { - throw new IllegalArgumentException("locs must be ascending and must not overlap: "+thisLoc+" >> "+this); - } - - private void growEntries(int newSize) { - Entry[] oldEntries = entries; - entries = new Entry[Math.max(3, newSize)]; - System.arraycopy(oldEntries, 0, entries, 0, oldEntries.length); - } - private void growBigDescs() { - int[] oldBigDescs = bigDescs; - bigDescs = new int[oldBigDescs.length * 2]; - System.arraycopy(oldBigDescs, 0, bigDescs, 0, oldBigDescs.length); - } - - /// Static methods that optimize the use of this class. - static Object addRefWithBytes(Object f, byte[] bytes, Entry e) { - return add(f, bytes, 0, U2_FORMAT, e); - } - static Object addRefWithLoc(Object f, int loc, Entry entry) { - return add(f, null, loc, U2_FORMAT, entry); - } - private static - Object add(Object prevFixups, - byte[] bytes, int loc, int fmt, - Entry e) { - Fixups f; - if (prevFixups == null) { - if (loc == SPECIAL_LOC && fmt == SPECIAL_FMT) { - // Special convention: If the attribute has a - // U2 relocation at position zero, store the Entry - // rather than building a Fixups structure. - return e; - } - f = new Fixups(bytes); - } else if (!(prevFixups instanceof Fixups)) { - // Recognize the special convention: - Entry firstEntry = (Entry) prevFixups; - f = new Fixups(bytes); - f.add(SPECIAL_LOC, SPECIAL_FMT, firstEntry); - } else { - f = (Fixups) prevFixups; - assert(f.bytes == bytes); - } - f.add(loc, fmt, e); - return f; - } - - public static - void setBytes(Object fixups, byte[] bytes) { - if (fixups instanceof Fixups) { - Fixups f = (Fixups) fixups; - f.setBytes(bytes); - } - } - - public static - Object trimToSize(Object fixups) { - if (fixups instanceof Fixups) { - Fixups f = (Fixups) fixups; - f.trimToSize(); - if (f.size() == 0) - fixups = null; - } - return fixups; - } - - // Iterate over all the references in this set of fixups. - public static - void visitRefs(Object fixups, Collection refs) { - if (fixups == null) { - } else if (!(fixups instanceof Fixups)) { - // Special convention; see above. - refs.add((Entry) fixups); - } else { - Fixups f = (Fixups) fixups; - f.visitRefs(refs); - } - } - - // Clear out this set of fixups by replacing each reference - // by a hardcoded coding of its reference, drawn from ix. - public static - void finishRefs(Object fixups, byte[] bytes, ConstantPool.Index ix) { - if (fixups == null) - return; - if (!(fixups instanceof Fixups)) { - // Special convention; see above. - int index = ix.indexOf((Entry) fixups); - storeIndex(bytes, SPECIAL_LOC, SPECIAL_FMT, index); - return; - } - Fixups f = (Fixups) fixups; - assert(f.bytes == bytes); - f.finishRefs(ix); - } - - void finishRefs(ConstantPool.Index ix) { - if (isEmpty()) - return; - for (Fixup fx : this) { - int index = ix.indexOf(fx.entry); - //System.out.println("finish "+fx+" = "+index); - // Note that the iterator has already fetched the - // bytes we are about to overwrite. - storeIndex(fx.location(), fx.format(), index); - } - // Further iterations should do nothing: - bytes = null; // do not clean them - clear(); - } - -/* - /// Testing. - public static void main(String[] av) { - byte[] bytes = new byte[1 << 20]; - ConstantPool cp = new ConstantPool(); - Fixups f = new Fixups(bytes); - boolean isU1 = false; - int span = 3; - int nextLoc = 0; - int[] locs = new int[100]; - final int[] indexes = new int[100]; - int iptr = 1; - for (int loc = 0; loc < bytes.length; loc++) { - if (loc == nextLoc && loc+1 < bytes.length) { - int fmt = (isU1 ? U1_FORMAT : U2_FORMAT); - Entry e = ConstantPool.getUtf8Entry("L"+loc); - f.add(loc, fmt, e); - isU1 ^= true; - if (iptr < 10) { - // Make it close in. - nextLoc += fmtLen(fmt) + (iptr < 5 ? 0 : 1); - } else { - nextLoc += span; - span = (int)(span * 1.77); - } - // Here are the bytes that would have gone here: - locs[iptr] = loc; - if (fmt == U1_FORMAT) { - indexes[iptr++] = (loc & 0xFF); - } else { - indexes[iptr++] = ((loc & 0xFF) << 8) | ((loc+1) & 0xFF); - ++loc; // skip a byte - } - continue; - } - bytes[loc] = (byte)loc; - } - System.out.println("size="+f.size() - +" overflow="+(f.bigDescs[BIGSIZE]-1)); - System.out.println("Fixups: "+f); - // Test collection contents. - assert(iptr == 1+f.size()); - List l = new ArrayList(f); - Collections.sort(l); // should not change the order - if (!l.equals(new ArrayList(f))) System.out.println("** disordered"); - f.setBytes(null); - if (!l.equals(new ArrayList(f))) System.out.println("** bad set 1"); - f.setBytes(bytes); - if (!l.equals(new ArrayList(f))) System.out.println("** bad set 2"); - Fixups f3 = new Fixups(f); - if (!l.equals(new ArrayList(f3))) System.out.println("** bad set 3"); - Iterator fi = f.iterator(); - for (int i = 1; i < iptr; i++) { - Fixup fx = (Fixup) fi.next(); - if (fx.location() != locs[i]) { - System.out.println("** "+fx+" != "+locs[i]); - } - if (fx.format() == U1_FORMAT) - System.out.println(fx+" -> "+bytes[locs[i]]); - else - System.out.println(fx+" -> "+bytes[locs[i]]+" "+bytes[locs[i]+1]); - } - assert(!fi.hasNext()); - indexes[0] = 1; // like iptr - Index ix = new Index("ix") { - public int indexOf(Entry e) { - return indexes[indexes[0]++]; - } - }; - f.finishRefs(ix); - for (int loc = 0; loc < bytes.length; loc++) { - if (bytes[loc] != (byte)loc) { - System.out.println("** ["+loc+"] = "+bytes[loc]+" != "+(byte)loc); - } - } - } -//*/ -} diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Histogram.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Histogram.java deleted file mode 100644 index f66433d73..000000000 --- a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Histogram.java +++ /dev/null @@ -1,818 +0,0 @@ -/* - * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package net.fabricmc.shade.com.sun.java.util.jar.pack; - -import java.io.IOException; -import java.io.InputStream; -import java.io.PrintStream; -import java.util.Arrays; - -/** - * Histogram derived from an integer array of events (int[]). - * @author John Rose - */ -final class Histogram { - // Compact histogram representation: 4 bytes per distinct value, - // plus 5 words per distinct count. - protected final int[][] matrix; // multi-row matrix {{counti,valueij...}} - protected final int totalWeight; // sum of all counts - - // These are created eagerly also, since that saves work. - // They cost another 8 bytes per distinct value. - protected final int[] values; // unique values, sorted by value - protected final int[] counts; // counts, same order as values - - private static final long LOW32 = (long)-1 >>> 32; - - /** Build a histogram given a sequence of values. - * To save work, the input should be sorted, but need not be. - */ - public - Histogram(int[] valueSequence) { - long[] hist2col = computeHistogram2Col(maybeSort(valueSequence)); - int[][] table = makeTable(hist2col); - values = table[0]; - counts = table[1]; - this.matrix = makeMatrix(hist2col); - this.totalWeight = valueSequence.length; - assert(assertWellFormed(valueSequence)); - } - public - Histogram(int[] valueSequence, int start, int end) { - this(sortedSlice(valueSequence, start, end)); - } - - /** Build a histogram given a compact matrix of counts and values. */ - public - Histogram(int[][] matrix) { - // sort the rows - matrix = normalizeMatrix(matrix); // clone and sort - this.matrix = matrix; - int length = 0; - int weight = 0; - for (int i = 0; i < matrix.length; i++) { - int rowLength = matrix[i].length-1; - length += rowLength; - weight += matrix[i][0] * rowLength; - } - this.totalWeight = weight; - long[] hist2col = new long[length]; - int fillp = 0; - for (int i = 0; i < matrix.length; i++) { - for (int j = 1; j < matrix[i].length; j++) { - // sort key is value, so put it in the high 32! - hist2col[fillp++] = ((long) matrix[i][j] << 32) - | (LOW32 & matrix[i][0]); - } - } - assert(fillp == hist2col.length); - Arrays.sort(hist2col); - int[][] table = makeTable(hist2col); - values = table[1]; //backwards - counts = table[0]; //backwards - assert(assertWellFormed(null)); - } - - /** Histogram of int values, reported compactly as a ragged matrix, - * indexed by descending frequency rank. - *

- * Format of matrix: - * Each row in the matrix begins with an occurrence count, - * and continues with all int values that occur at that frequency. - *

-     *  int[][] matrix = {
-     *    { count1, value11, value12, value13, ...  },
-     *    { count2, value21, value22, ... },
-     *    ...
-     *  }
-     *  
- * The first column of the matrix { count1, count2, ... } - * is sorted in descending order, and contains no duplicates. - * Each row of the matrix (apart from its first element) - * is sorted in ascending order, and contains no duplicates. - * That is, each sequence { valuei1, valuei2, ... } is sorted. - */ - public - int[][] getMatrix() { return matrix; } - - public - int getRowCount() { return matrix.length; } - - public - int getRowFrequency(int rn) { return matrix[rn][0]; } - - public - int getRowLength(int rn) { return matrix[rn].length-1; } - - public - int getRowValue(int rn, int vn) { return matrix[rn][vn+1]; } - - public - int getRowWeight(int rn) { - return getRowFrequency(rn) * getRowLength(rn); - } - - public - int getTotalWeight() { - return totalWeight; - } - - public - int getTotalLength() { - return values.length; - } - - /** Returns an array of all values, sorted. */ - public - int[] getAllValues() { - - return values; - } - - /** Returns an array parallel with {@link #getValues}, - * with a frequency for each value. - */ - public - int[] getAllFrequencies() { - return counts; - } - - private static double log2 = Math.log(2); - - public - int getFrequency(int value) { - int pos = Arrays.binarySearch(values, value); - if (pos < 0) return 0; - assert(values[pos] == value); - return counts[pos]; - } - - public - double getBitLength(int value) { - double prob = (double) getFrequency(value) / getTotalWeight(); - return - Math.log(prob) / log2; - } - - public - double getRowBitLength(int rn) { - double prob = (double) getRowFrequency(rn) / getTotalWeight(); - return - Math.log(prob) / log2; - } - - public - interface BitMetric { - public double getBitLength(int value); - } - private final BitMetric bitMetric = new BitMetric() { - public double getBitLength(int value) { - return Histogram.this.getBitLength(value); - } - }; - public BitMetric getBitMetric() { - return bitMetric; - } - - /** bit-length is negative entropy: -H(matrix). */ - public - double getBitLength() { - double sum = 0; - for (int i = 0; i < matrix.length; i++) { - sum += getRowBitLength(i) * getRowWeight(i); - } - assert(0.1 > Math.abs(sum - getBitLength(bitMetric))); - return sum; - } - - /** bit-length in to another coding (cross-entropy) */ - public - double getBitLength(BitMetric len) { - double sum = 0; - for (int i = 0; i < matrix.length; i++) { - for (int j = 1; j < matrix[i].length; j++) { - sum += matrix[i][0] * len.getBitLength(matrix[i][j]); - } - } - return sum; - } - - private static - double round(double x, double scale) { - return Math.round(x * scale) / scale; - } - - /** Sort rows and columns. - * Merge adjacent rows with the same key element [0]. - * Make a fresh copy of all of it. - */ - public int[][] normalizeMatrix(int[][] matrix) { - long[] rowMap = new long[matrix.length]; - for (int i = 0; i < matrix.length; i++) { - if (matrix[i].length <= 1) continue; - int count = matrix[i][0]; - if (count <= 0) continue; - rowMap[i] = (long) count << 32 | i; - } - Arrays.sort(rowMap); - int[][] newMatrix = new int[matrix.length][]; - int prevCount = -1; - int fillp1 = 0; - int fillp2 = 0; - for (int i = 0; ; i++) { - int[] row; - if (i < matrix.length) { - long rowMapEntry = rowMap[rowMap.length-i-1]; - if (rowMapEntry == 0) continue; - row = matrix[(int)rowMapEntry]; - assert(rowMapEntry>>>32 == row[0]); - } else { - row = new int[]{ -1 }; // close it off - } - if (row[0] != prevCount && fillp2 > fillp1) { - // Close off previous run. - int length = 0; - for (int p = fillp1; p < fillp2; p++) { - int[] row0 = newMatrix[p]; // previously visited row - assert(row0[0] == prevCount); - length += row0.length-1; - } - int[] row1 = new int[1+length]; // cloned & consolidated row - row1[0] = prevCount; - int rfillp = 1; - for (int p = fillp1; p < fillp2; p++) { - int[] row0 = newMatrix[p]; // previously visited row - assert(row0[0] == prevCount); - System.arraycopy(row0, 1, row1, rfillp, row0.length-1); - rfillp += row0.length-1; - } - if (!isSorted(row1, 1, true)) { - Arrays.sort(row1, 1, row1.length); - int jfillp = 2; - // Detect and squeeze out duplicates. - for (int j = 2; j < row1.length; j++) { - if (row1[j] != row1[j-1]) - row1[jfillp++] = row1[j]; - } - if (jfillp < row1.length) { - // Reallocate because of lost duplicates. - int[] newRow1 = new int[jfillp]; - System.arraycopy(row1, 0, newRow1, 0, jfillp); - row1 = newRow1; - } - } - newMatrix[fillp1++] = row1; - fillp2 = fillp1; - } - if (i == matrix.length) - break; - prevCount = row[0]; - newMatrix[fillp2++] = row; - } - assert(fillp1 == fillp2); // no unfinished business - // Now drop missing rows. - matrix = newMatrix; - if (fillp1 < matrix.length) { - newMatrix = new int[fillp1][]; - System.arraycopy(matrix, 0, newMatrix, 0, fillp1); - matrix = newMatrix; - } - return matrix; - } - - public - String[] getRowTitles(String name) { - int totalUnique = getTotalLength(); - int ltotalWeight = getTotalWeight(); - String[] histTitles = new String[matrix.length]; - int cumWeight = 0; - int cumUnique = 0; - for (int i = 0; i < matrix.length; i++) { - int count = getRowFrequency(i); - int unique = getRowLength(i); - int weight = getRowWeight(i); - cumWeight += weight; - cumUnique += unique; - long wpct = ((long)cumWeight * 100 + ltotalWeight/2) / ltotalWeight; - long upct = ((long)cumUnique * 100 + totalUnique/2) / totalUnique; - double len = getRowBitLength(i); - assert(0.1 > Math.abs(len - getBitLength(matrix[i][1]))); - histTitles[i] = name+"["+i+"]" - +" len="+round(len,10) - +" ("+count+"*["+unique+"])" - +" ("+cumWeight+":"+wpct+"%)" - +" ["+cumUnique+":"+upct+"%]"; - } - return histTitles; - } - - /** Print a report of this histogram. - */ - public - void print(PrintStream out) { - print("hist", out); - } - - /** Print a report of this histogram. - */ - public - void print(String name, PrintStream out) { - print(name, getRowTitles(name), out); - } - - /** Print a report of this histogram. - */ - public - void print(String name, String[] histTitles, PrintStream out) { - int totalUnique = getTotalLength(); - int ltotalWeight = getTotalWeight(); - double tlen = getBitLength(); - double avgLen = tlen / ltotalWeight; - double avg = (double) ltotalWeight / totalUnique; - String title = (name - +" len="+round(tlen,10) - +" avgLen="+round(avgLen,10) - +" weight("+ltotalWeight+")" - +" unique["+totalUnique+"]" - +" avgWeight("+round(avg,100)+")"); - if (histTitles == null) { - out.println(title); - } else { - out.println(title+" {"); - StringBuffer buf = new StringBuffer(); - for (int i = 0; i < matrix.length; i++) { - buf.setLength(0); - buf.append(" ").append(histTitles[i]).append(" {"); - for (int j = 1; j < matrix[i].length; j++) { - buf.append(" ").append(matrix[i][j]); - } - buf.append(" }"); - out.println(buf); - } - out.println("}"); - } - } - -/* - public static - int[][] makeHistogramMatrix(int[] values) { - // Make sure they are sorted. - values = maybeSort(values); - long[] hist2col = computeHistogram2Col(values); - int[][] matrix = makeMatrix(hist2col); - return matrix; - } -*/ - - private static - int[][] makeMatrix(long[] hist2col) { - // Sort by increasing count, then by increasing value. - Arrays.sort(hist2col); - int[] counts = new int[hist2col.length]; - for (int i = 0; i < counts.length; i++) { - counts[i] = (int)( hist2col[i] >>> 32 ); - } - long[] countHist = computeHistogram2Col(counts); - int[][] matrix = new int[countHist.length][]; - int histp = 0; // cursor into hist2col (increasing count, value) - int countp = 0; // cursor into countHist (increasing count) - // Do a join between hist2col (resorted) and countHist. - for (int i = matrix.length; --i >= 0; ) { - long countAndRep = countHist[countp++]; - int count = (int) (countAndRep); // what is the value count? - int repeat = (int) (countAndRep >>> 32); // # times repeated? - int[] row = new int[1+repeat]; - row[0] = count; - for (int j = 0; j < repeat; j++) { - long countAndValue = hist2col[histp++]; - assert(countAndValue >>> 32 == count); - row[1+j] = (int) countAndValue; - } - matrix[i] = row; - } - assert(histp == hist2col.length); - return matrix; - } - - private static - int[][] makeTable(long[] hist2col) { - int[][] table = new int[2][hist2col.length]; - // Break apart the entries in hist2col. - // table[0] gets values, table[1] gets entries. - for (int i = 0; i < hist2col.length; i++) { - table[0][i] = (int)( hist2col[i] ); - table[1][i] = (int)( hist2col[i] >>> 32 ); - } - return table; - } - - /** Simple two-column histogram. Contains repeated counts. - * Assumes input is sorted. Does not sort output columns. - *

- * Format of result: - *

-     *  long[] hist = {
-     *    (count1 << 32) | (value1),
-     *    (count2 << 32) | (value2),
-     *    ...
-     *  }
-     *  
- * In addition, the sequence {valuei...} is guaranteed to be sorted. - * Note that resorting this using Arrays.sort() will reorder the - * entries by increasing count. - */ - private static - long[] computeHistogram2Col(int[] sortedValues) { - switch (sortedValues.length) { - case 0: - return new long[]{ }; - case 1: - return new long[]{ ((long)1 << 32) | (LOW32 & sortedValues[0]) }; - } - long[] hist = null; - for (boolean sizeOnly = true; ; sizeOnly = false) { - int prevIndex = -1; - int prevValue = sortedValues[0] ^ -1; // force a difference - int prevCount = 0; - for (int i = 0; i <= sortedValues.length; i++) { - int thisValue; - if (i < sortedValues.length) - thisValue = sortedValues[i]; - else - thisValue = prevValue ^ -1; // force a difference at end - if (thisValue == prevValue) { - prevCount += 1; - } else { - // Found a new value. - if (!sizeOnly && prevCount != 0) { - // Save away previous value. - hist[prevIndex] = ((long)prevCount << 32) - | (LOW32 & prevValue); - } - prevValue = thisValue; - prevCount = 1; - prevIndex += 1; - } - } - if (sizeOnly) { - // Finished the sizing pass. Allocate the histogram. - hist = new long[prevIndex]; - } else { - break; // done - } - } - return hist; - } - - /** Regroup the histogram, so that it becomes an approximate histogram - * whose rows are of the given lengths. - * If matrix rows must be split, the latter parts (larger values) - * are placed earlier in the new matrix. - * If matrix rows are joined, they are resorted into ascending order. - * In the new histogram, the counts are averaged over row entries. - */ - private static - int[][] regroupHistogram(int[][] matrix, int[] groups) { - long oldEntries = 0; - for (int i = 0; i < matrix.length; i++) { - oldEntries += matrix[i].length-1; - } - long newEntries = 0; - for (int ni = 0; ni < groups.length; ni++) { - newEntries += groups[ni]; - } - if (newEntries > oldEntries) { - int newlen = groups.length; - long ok = oldEntries; - for (int ni = 0; ni < groups.length; ni++) { - if (ok < groups[ni]) { - int[] newGroups = new int[ni+1]; - System.arraycopy(groups, 0, newGroups, 0, ni+1); - groups = newGroups; - groups[ni] = (int) ok; - ok = 0; - break; - } - ok -= groups[ni]; - } - } else { - long excess = oldEntries - newEntries; - int[] newGroups = new int[groups.length+1]; - System.arraycopy(groups, 0, newGroups, 0, groups.length); - newGroups[groups.length] = (int) excess; - groups = newGroups; - } - int[][] newMatrix = new int[groups.length][]; - // Fill pointers. - int i = 0; // into matrix - int jMin = 1; - int jMax = matrix[i].length; - for (int ni = 0; ni < groups.length; ni++) { - int groupLength = groups[ni]; - int[] group = new int[1+groupLength]; - long groupWeight = 0; // count of all in new group - newMatrix[ni] = group; - int njFill = 1; - while (njFill < group.length) { - int len = group.length - njFill; - while (jMin == jMax) { - jMin = 1; - jMax = matrix[++i].length; - } - if (len > jMax - jMin) len = jMax - jMin; - groupWeight += (long) matrix[i][0] * len; - System.arraycopy(matrix[i], jMax - len, group, njFill, len); - jMax -= len; - njFill += len; - } - Arrays.sort(group, 1, group.length); - // compute average count of new group: - group[0] = (int) ((groupWeight + groupLength/2) / groupLength); - } - assert(jMin == jMax); - assert(i == matrix.length-1); - return newMatrix; - } - - public static - Histogram makeByteHistogram(InputStream bytes) throws IOException { - byte[] buf = new byte[1<<12]; - int[] tally = new int[1<<8]; - for (int nr; (nr = bytes.read(buf)) > 0; ) { - for (int i = 0; i < nr; i++) { - tally[buf[i] & 0xFF] += 1; - } - } - // Build a matrix. - int[][] matrix = new int[1<<8][2]; - for (int i = 0; i < tally.length; i++) { - matrix[i][0] = tally[i]; - matrix[i][1] = i; - } - return new Histogram(matrix); - } - - /** Slice and sort the given input array. */ - private static - int[] sortedSlice(int[] valueSequence, int start, int end) { - if (start == 0 && end == valueSequence.length && - isSorted(valueSequence, 0, false)) { - return valueSequence; - } else { - int[] slice = new int[end-start]; - System.arraycopy(valueSequence, start, slice, 0, slice.length); - Arrays.sort(slice); - return slice; - } - } - - /** Tell if an array is sorted. */ - private static - boolean isSorted(int[] values, int from, boolean strict) { - for (int i = from+1; i < values.length; i++) { - if (strict ? !(values[i-1] < values[i]) - : !(values[i-1] <= values[i])) { - return false; // found witness to disorder - } - } - return true; // no witness => sorted - } - - /** Clone and sort the array, if not already sorted. */ - private static - int[] maybeSort(int[] values) { - if (!isSorted(values, 0, false)) { - values = values.clone(); - Arrays.sort(values); - } - return values; - } - - - /// Debug stuff follows. - - private boolean assertWellFormed(int[] valueSequence) { -/* - // Sanity check. - int weight = 0; - int vlength = 0; - for (int i = 0; i < matrix.length; i++) { - int vlengthi = (matrix[i].length-1); - int count = matrix[i][0]; - assert(vlengthi > 0); // no empty rows - assert(count > 0); // no impossible rows - vlength += vlengthi; - weight += count * vlengthi; - } - assert(isSorted(values, 0, true)); - // make sure the counts all add up - assert(totalWeight == weight); - assert(vlength == values.length); - assert(vlength == counts.length); - int weight2 = 0; - for (int i = 0; i < counts.length; i++) { - weight2 += counts[i]; - } - assert(weight2 == weight); - int[] revcol1 = new int[matrix.length]; //1st matrix colunm - for (int i = 0; i < matrix.length; i++) { - // spot checking: try a random query on each matrix row - assert(matrix[i].length > 1); - revcol1[matrix.length-i-1] = matrix[i][0]; - assert(isSorted(matrix[i], 1, true)); - int rand = (matrix[i].length+1) / 2; - int val = matrix[i][rand]; - int count = matrix[i][0]; - int pos = Arrays.binarySearch(values, val); - assert(values[pos] == val); - assert(counts[pos] == matrix[i][0]); - if (valueSequence != null) { - int count2 = 0; - for (int j = 0; j < valueSequence.length; j++) { - if (valueSequence[j] == val) count2++; - } - assert(count2 == count); - } - } - assert(isSorted(revcol1, 0, true)); -//*/ - return true; - } - -/* - public static - int[] readValuesFrom(InputStream instr) { - return readValuesFrom(new InputStreamReader(instr)); - } - public static - int[] readValuesFrom(Reader inrdr) { - inrdr = new BufferedReader(inrdr); - final StreamTokenizer in = new StreamTokenizer(inrdr); - final int TT_NOTHING = -99; - in.commentChar('#'); - return readValuesFrom(new Iterator() { - int token = TT_NOTHING; - private int getToken() { - if (token == TT_NOTHING) { - try { - token = in.nextToken(); - assert(token != TT_NOTHING); - } catch (IOException ee) { - throw new RuntimeException(ee); - } - } - return token; - } - public boolean hasNext() { - return getToken() != StreamTokenizer.TT_EOF; - } - public Object next() { - int ntok = getToken(); - token = TT_NOTHING; - switch (ntok) { - case StreamTokenizer.TT_EOF: - throw new NoSuchElementException(); - case StreamTokenizer.TT_NUMBER: - return new Integer((int) in.nval); - default: - assert(false); - return null; - } - } - public void remove() { - throw new UnsupportedOperationException(); - } - }); - } - public static - int[] readValuesFrom(Iterator iter) { - return readValuesFrom(iter, 0); - } - public static - int[] readValuesFrom(Iterator iter, int initSize) { - int[] na = new int[Math.max(10, initSize)]; - int np = 0; - while (iter.hasNext()) { - Integer val = (Integer) iter.next(); - if (np == na.length) { - int[] na2 = new int[np*2]; - System.arraycopy(na, 0, na2, 0, np); - na = na2; - } - na[np++] = val.intValue(); - } - if (np != na.length) { - int[] na2 = new int[np]; - System.arraycopy(na, 0, na2, 0, np); - na = na2; - } - return na; - } - - public static - Histogram makeByteHistogram(byte[] bytes) { - try { - return makeByteHistogram(new ByteArrayInputStream(bytes)); - } catch (IOException ee) { - throw new RuntimeException(ee); - } - } - - public static - void main(String[] av) throws IOException { - if (av.length > 0 && av[0].equals("-r")) { - int[] values = new int[Integer.parseInt(av[1])]; - int limit = values.length; - if (av.length >= 3) { - limit = (int)( limit * Double.parseDouble(av[2]) ); - } - Random rnd = new Random(); - for (int i = 0; i < values.length; i++) { - values[i] = rnd.nextInt(limit);; - } - Histogram rh = new Histogram(values); - rh.print("random", System.out); - return; - } - if (av.length > 0 && av[0].equals("-s")) { - int[] values = readValuesFrom(System.in); - Random rnd = new Random(); - for (int i = values.length; --i > 0; ) { - int j = rnd.nextInt(i+1); - if (j < i) { - int tem = values[i]; - values[i] = values[j]; - values[j] = tem; - } - } - for (int i = 0; i < values.length; i++) - System.out.println(values[i]); - return; - } - if (av.length > 0 && av[0].equals("-e")) { - // edge cases - new Histogram(new int[][] { - {1, 11, 111}, - {0, 123, 456}, - {1, 111, 1111}, - {0, 456, 123}, - {3}, - {}, - {3}, - {2, 22}, - {4} - }).print(System.out); - return; - } - if (av.length > 0 && av[0].equals("-b")) { - // edge cases - Histogram bh = makeByteHistogram(System.in); - bh.print("bytes", System.out); - return; - } - boolean regroup = false; - if (av.length > 0 && av[0].equals("-g")) { - regroup = true; - } - - int[] values = readValuesFrom(System.in); - Histogram h = new Histogram(values); - if (!regroup) - h.print(System.out); - if (regroup) { - int[] groups = new int[12]; - for (int i = 0; i < groups.length; i++) { - groups[i] = 1< 0); - assert(pc+length <= bytes.length); - // Speed hack: Instruction.next reuses self if possible. - if (reuse != null && !reuse.special) { - reuse.reset(bytes, pc, bc, w, length); - return reuse; - } - return new Instruction(bytes, pc, bc, w, length); - } - - // Return the constant pool reference type, or 0 if none. - public byte getCPTag() { - return BC_TAG[w][bc]; - } - - // Return the constant pool index, or -1 if none. - public int getCPIndex() { - int indexLoc = BC_INDEX[w][bc]; - if (indexLoc == 0) return -1; - assert(w == 0); - if (length == 2) - return getByte(bytes, pc+indexLoc); // _ldc opcode only - else - return getShort(bytes, pc+indexLoc); - } - - public void setCPIndex(int cpi) { - int indexLoc = BC_INDEX[w][bc]; - assert(indexLoc != 0); - if (length == 2) - setByte(bytes, pc+indexLoc, cpi); // _ldc opcode only - else - setShort(bytes, pc+indexLoc, cpi); - assert(getCPIndex() == cpi); - } - - public ConstantPool.Entry getCPRef(ConstantPool.Entry[] cpMap) { - int index = getCPIndex(); - return (index < 0) ? null : cpMap[index]; - } - - // Return the slot of the affected local, or -1 if none. - public int getLocalSlot() { - int slotLoc = BC_SLOT[w][bc]; - if (slotLoc == 0) return -1; - if (w == 0) - return getByte(bytes, pc+slotLoc); - else - return getShort(bytes, pc+slotLoc); - } - - // Return the target of the branch, or -1 if none. - public int getBranchLabel() { - int branchLoc = BC_BRANCH[w][bc]; - if (branchLoc == 0) return -1; - assert(w == 0); - assert(length == 3 || length == 5); - int offset; - if (length == 3) - offset = (short)getShort(bytes, pc+branchLoc); - else - offset = getInt(bytes, pc+branchLoc); - assert(offset+pc >= 0); - assert(offset+pc <= bytes.length); - return offset+pc; - } - - public void setBranchLabel(int targetPC) { - int branchLoc = BC_BRANCH[w][bc]; - assert(branchLoc != 0); - if (length == 3) - setShort(bytes, pc+branchLoc, targetPC-pc); - else - setInt(bytes, pc+branchLoc, targetPC-pc); - assert(targetPC == getBranchLabel()); - } - - // Return the trailing constant in the instruction (as a signed value). - // Return 0 if there is none. - public int getConstant() { - int conLoc = BC_CON[w][bc]; - if (conLoc == 0) return 0; - switch (length - conLoc) { - case 1: return (byte) getByte(bytes, pc+conLoc); - case 2: return (short) getShort(bytes, pc+conLoc); - } - assert(false); - return 0; - } - - public void setConstant(int con) { - int conLoc = BC_CON[w][bc]; - assert(conLoc != 0); - switch (length - conLoc) { - case 1: setByte(bytes, pc+conLoc, con); break; - case 2: setShort(bytes, pc+conLoc, con); break; - } - assert(con == getConstant()); - } - - public abstract static class Switch extends Instruction { - // Each case is a (value, label) pair, indexed 0 <= n < caseCount - public abstract int getCaseCount(); - public abstract int getCaseValue(int n); - public abstract int getCaseLabel(int n); - public abstract void setCaseCount(int caseCount); - public abstract void setCaseValue(int n, int value); - public abstract void setCaseLabel(int n, int targetPC); - protected abstract int getLength(int caseCount); - - public int getDefaultLabel() { return intAt(0)+pc; } - public void setDefaultLabel(int targetPC) { setIntAt(0, targetPC-pc); } - - protected int apc; // aligned pc (table base) - protected int intAt(int n) { return getInt(bytes, apc + n*4); } - protected void setIntAt(int n, int x) { setInt(bytes, apc + n*4, x); } - protected Switch(byte[] bytes, int pc, int bc) { - super(bytes, pc, bc, /*w*/0, /*length*/0); - this.apc = alignPC(pc+1); - this.special = true; - length = getLength(getCaseCount()); - } - public int getAlignedPC() { return apc; } - public String toString() { - String s = super.toString(); - s += " Default:"+labstr(getDefaultLabel()); - int caseCount = getCaseCount(); - for (int i = 0; i < caseCount; i++) { - s += "\n\tCase "+getCaseValue(i)+":"+labstr(getCaseLabel(i)); - } - return s; - } - public static int alignPC(int apc) { - while (apc % 4 != 0) ++apc; - return apc; - } - } - - public static class TableSwitch extends Switch { - // apc: (df, lo, hi, (hi-lo+1)*(label)) - public int getLowCase() { return intAt(1); } - public int getHighCase() { return intAt(2); } - public int getCaseCount() { return intAt(2)-intAt(1)+1; } - public int getCaseValue(int n) { return getLowCase()+n; } - public int getCaseLabel(int n) { return intAt(3+n)+pc; } - - public void setLowCase(int val) { setIntAt(1, val); } - public void setHighCase(int val) { setIntAt(2, val); } - public void setCaseLabel(int n, int tpc) { setIntAt(3+n, tpc-pc); } - public void setCaseCount(int caseCount) { - setHighCase(getLowCase() + caseCount - 1); - length = getLength(caseCount); - } - public void setCaseValue(int n, int val) { - if (n != 0) throw new UnsupportedOperationException(); - int caseCount = getCaseCount(); - setLowCase(val); - setCaseCount(caseCount); // keep invariant - } - - TableSwitch(byte[] bytes, int pc) { - super(bytes, pc, Constants._tableswitch); - } - protected int getLength(int caseCount) { - return (apc-pc) + (3 + caseCount) * 4; - } - } - - public static class LookupSwitch extends Switch { - // apc: (df, nc, nc*(case, label)) - public int getCaseCount() { return intAt(1); } - public int getCaseValue(int n) { return intAt(2+n*2+0); } - public int getCaseLabel(int n) { return intAt(2+n*2+1)+pc; } - - public void setCaseCount(int caseCount) { - setIntAt(1, caseCount); - length = getLength(caseCount); - } - public void setCaseValue(int n, int val) { setIntAt(2+n*2+0, val); } - public void setCaseLabel(int n, int tpc) { setIntAt(2+n*2+1, tpc-pc); } - - LookupSwitch(byte[] bytes, int pc) { - super(bytes, pc, Constants._lookupswitch); - } - protected int getLength(int caseCount) { - return (apc-pc) + (2 + caseCount*2) * 4; - } - } - - /** Two instructions are equal if they have the same bytes. */ - public boolean equals(Object o) { - return (o != null) && (o.getClass() == Instruction.class) - && equals((Instruction) o); - } - - public int hashCode() { - int hash = 3; - hash = 11 * hash + Arrays.hashCode(this.bytes); - hash = 11 * hash + this.pc; - hash = 11 * hash + this.bc; - hash = 11 * hash + this.w; - hash = 11 * hash + this.length; - return hash; - } - - public boolean equals(Instruction that) { - if (this.pc != that.pc) return false; - if (this.bc != that.bc) return false; - if (this.w != that.w) return false; - if (this.length != that.length) return false; - for (int i = 1; i < length; i++) { - if (this.bytes[this.pc+i] != that.bytes[that.pc+i]) - return false; - } - return true; - } - - static String labstr(int pc) { - if (pc >= 0 && pc < 100000) - return ((100000+pc)+"").substring(1); - return pc+""; - } - public String toString() { - return toString(null); - } - public String toString(ConstantPool.Entry[] cpMap) { - String s = labstr(pc) + ": "; - if (bc >= Constants._bytecode_limit) { - s += Integer.toHexString(bc); - return s; - } - if (w == 1) s += "wide "; - String bcname = (bc < BC_NAME.length)? BC_NAME[bc]: null; - if (bcname == null) { - return s+"opcode#"+bc; - } - s += bcname; - int tag = getCPTag(); - if (tag != 0) s += " "+ConstantPool.tagName(tag)+":"; - int idx = getCPIndex(); - if (idx >= 0) s += (cpMap == null) ? ""+idx : "="+cpMap[idx].stringValue(); - int slt = getLocalSlot(); - if (slt >= 0) s += " Local:"+slt; - int lab = getBranchLabel(); - if (lab >= 0) s += " To:"+labstr(lab); - int con = getConstant(); - if (con != 0) s += " Con:"+con; - return s; - } - - - //public static byte constantPoolTagFor(int bc) { return BC_TAG[0][bc]; } - - /// Fetching values from byte arrays: - - public int getIntAt(int off) { - return getInt(bytes, pc+off); - } - public int getShortAt(int off) { - return getShort(bytes, pc+off); - } - public int getByteAt(int off) { - return getByte(bytes, pc+off); - } - - - public static int getInt(byte[] bytes, int pc) { - return (getShort(bytes, pc+0) << 16) + (getShort(bytes, pc+2) << 0); - } - public static int getShort(byte[] bytes, int pc) { - return (getByte(bytes, pc+0) << 8) + (getByte(bytes, pc+1) << 0); - } - public static int getByte(byte[] bytes, int pc) { - return bytes[pc] & 0xFF; - } - - - public static void setInt(byte[] bytes, int pc, int x) { - setShort(bytes, pc+0, x >> 16); - setShort(bytes, pc+2, x >> 0); - } - public static void setShort(byte[] bytes, int pc, int x) { - setByte(bytes, pc+0, x >> 8); - setByte(bytes, pc+1, x >> 0); - } - public static void setByte(byte[] bytes, int pc, int x) { - bytes[pc] = (byte)x; - } - - // some bytecode classifiers - - - public static boolean isNonstandard(int bc) { - return BC_LENGTH[0][bc] < 0; - } - - public static int opLength(int bc) { - int l = BC_LENGTH[0][bc]; - assert(l > 0); - return l; - } - public static int opWideLength(int bc) { - int l = BC_LENGTH[1][bc]; - assert(l > 0); - return l; - } - - public static boolean isLocalSlotOp(int bc) { - return (bc < BC_SLOT[0].length && BC_SLOT[0][bc] > 0); - } - - public static boolean isBranchOp(int bc) { - return (bc < BC_BRANCH[0].length && BC_BRANCH[0][bc] > 0); - } - - public static boolean isCPRefOp(int bc) { - if (bc < BC_INDEX[0].length && BC_INDEX[0][bc] > 0) return true; - if (bc >= Constants._xldc_op && bc < Constants._xldc_limit) return true; - if (bc == Constants._invokespecial_int || bc == Constants._invokestatic_int) return true; - return false; - } - - public static byte getCPRefOpTag(int bc) { - if (bc < BC_INDEX[0].length && BC_INDEX[0][bc] > 0) return BC_TAG[0][bc]; - if (bc >= Constants._xldc_op && bc < Constants._xldc_limit) return Constants.CONSTANT_LoadableValue; - if (bc == Constants._invokestatic_int || bc == Constants._invokespecial_int) return Constants.CONSTANT_InterfaceMethodref; - return Constants.CONSTANT_None; - } - - public static boolean isFieldOp(int bc) { - return (bc >= Constants._getstatic && bc <= Constants._putfield); - } - - public static boolean isInvokeInitOp(int bc) { - return (bc >= Constants._invokeinit_op && bc < Constants._invokeinit_limit); - } - - public static boolean isSelfLinkerOp(int bc) { - return (bc >= Constants._self_linker_op && bc < Constants._self_linker_limit); - } - - /// Format definitions. - - private static final byte[][] BC_LENGTH = new byte[2][0x100]; - private static final byte[][] BC_INDEX = new byte[2][0x100]; - private static final byte[][] BC_TAG = new byte[2][0x100]; - private static final byte[][] BC_BRANCH = new byte[2][0x100]; - private static final byte[][] BC_SLOT = new byte[2][0x100]; - private static final byte[][] BC_CON = new byte[2][0x100]; - private static final String[] BC_NAME = new String[0x100]; // debug only - private static final String[][] BC_FORMAT = new String[2][Constants._bytecode_limit]; // debug only - static { - for (int i = 0; i < Constants._bytecode_limit; i++) { - BC_LENGTH[0][i] = -1; - BC_LENGTH[1][i] = -1; - } - def("b", Constants._nop, Constants._dconst_1); - def("bx", Constants._bipush); - def("bxx", Constants._sipush); - def("bk", Constants._ldc); // do not pack - def("bkk", Constants._ldc_w, Constants._ldc2_w); // do not pack - def("blwbll", Constants._iload, Constants._aload); - def("b", Constants._iload_0, Constants._saload); - def("blwbll", Constants._istore, Constants._astore); - def("b", Constants._istore_0, Constants._lxor); - def("blxwbllxx", Constants._iinc); - def("b", Constants._i2l, Constants._dcmpg); - def("boo", Constants._ifeq, Constants._jsr); // pack oo - def("blwbll", Constants._ret); - def("", Constants._tableswitch, Constants._lookupswitch); // pack all ints, omit padding - def("b", Constants._ireturn, Constants._return); - def("bkf", Constants._getstatic, Constants._putfield); // pack kf (base=Field) - def("bkm", Constants._invokevirtual, Constants._invokestatic); // pack kn (base=Method) - def("bkixx", Constants._invokeinterface); // pack ki (base=IMethod), omit xx - def("bkyxx", Constants._invokedynamic); // pack ky (base=Any), omit xx - def("bkc", Constants._new); // pack kc - def("bx", Constants._newarray); - def("bkc", Constants._anewarray); // pack kc - def("b", Constants._arraylength, Constants._athrow); - def("bkc", Constants._checkcast, Constants._instanceof); // pack kc - def("b", Constants._monitorenter, Constants._monitorexit); - def("", Constants._wide); - def("bkcx", Constants._multianewarray); // pack kc - def("boo", Constants._ifnull, Constants._ifnonnull); // pack oo - def("boooo", Constants._goto_w, Constants._jsr_w); // pack oooo - for (int i = 0; i < Constants._bytecode_limit; i++) { - //System.out.println(i+": l="+BC_LENGTH[0][i]+" i="+BC_INDEX[0][i]); - //assert(BC_LENGTH[0][i] != -1); - if (BC_LENGTH[0][i] == -1) { - continue; // unknown opcode - } - - // Have a complete mapping, to support spurious _wide prefixes. - if (BC_LENGTH[1][i] == -1) - BC_LENGTH[1][i] = (byte)(1+BC_LENGTH[0][i]); - } - - String names = - "nop aconst_null iconst_m1 iconst_0 iconst_1 iconst_2 iconst_3 iconst_4 "+ - "iconst_5 lconst_0 lconst_1 fconst_0 fconst_1 fconst_2 dconst_0 dconst_1 "+ - "bipush sipush ldc ldc_w ldc2_w iload lload fload dload aload iload_0 "+ - "iload_1 iload_2 iload_3 lload_0 lload_1 lload_2 lload_3 fload_0 fload_1 "+ - "fload_2 fload_3 dload_0 dload_1 dload_2 dload_3 aload_0 aload_1 aload_2 "+ - "aload_3 iaload laload faload daload aaload baload caload saload istore "+ - "lstore fstore dstore astore istore_0 istore_1 istore_2 istore_3 lstore_0 "+ - "lstore_1 lstore_2 lstore_3 fstore_0 fstore_1 fstore_2 fstore_3 dstore_0 "+ - "dstore_1 dstore_2 dstore_3 astore_0 astore_1 astore_2 astore_3 iastore "+ - "lastore fastore dastore aastore bastore castore sastore pop pop2 dup "+ - "dup_x1 dup_x2 dup2 dup2_x1 dup2_x2 swap iadd ladd fadd dadd isub lsub "+ - "fsub dsub imul lmul fmul dmul idiv ldiv fdiv ddiv irem lrem frem drem "+ - "ineg lneg fneg dneg ishl lshl ishr lshr iushr lushr iand land ior lor "+ - "ixor lxor iinc i2l i2f i2d l2i l2f l2d f2i f2l f2d d2i d2l d2f i2b i2c "+ - "i2s lcmp fcmpl fcmpg dcmpl dcmpg ifeq ifne iflt ifge ifgt ifle if_icmpeq "+ - "if_icmpne if_icmplt if_icmpge if_icmpgt if_icmple if_acmpeq if_acmpne "+ - "goto jsr ret tableswitch lookupswitch ireturn lreturn freturn dreturn "+ - "areturn return getstatic putstatic getfield putfield invokevirtual "+ - "invokespecial invokestatic invokeinterface invokedynamic new newarray "+ - "anewarray arraylength athrow checkcast instanceof monitorenter "+ - "monitorexit wide multianewarray ifnull ifnonnull goto_w jsr_w "; - for (int bc = 0; names.length() > 0; bc++) { - int sp = names.indexOf(' '); - BC_NAME[bc] = names.substring(0, sp); - names = names.substring(sp+1); - } - } - public static String byteName(int bc) { - String iname; - if (bc < BC_NAME.length && BC_NAME[bc] != null) { - iname = BC_NAME[bc]; - } else if (isSelfLinkerOp(bc)) { - int idx = (bc - Constants._self_linker_op); - boolean isSuper = (idx >= Constants._self_linker_super_flag); - if (isSuper) idx -= Constants._self_linker_super_flag; - boolean isAload = (idx >= Constants._self_linker_aload_flag); - if (isAload) idx -= Constants._self_linker_aload_flag; - int origBC = Constants._first_linker_op + idx; - assert(origBC >= Constants._first_linker_op && origBC <= Constants._last_linker_op); - iname = BC_NAME[origBC]; - iname += (isSuper ? "_super" : "_this"); - if (isAload) iname = "aload_0&" + iname; - iname = "*"+iname; - } else if (isInvokeInitOp(bc)) { - int idx = (bc - Constants._invokeinit_op); - switch (idx) { - case Constants._invokeinit_self_option: - iname = "*invokespecial_init_this"; break; - case Constants._invokeinit_super_option: - iname = "*invokespecial_init_super"; break; - default: - assert(idx == Constants._invokeinit_new_option); - iname = "*invokespecial_init_new"; break; - } - } else { - switch (bc) { - case Constants._ildc: iname = "*ildc"; break; - case Constants._fldc: iname = "*fldc"; break; - case Constants._ildc_w: iname = "*ildc_w"; break; - case Constants._fldc_w: iname = "*fldc_w"; break; - case Constants._dldc2_w: iname = "*dldc2_w"; break; - case Constants._cldc: iname = "*cldc"; break; - case Constants._cldc_w: iname = "*cldc_w"; break; - case Constants._qldc: iname = "*qldc"; break; - case Constants._qldc_w: iname = "*qldc_w"; break; - case Constants._byte_escape: iname = "*byte_escape"; break; - case Constants._ref_escape: iname = "*ref_escape"; break; - case Constants._end_marker: iname = "*end"; break; - default: iname = "*bc#"+bc; break; - } - } - return iname; - } - private static int BW = 4; // width of classification field - private static void def(String fmt, int bc) { - def(fmt, bc, bc); - } - private static void def(String fmt, int from_bc, int to_bc) { - String[] fmts = { fmt, null }; - if (fmt.indexOf('w') > 0) { - fmts[1] = fmt.substring(fmt.indexOf('w')); - fmts[0] = fmt.substring(0, fmt.indexOf('w')); - } - for (int w = 0; w <= 1; w++) { - fmt = fmts[w]; - if (fmt == null) continue; - int length = fmt.length(); - int index = Math.max(0, fmt.indexOf('k')); - int tag = Constants.CONSTANT_None; - int branch = Math.max(0, fmt.indexOf('o')); - int slot = Math.max(0, fmt.indexOf('l')); - int con = Math.max(0, fmt.indexOf('x')); - if (index > 0 && index+1 < length) { - switch (fmt.charAt(index+1)) { - case 'c': tag = Constants.CONSTANT_Class; break; - case 'k': tag = Constants.CONSTANT_LoadableValue; break; - case 'f': tag = Constants.CONSTANT_Fieldref; break; - case 'm': tag = Constants.CONSTANT_Methodref; break; - case 'i': tag = Constants.CONSTANT_InterfaceMethodref; break; - case 'y': tag = Constants.CONSTANT_InvokeDynamic; break; - } - assert(tag != Constants.CONSTANT_None); - } else if (index > 0 && length == 2) { - assert(from_bc == Constants._ldc); - tag = Constants.CONSTANT_LoadableValue; // _ldc opcode only - } - for (int bc = from_bc; bc <= to_bc; bc++) { - BC_FORMAT[w][bc] = fmt; - assert(BC_LENGTH[w][bc] == -1); - BC_LENGTH[w][bc] = (byte) length; - BC_INDEX[w][bc] = (byte) index; - BC_TAG[w][bc] = (byte) tag; - assert(!(index == 0 && tag != Constants.CONSTANT_None)); - BC_BRANCH[w][bc] = (byte) branch; - BC_SLOT[w][bc] = (byte) slot; - assert(branch == 0 || slot == 0); // not both branch & local - assert(branch == 0 || index == 0); // not both branch & cp - assert(slot == 0 || index == 0); // not both local & cp - BC_CON[w][bc] = (byte) con; - } - } - } - - public static void opcodeChecker(byte[] code, ConstantPool.Entry[] cpMap, - Package.Version clsVersion) throws FormatException { - Instruction i = at(code, 0); - while (i != null) { - int opcode = i.getBC(); - if (opcode < Constants._nop || opcode > Constants._jsr_w) { - String message = "illegal opcode: " + opcode + " " + i; - throw new FormatException(message); - } - ConstantPool.Entry e = i.getCPRef(cpMap); - if (e != null) { - byte tag = i.getCPTag(); - boolean match = e.tagMatches(tag); - if (!match && - (i.bc == Constants._invokespecial || i.bc == Constants._invokestatic) && - e.tagMatches(Constants.CONSTANT_InterfaceMethodref) && - clsVersion.greaterThan(Constants.JAVA7_MAX_CLASS_VERSION)) { - match = true; - } - if (!match) { - String message = "illegal reference, expected type=" - + ConstantPool.tagName(tag) + ": " - + i.toString(cpMap); - throw new FormatException(message); - } - } - i = i.next(); - } - } - static class FormatException extends IOException { - private static final long serialVersionUID = 3175572275651367015L; - - FormatException(String message) { - super(message); - } - } -} diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/NativeUnpack.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/NativeUnpack.java deleted file mode 100644 index dfff91a22..000000000 --- a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/NativeUnpack.java +++ /dev/null @@ -1,331 +0,0 @@ -/* - * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - - -package net.fabricmc.shade.com.sun.java.util.jar.pack; - -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; -import java.security.PrivilegedAction; -import java.util.jar.JarOutputStream; -import net.fabricmc.shade.java.util.jar.Pack200; -import java.util.zip.CRC32; -import java.util.zip.Deflater; -import java.util.zip.ZipEntry; -import java.util.zip.ZipOutputStream; - -@SuppressWarnings({"removal"}) -class NativeUnpack { - // Pointer to the native unpacker obj - private long unpackerPtr; - - // Input stream. - private BufferedInputStream in; - - private static synchronized native void initIDs(); - - // Starts processing at the indicated position in the buffer. - // If the buffer is null, the readInputFn callback is used to get bytes. - // Returns (s<<32|f), the number of following segments and files. - private synchronized native long start(ByteBuffer buf, long offset); - - // Returns true if there's another, and fills in the parts. - private synchronized native boolean getNextFile(Object[] parts); - - private synchronized native ByteBuffer getUnusedInput(); - - // Resets the engine and frees all resources. - // Returns total number of bytes consumed by the engine. - private synchronized native long finish(); - - // Setting state in the unpacker. - protected synchronized native boolean setOption(String opt, String value); - protected synchronized native String getOption(String opt); - - private int _verbose; - - // State for progress bar: - private long _byteCount; // bytes read in current segment - private int _segCount; // number of segs scanned - private int _fileCount; // number of files written - private long _estByteLimit; // estimate of eventual total - private int _estSegLimit; // ditto - private int _estFileLimit; // ditto - private int _prevPercent = -1; // for monotonicity - - private final CRC32 _crc32 = new CRC32(); - private byte[] _buf = new byte[1<<14]; - - private UnpackerImpl _p200; - private PropMap _props; - - static { - // If loading from stand alone build uncomment this. - // System.loadLibrary("unpack"); - java.security.AccessController.doPrivileged( - (PrivilegedAction) () -> { - System.loadLibrary("unpack"); - return null; - }); - initIDs(); - } - - NativeUnpack(UnpackerImpl p200) { - super(); - _p200 = p200; - _props = p200.props; - p200._nunp = this; - } - - // for JNI callbacks - private static Object currentInstance() { - UnpackerImpl p200 = (UnpackerImpl) Utils.getTLGlobals(); - return (p200 == null)? null: p200._nunp; - } - - private synchronized long getUnpackerPtr() { - return unpackerPtr; - } - - // Callback from the unpacker engine to get more data. - private long readInputFn(ByteBuffer pbuf, long minlen) throws IOException { - if (in == null) return 0; // nothing is readable - long maxlen = pbuf.capacity() - pbuf.position(); - assert(minlen <= maxlen); // don't talk nonsense - long numread = 0; - int steps = 0; - while (numread < minlen) { - steps++; - // read available input, up to buf.length or maxlen - int readlen = _buf.length; - if (readlen > (maxlen - numread)) - readlen = (int)(maxlen - numread); - int nr = in.read(_buf, 0, readlen); - if (nr <= 0) break; - numread += nr; - assert(numread <= maxlen); - // %%% get rid of this extra copy by using nio? - pbuf.put(_buf, 0, nr); - } - if (_verbose > 1) - Utils.log.fine("readInputFn("+minlen+","+maxlen+") => "+numread+" steps="+steps); - if (maxlen > 100) { - _estByteLimit = _byteCount + maxlen; - } else { - _estByteLimit = (_byteCount + numread) * 20; - } - _byteCount += numread; - updateProgress(); - return numread; - } - - private void updateProgress() { - // Progress is a combination of segment reading and file writing. - final double READ_WT = 0.33; - final double WRITE_WT = 0.67; - double readProgress = _segCount; - if (_estByteLimit > 0 && _byteCount > 0) - readProgress += (double)_byteCount / _estByteLimit; - double writeProgress = _fileCount; - double scaledProgress - = READ_WT * readProgress / Math.max(_estSegLimit,1) - + WRITE_WT * writeProgress / Math.max(_estFileLimit,1); - int percent = (int) Math.round(100*scaledProgress); - if (percent > 100) percent = 100; - if (percent > _prevPercent) { - _prevPercent = percent; - _props.setInteger(Pack200.Unpacker.PROGRESS, percent); - if (_verbose > 0) - Utils.log.info("progress = "+percent); - } - } - - private void copyInOption(String opt) { - String val = _props.getProperty(opt); - if (_verbose > 0) - Utils.log.info("set "+opt+"="+val); - if (val != null) { - boolean set = setOption(opt, val); - if (!set) - Utils.log.warning("Invalid option "+opt+"="+val); - } - } - - void run(InputStream inRaw, JarOutputStream jstream, - ByteBuffer presetInput) throws IOException { - BufferedInputStream in0 = new BufferedInputStream(inRaw); - this.in = in0; // for readInputFn to see - _verbose = _props.getInteger(Utils.DEBUG_VERBOSE); - // Fix for BugId: 4902477, -unpack.modification.time = 1059010598000 - // TODO eliminate and fix in unpack.cpp - - final int modtime = Pack200.Packer.KEEP.equals(_props.getProperty(Utils.UNPACK_MODIFICATION_TIME, "0")) ? - Constants.NO_MODTIME : _props.getTime(Utils.UNPACK_MODIFICATION_TIME); - - copyInOption(Utils.DEBUG_VERBOSE); - copyInOption(Pack200.Unpacker.DEFLATE_HINT); - if (modtime == Constants.NO_MODTIME) // Don't pass KEEP && NOW - copyInOption(Utils.UNPACK_MODIFICATION_TIME); - updateProgress(); // reset progress bar - for (;;) { - // Read the packed bits. - long counts = start(presetInput, 0); - _byteCount = _estByteLimit = 0; // reset partial scan counts - ++_segCount; // just finished scanning a whole segment... - int nextSeg = (int)( counts >>> 32 ); - int nextFile = (int)( counts >>> 0 ); - - // Estimate eventual total number of segments and files. - _estSegLimit = _segCount + nextSeg; - double filesAfterThisSeg = _fileCount + nextFile; - _estFileLimit = (int)( (filesAfterThisSeg * - _estSegLimit) / _segCount ); - - // Write the files. - int[] intParts = { 0,0, 0, 0 }; - // intParts = {size.hi/lo, mod, defl} - Object[] parts = { intParts, null, null, null }; - // parts = { {intParts}, name, data0/1 } - while (getNextFile(parts)) { - //BandStructure.printArrayTo(System.out, intParts, 0, parts.length); - String name = (String) parts[1]; - long size = ( (long)intParts[0] << 32) - + (((long)intParts[1] << 32) >>> 32); - - long mtime = (modtime != Constants.NO_MODTIME ) ? - modtime : intParts[2] ; - boolean deflateHint = (intParts[3] != 0); - ByteBuffer data0 = (ByteBuffer) parts[2]; - ByteBuffer data1 = (ByteBuffer) parts[3]; - writeEntry(jstream, name, mtime, size, deflateHint, - data0, data1); - ++_fileCount; - updateProgress(); - } - presetInput = getUnusedInput(); - long consumed = finish(); - if (_verbose > 0) - Utils.log.info("bytes consumed = "+consumed); - if (presetInput == null && - !Utils.isPackMagic(Utils.readMagic(in0))) { - break; - } - if (_verbose > 0 ) { - if (presetInput != null) - Utils.log.info("unused input = "+presetInput); - } - } - } - - void run(InputStream in, JarOutputStream jstream) throws IOException { - run(in, jstream, null); - } - - void run(File inFile, JarOutputStream jstream) throws IOException { - // %%% maybe memory-map the file, and pass it straight into unpacker - ByteBuffer mappedFile = null; - try (FileInputStream fis = new FileInputStream(inFile)) { - run(fis, jstream, mappedFile); - } - // Note: caller is responsible to finish with jstream. - } - - private void writeEntry(JarOutputStream j, String name, - long mtime, long lsize, boolean deflateHint, - ByteBuffer data0, ByteBuffer data1) throws IOException { - int size = (int)lsize; - if (size != lsize) - throw new IOException("file too large: "+lsize); - - CRC32 crc32 = _crc32; - - if (_verbose > 1) - Utils.log.fine("Writing entry: "+name+" size="+size - +(deflateHint?" deflated":"")); - - if (_buf.length < size) { - int newSize = size; - while (newSize < _buf.length) { - newSize <<= 1; - if (newSize <= 0) { - newSize = size; - break; - } - } - _buf = new byte[newSize]; - } - assert(_buf.length >= size); - - int fillp = 0; - if (data0 != null) { - int size0 = data0.capacity(); - data0.get(_buf, fillp, size0); - fillp += size0; - } - if (data1 != null) { - int size1 = data1.capacity(); - data1.get(_buf, fillp, size1); - fillp += size1; - } - while (fillp < size) { - // Fill in rest of data from the stream itself. - int nr = in.read(_buf, fillp, size - fillp); - if (nr <= 0) throw new IOException("EOF at end of archive"); - fillp += nr; - } - - ZipEntry z = new ZipEntry(name); - z.setTime(mtime * 1000); - - if (size == 0) { - z.setMethod(ZipOutputStream.STORED); - z.setSize(0); - z.setCrc(0); - z.setCompressedSize(0); - } else if (!deflateHint) { - z.setMethod(ZipOutputStream.STORED); - z.setSize(size); - z.setCompressedSize(size); - crc32.reset(); - crc32.update(_buf, 0, size); - z.setCrc(crc32.getValue()); - } else { - z.setMethod(Deflater.DEFLATED); - z.setSize(size); - } - - j.putNextEntry(z); - - if (size > 0) - j.write(_buf, 0, size); - - j.closeEntry(); - if (_verbose > 0) Utils.log.info("Writing " + Utils.zeString(z)); - } -} diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Package.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Package.java deleted file mode 100644 index 6e783ac5f..000000000 --- a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Package.java +++ /dev/null @@ -1,1375 +0,0 @@ -/* - * Copyright (c) 2001, 2016, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package net.fabricmc.shade.com.sun.java.util.jar.pack; - -import net.fabricmc.shade.com.sun.java.util.jar.pack.ConstantPool.ClassEntry; -import net.fabricmc.shade.com.sun.java.util.jar.pack.ConstantPool.DescriptorEntry; -import net.fabricmc.shade.com.sun.java.util.jar.pack.ConstantPool.BootstrapMethodEntry; -import net.fabricmc.shade.com.sun.java.util.jar.pack.ConstantPool.Index; -import net.fabricmc.shade.com.sun.java.util.jar.pack.ConstantPool.LiteralEntry; -import net.fabricmc.shade.com.sun.java.util.jar.pack.ConstantPool.Utf8Entry; -import net.fabricmc.shade.com.sun.java.util.jar.pack.ConstantPool.Entry; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.SequenceInputStream; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.ListIterator; -import java.util.Map; -import java.util.Set; -import java.util.jar.JarFile; - -/** - * Define the main data structure transmitted by pack/unpack. - * @author John Rose - */ -class Package { - int verbose; - { - PropMap pmap = Utils.currentPropMap(); - if (pmap != null) - verbose = pmap.getInteger(Utils.DEBUG_VERBOSE); - } - - final int magic = Constants.JAVA_PACKAGE_MAGIC; - - int default_modtime = Constants.NO_MODTIME; - int default_options = 0; // FO_DEFLATE_HINT - - Version defaultClassVersion = null; - - // These fields can be adjusted by driver properties. - final Version minClassVersion; - final Version maxClassVersion; - // null, indicates that consensus rules during package write - final Version packageVersion; - - Version observedHighestClassVersion = null; - - - // What constants are used in this unit? - ConstantPool.IndexGroup cp = new ConstantPool.IndexGroup(); - - /* - * typically used by the PackageReader to set the defaults, in which - * case we take the defaults. - */ - public Package() { - minClassVersion = Constants.JAVA_MIN_CLASS_VERSION; - maxClassVersion = Constants.JAVA_MAX_CLASS_VERSION; - packageVersion = null; - } - - - /* - * Typically used by the PackerImpl during before packing, the defaults are - * overridden by the users preferences. - */ - public Package(Version minClassVersion, Version maxClassVersion, Version packageVersion) { - // Fill in permitted range of major/minor version numbers. - this.minClassVersion = minClassVersion == null - ? Constants.JAVA_MIN_CLASS_VERSION - : minClassVersion; - this.maxClassVersion = maxClassVersion == null - ? Constants.JAVA_MAX_CLASS_VERSION - : maxClassVersion; - this.packageVersion = packageVersion; - } - - - public void reset() { - cp = new ConstantPool.IndexGroup(); - classes.clear(); - files.clear(); - BandStructure.nextSeqForDebug = 0; - observedHighestClassVersion = null; - } - - // Special empty versions of Code and InnerClasses, used for markers. - public static final Attribute.Layout attrCodeEmpty; - public static final Attribute.Layout attrBootstrapMethodsEmpty; - public static final Attribute.Layout attrInnerClassesEmpty; - public static final Attribute.Layout attrSourceFileSpecial; - public static final Map attrDefs; - static { - Map ad = new HashMap<>(3); - attrCodeEmpty = Attribute.define(ad, Constants.ATTR_CONTEXT_METHOD, - "Code", "").layout(); - attrBootstrapMethodsEmpty = Attribute.define(ad, Constants.ATTR_CONTEXT_CLASS, - "BootstrapMethods", "").layout(); - attrInnerClassesEmpty = Attribute.define(ad, Constants.ATTR_CONTEXT_CLASS, - "InnerClasses", "").layout(); - attrSourceFileSpecial = Attribute.define(ad, Constants.ATTR_CONTEXT_CLASS, - "SourceFile", "RUNH").layout(); - attrDefs = Collections.unmodifiableMap(ad); - } - - Version getDefaultClassVersion() { - return defaultClassVersion; - } - - /** Return the highest version number of all classes, - * or 0 if there are no classes. - */ - private void setHighestClassVersion() { - if (observedHighestClassVersion != null) - return; - Version res = Constants.JAVA_MIN_CLASS_VERSION; // initial low value - for (Class cls : classes) { - Version ver = cls.getVersion(); - if (res.lessThan(ver)) res = ver; - } - observedHighestClassVersion = res; - } - - Version getHighestClassVersion() { - setHighestClassVersion(); - return observedHighestClassVersion; - } - - // What Java classes are in this unit? - - ArrayList classes = new ArrayList<>(); - - public List getClasses() { - return classes; - } - - public final - class Class extends Attribute.Holder implements Comparable { - public Package getPackage() { return Package.this; } - - // Optional file characteristics and data source (a "class stub") - File file; - - // File header - int magic; - Version version; - - // Local constant pool (one-way mapping of index => package cp). - Entry[] cpMap; - - // Class header - //int flags; // in Attribute.Holder.this.flags - ClassEntry thisClass; - ClassEntry superClass; - ClassEntry[] interfaces; - - // Class parts - ArrayList fields; - ArrayList methods; - //ArrayList attributes; // in Attribute.Holder.this.attributes - // Note that InnerClasses may be collected at the package level. - ArrayList innerClasses; - ArrayList bootstrapMethods; - - Class(int flags, ClassEntry thisClass, ClassEntry superClass, ClassEntry[] interfaces) { - this.magic = Constants.JAVA_MAGIC; - this.version = defaultClassVersion; - this.flags = flags; - this.thisClass = thisClass; - this.superClass = superClass; - this.interfaces = interfaces; - - boolean added = classes.add(this); - assert(added); - } - - Class(String classFile) { - // A blank class; must be read with a ClassReader, etc. - initFile(newStub(classFile)); - } - - List getFields() { return fields == null ? noFields : fields; } - List getMethods() { return methods == null ? noMethods : methods; } - - public String getName() { - return thisClass.stringValue(); - } - - Version getVersion() { - return this.version; - } - - // Note: equals and hashCode are identity-based. - public int compareTo(Class that) { - String n0 = this.getName(); - String n1 = that.getName(); - return n0.compareTo(n1); - } - - String getObviousSourceFile() { - return Package.getObviousSourceFile(getName()); - } - - private void transformSourceFile(boolean minimize) { - // Replace "obvious" SourceFile by null. - Attribute olda = getAttribute(attrSourceFileSpecial); - if (olda == null) - return; // no SourceFile attr. - String obvious = getObviousSourceFile(); - List ref = new ArrayList<>(1); - olda.visitRefs(this, Constants.VRM_PACKAGE, ref); - Utf8Entry sfName = (Utf8Entry) ref.get(0); - Attribute a = olda; - if (sfName == null) { - if (minimize) { - // A pair of zero bytes. Cannot use predef. layout. - a = Attribute.find(Constants.ATTR_CONTEXT_CLASS, "SourceFile", "H"); - a = a.addContent(new byte[2]); - } else { - // Expand null attribute to the obvious string. - byte[] bytes = new byte[2]; - sfName = getRefString(obvious); - Object f = null; - f = Fixups.addRefWithBytes(f, bytes, sfName); - a = attrSourceFileSpecial.addContent(bytes, f); - } - } else if (obvious.equals(sfName.stringValue())) { - if (minimize) { - // Replace by an all-zero attribute. - a = attrSourceFileSpecial.addContent(new byte[2]); - } else { - assert(false); - } - } - if (a != olda) { - if (verbose > 2) - Utils.log.fine("recoding obvious SourceFile="+obvious); - List newAttrs = new ArrayList<>(getAttributes()); - int where = newAttrs.indexOf(olda); - newAttrs.set(where, a); - setAttributes(newAttrs); - } - } - - void minimizeSourceFile() { - transformSourceFile(true); - } - void expandSourceFile() { - transformSourceFile(false); - } - - protected Entry[] getCPMap() { - return cpMap; - } - - protected void setCPMap(Entry[] cpMap) { - this.cpMap = cpMap; - } - - boolean hasBootstrapMethods() { - return bootstrapMethods != null && !bootstrapMethods.isEmpty(); - } - - List getBootstrapMethods() { - return bootstrapMethods; - } - - BootstrapMethodEntry[] getBootstrapMethodMap() { - return (hasBootstrapMethods()) - ? bootstrapMethods.toArray(new BootstrapMethodEntry[bootstrapMethods.size()]) - : null; - } - - void setBootstrapMethods(Collection bsms) { - assert(bootstrapMethods == null); // do not do this twice - bootstrapMethods = new ArrayList<>(bsms); - } - - boolean hasInnerClasses() { - return innerClasses != null; - } - List getInnerClasses() { - return innerClasses; - } - - public void setInnerClasses(Collection ics) { - innerClasses = (ics == null) ? null : new ArrayList<>(ics); - // Edit the attribute list, if necessary. - Attribute a = getAttribute(attrInnerClassesEmpty); - if (innerClasses != null && a == null) - addAttribute(attrInnerClassesEmpty.canonicalInstance()); - else if (innerClasses == null && a != null) - removeAttribute(a); - } - - /** Given a global map of ICs (keyed by thisClass), - * compute the subset of its Map.values which are - * required to be present in the local InnerClasses - * attribute. Perform this calculation without - * reference to any actual InnerClasses attribute. - *

- * The order of the resulting list is consistent - * with that of Package.this.allInnerClasses. - */ - public List computeGloballyImpliedICs() { - Set cpRefs = new HashSet<>(); - { // This block temporarily displaces this.innerClasses. - ArrayList innerClassesSaved = innerClasses; - innerClasses = null; // ignore for the moment - visitRefs(Constants.VRM_CLASSIC, cpRefs); - innerClasses = innerClassesSaved; - } - ConstantPool.completeReferencesIn(cpRefs, true); - - Set icRefs = new HashSet<>(); - for (Entry e : cpRefs) { - // Restrict cpRefs to InnerClasses entries only. - if (!(e instanceof ClassEntry)) continue; - // For every IC reference, add its outers also. - while (e != null) { - InnerClass ic = getGlobalInnerClass(e); - if (ic == null) break; - if (!icRefs.add(e)) break; - e = ic.outerClass; - // If we add A$B$C to the mix, we must also add A$B. - } - } - // This loop is structured this way so as to accumulate - // entries into impliedICs in an order which reflects - // the order of allInnerClasses. - ArrayList impliedICs = new ArrayList<>(); - for (InnerClass ic : allInnerClasses) { - // This one is locally relevant if it describes - // a member of the current class, or if the current - // class uses it somehow. In the particular case - // where thisClass is an inner class, it will already - // be a member of icRefs. - if (icRefs.contains(ic.thisClass) - || ic.outerClass == this.thisClass) { - // Add every relevant class to the IC attribute: - if (verbose > 1) - Utils.log.fine("Relevant IC: "+ic); - impliedICs.add(ic); - } - } - return impliedICs; - } - - // Helper for both minimizing and expanding. - // Computes a symmetric difference. - private List computeICdiff() { - List impliedICs = computeGloballyImpliedICs(); - List actualICs = getInnerClasses(); - if (actualICs == null) - actualICs = Collections.emptyList(); - - // Symmetric difference is calculated from I, A like this: - // diff = (I+A) - (I*A) - // Note that the center C is unordered, but the result - // preserves the original ordering of I and A. - // - // Class file rules require that outers precede inners. - // So, add I before A, in case A$B$Z is local, but A$B - // is implicit. The reverse is never the case. - if (actualICs.isEmpty()) { - return impliedICs; - // Diff is I since A is empty. - } - if (impliedICs.isEmpty()) { - return actualICs; - // Diff is A since I is empty. - } - // (I*A) is non-trivial - Set center = new HashSet<>(actualICs); - center.retainAll(new HashSet<>(impliedICs)); - impliedICs.addAll(actualICs); - impliedICs.removeAll(center); - // Diff is now I^A = (I+A)-(I*A). - return impliedICs; - } - - /** When packing, anticipate the effect of expandLocalICs. - * Replace the local ICs by their symmetric difference - * with the globally implied ICs for this class; if this - * difference is empty, remove the local ICs altogether. - *

- * An empty local IC attribute is reserved to signal - * the unpacker to delete the attribute altogether, - * so a missing local IC attribute signals the unpacker - * to use the globally implied ICs changed. - */ - void minimizeLocalICs() { - List diff = computeICdiff(); - List actualICs = innerClasses; - List localICs; // will be the diff, modulo edge cases - if (diff.isEmpty()) { - // No diff, so transmit no attribute. - localICs = null; - if (actualICs != null && actualICs.isEmpty()) { - // Odd case: No implied ICs, and a zero length attr. - // Do not support it directly. - if (verbose > 0) - Utils.log.info("Warning: Dropping empty InnerClasses attribute from "+this); - } - } else if (actualICs == null) { - // No local IC attribute, even though some are implied. - // Signal with trivial attribute. - localICs = Collections.emptyList(); - } else { - // Transmit a non-empty diff, which will create - // a local ICs attribute. - localICs = diff; - } - // Reduce the set to the symmetric difference. - setInnerClasses(localICs); - if (verbose > 1 && localICs != null) - Utils.log.fine("keeping local ICs in "+this+": "+localICs); - } - - /** When unpacking, undo the effect of minimizeLocalICs. - * Must return negative if any IC tuples may have been deleted. - * Otherwise, return positive if any IC tuples were added. - */ - int expandLocalICs() { - List localICs = innerClasses; - List actualICs; - int changed; - if (localICs == null) { - // Diff was empty. (Common case.) - List impliedICs = computeGloballyImpliedICs(); - if (impliedICs.isEmpty()) { - actualICs = null; - changed = 0; - } else { - actualICs = impliedICs; - changed = 1; // added more tuples - } - } else if (localICs.isEmpty()) { - // It was a non-empty diff, but the local ICs were absent. - actualICs = null; - // [] => null, no tuple change, but attribute deletion. - changed = -1; - } else { - // Non-trivial diff was transmitted. - actualICs = computeICdiff(); - // If we only added more ICs, return +1. - changed = actualICs.containsAll(localICs)? +1: -1; - } - setInnerClasses(actualICs); - return changed; - } - - public abstract - class Member extends Attribute.Holder implements Comparable { - DescriptorEntry descriptor; - - protected Member(int flags, DescriptorEntry descriptor) { - this.flags = flags; - this.descriptor = descriptor; - } - - public Class thisClass() { return Class.this; } - - public DescriptorEntry getDescriptor() { - return descriptor; - } - public String getName() { - return descriptor.nameRef.stringValue(); - } - public String getType() { - return descriptor.typeRef.stringValue(); - } - - protected Entry[] getCPMap() { - return cpMap; - } - protected void visitRefs(int mode, Collection refs) { - if (verbose > 2) Utils.log.fine("visitRefs "+this); - // Careful: The descriptor is used by the package, - // but the classfile breaks it into component refs. - if (mode == Constants.VRM_CLASSIC) { - refs.add(descriptor.nameRef); - refs.add(descriptor.typeRef); - } else { - refs.add(descriptor); - } - // Handle attribute list: - super.visitRefs(mode, refs); - } - - public String toString() { - return Class.this + "." + descriptor.prettyString(); - } - } - - public - class Field extends Member { - // Order is significant for fields: It is visible to reflection. - int order; - - public Field(int flags, DescriptorEntry descriptor) { - super(flags, descriptor); - assert(!descriptor.isMethod()); - if (fields == null) - fields = new ArrayList<>(); - boolean added = fields.add(this); - assert(added); - order = fields.size(); - } - - public byte getLiteralTag() { - return descriptor.getLiteralTag(); - } - - public int compareTo(Member o) { - Field that = (Field)o; - return this.order - that.order; - } - } - - public - class Method extends Member { - // Code attribute is specially hardwired. - Code code; - - public Method(int flags, DescriptorEntry descriptor) { - super(flags, descriptor); - assert(descriptor.isMethod()); - if (methods == null) - methods = new ArrayList<>(); - boolean added = methods.add(this); - assert(added); - } - - public void trimToSize() { - super.trimToSize(); - if (code != null) - code.trimToSize(); - } - - public int getArgumentSize() { - int argSize = descriptor.typeRef.computeSize(true); - int thisSize = Modifier.isStatic(flags) ? 0 : 1; - return thisSize + argSize; - } - - // Sort methods in a canonical order (by type, then by name). - public int compareTo(Member o) { - Method that = (Method)o; - return this.getDescriptor().compareTo(that.getDescriptor()); - } - - public void strip(String attrName) { - if ("Code".equals(attrName)) - code = null; - if (code != null) - code.strip(attrName); - super.strip(attrName); - } - protected void visitRefs(int mode, Collection refs) { - super.visitRefs(mode, refs); - if (code != null) { - if (mode == Constants.VRM_CLASSIC) { - refs.add(getRefString("Code")); - } - code.visitRefs(mode, refs); - } - } - } - - public void trimToSize() { - super.trimToSize(); - for (int isM = 0; isM <= 1; isM++) { - ArrayList members = (isM == 0) ? fields : methods; - if (members == null) continue; - members.trimToSize(); - for (Member m : members) { - m.trimToSize(); - } - } - if (innerClasses != null) { - innerClasses.trimToSize(); - } - } - - public void strip(String attrName) { - if ("InnerClass".equals(attrName)) - innerClasses = null; - for (int isM = 0; isM <= 1; isM++) { - ArrayList members = (isM == 0) ? fields : methods; - if (members == null) continue; - for (Member m : members) { - m.strip(attrName); - } - } - super.strip(attrName); - } - - protected void visitRefs(int mode, Collection refs) { - if (verbose > 2) Utils.log.fine("visitRefs "+this); - refs.add(thisClass); - refs.add(superClass); - refs.addAll(Arrays.asList(interfaces)); - for (int isM = 0; isM <= 1; isM++) { - ArrayList members = (isM == 0) ? fields : methods; - if (members == null) continue; - for (Member m : members) { - boolean ok = false; - try { - m.visitRefs(mode, refs); - ok = true; - } finally { - if (!ok) - Utils.log.warning("Error scanning "+m); - } - } - } - visitInnerClassRefs(mode, refs); - // Handle attribute list: - super.visitRefs(mode, refs); - } - - protected void visitInnerClassRefs(int mode, Collection refs) { - Package.visitInnerClassRefs(innerClasses, mode, refs); - } - - // Hook called by ClassReader when it's done. - void finishReading() { - trimToSize(); - maybeChooseFileName(); - } - - public void initFile(File file) { - assert(this.file == null); // set-once - if (file == null) { - // Build a trivial stub. - file = newStub(canonicalFileName()); - } - this.file = file; - assert(file.isClassStub()); - file.stubClass = this; - maybeChooseFileName(); - } - - public void maybeChooseFileName() { - if (thisClass == null) { - return; // do not choose yet - } - String canonName = canonicalFileName(); - if (file.nameString.equals("")) { - file.nameString = canonName; - } - if (file.nameString.equals(canonName)) { - // The file name is predictable. Transmit "". - file.name = getRefString(""); - return; - } - // If name has not yet been looked up, find it now. - if (file.name == null) { - file.name = getRefString(file.nameString); - } - } - - public String canonicalFileName() { - if (thisClass == null) return null; - return thisClass.stringValue() + ".class"; - } - - public java.io.File getFileName(java.io.File parent) { - String name = file.name.stringValue(); - if (name.equals("")) - name = canonicalFileName(); - String fname = name.replace('/', java.io.File.separatorChar); - return new java.io.File(parent, fname); - } - public java.io.File getFileName() { - return getFileName(null); - } - - public String toString() { - return thisClass.stringValue(); - } - } - - void addClass(Class c) { - assert(c.getPackage() == this); - boolean added = classes.add(c); - assert(added); - // Make sure the class is represented in the total file order: - if (c.file == null) c.initFile(null); - addFile(c.file); - } - - // What non-class files are in this unit? - ArrayList files = new ArrayList<>(); - - public List getFiles() { - return files; - } - - public List getClassStubs() { - List classStubs = new ArrayList<>(classes.size()); - for (Class cls : classes) { - assert(cls.file.isClassStub()); - classStubs.add(cls.file); - } - return classStubs; - } - - public final class File implements Comparable { - String nameString; // true name of this file - Utf8Entry name; - int modtime = Constants.NO_MODTIME; - int options = 0; // random flag bits, such as deflate_hint - Class stubClass; // if this is a stub, here's the class - ArrayList prepend = new ArrayList<>(); // list of byte[] - ByteArrayOutputStream append = new ByteArrayOutputStream(); - - File(Utf8Entry name) { - this.name = name; - this.nameString = name.stringValue(); - // caller must fill in contents - } - File(String nameString) { - nameString = fixupFileName(nameString); - this.name = getRefString(nameString); - this.nameString = name.stringValue(); - } - - public boolean isDirectory() { - // JAR directory. Useless. - return nameString.endsWith("/"); - } - public boolean isClassStub() { - return (options & Constants.FO_IS_CLASS_STUB) != 0; - } - public Class getStubClass() { - assert(isClassStub()); - assert(stubClass != null); - return stubClass; - } - public boolean isTrivialClassStub() { - return isClassStub() - && name.stringValue().equals("") - && (modtime == Constants.NO_MODTIME || modtime == default_modtime) - && (options &~ Constants.FO_IS_CLASS_STUB) == 0; - } - - // The nameString is the key. Ignore other things. - // (Note: The name might be "", in the case of a trivial class stub.) - public boolean equals(Object o) { - if (o == null || (o.getClass() != File.class)) - return false; - File that = (File)o; - return that.nameString.equals(this.nameString); - } - public int hashCode() { - return nameString.hashCode(); - } - // Simple alphabetic sort. PackageWriter uses a better comparator. - public int compareTo(File that) { - return this.nameString.compareTo(that.nameString); - } - public String toString() { - return nameString+"{" - +(isClassStub()?"*":"") - +(BandStructure.testBit(options,Constants.FO_DEFLATE_HINT)?"@":"") - +(modtime==Constants.NO_MODTIME?"":"M"+modtime) - +(getFileLength()==0?"":"["+getFileLength()+"]") - +"}"; - } - - public java.io.File getFileName() { - return getFileName(null); - } - public java.io.File getFileName(java.io.File parent) { - String lname = this.nameString; - //if (name.startsWith("./")) name = name.substring(2); - String fname = lname.replace('/', java.io.File.separatorChar); - return new java.io.File(parent, fname); - } - - public void addBytes(byte[] bytes) { - addBytes(bytes, 0, bytes.length); - } - public void addBytes(byte[] bytes, int off, int len) { - if (((append.size() | len) << 2) < 0) { - prepend.add(append.toByteArray()); - append.reset(); - } - append.write(bytes, off, len); - } - public long getFileLength() { - long len = 0; - if (prepend == null || append == null) return 0; - for (byte[] block : prepend) { - len += block.length; - } - len += append.size(); - return len; - } - public void writeTo(OutputStream out) throws IOException { - if (prepend == null || append == null) return; - for (byte[] block : prepend) { - out.write(block); - } - append.writeTo(out); - } - public void readFrom(InputStream in) throws IOException { - byte[] buf = new byte[1 << 16]; - int nr; - while ((nr = in.read(buf)) > 0) { - addBytes(buf, 0, nr); - } - } - public InputStream getInputStream() { - InputStream in = new ByteArrayInputStream(append.toByteArray()); - if (prepend.isEmpty()) return in; - List isa = new ArrayList<>(prepend.size()+1); - for (byte[] bytes : prepend) { - isa.add(new ByteArrayInputStream(bytes)); - } - isa.add(in); - return new SequenceInputStream(Collections.enumeration(isa)); - } - - protected void visitRefs(int mode, Collection refs) { - assert(name != null); - refs.add(name); - } - } - - File newStub(String classFileNameString) { - File stub = new File(classFileNameString); - stub.options |= Constants.FO_IS_CLASS_STUB; - stub.prepend = null; - stub.append = null; // do not collect data - return stub; - } - - private static String fixupFileName(String name) { - String fname = name.replace(java.io.File.separatorChar, '/'); - if (fname.startsWith("/")) { - throw new IllegalArgumentException("absolute file name "+fname); - } - return fname; - } - - void addFile(File file) { - boolean added = files.add(file); - assert(added); - } - - // Is there a globally declared table of inner classes? - List allInnerClasses = new ArrayList<>(); - Map allInnerClassesByThis; - - public - List getAllInnerClasses() { - return allInnerClasses; - } - - public - void setAllInnerClasses(Collection ics) { - assert(ics != allInnerClasses); - allInnerClasses.clear(); - allInnerClasses.addAll(ics); - - // Make an index: - allInnerClassesByThis = new HashMap<>(allInnerClasses.size()); - for (InnerClass ic : allInnerClasses) { - Object pic = allInnerClassesByThis.put(ic.thisClass, ic); - assert(pic == null); // caller must ensure key uniqueness! - } - } - - /** Return a global inner class record for the given thisClass. */ - public - InnerClass getGlobalInnerClass(Entry thisClass) { - assert(thisClass instanceof ClassEntry); - return allInnerClassesByThis.get(thisClass); - } - - static - class InnerClass implements Comparable { - final ClassEntry thisClass; - final ClassEntry outerClass; - final Utf8Entry name; - final int flags; - - // Can name and outerClass be derived from thisClass? - final boolean predictable; - - // About 30% of inner classes are anonymous (in rt.jar). - // About 60% are class members; the rest are named locals. - // Nearly all have predictable outers and names. - - InnerClass(ClassEntry thisClass, ClassEntry outerClass, - Utf8Entry name, int flags) { - this.thisClass = thisClass; - this.outerClass = outerClass; - this.name = name; - this.flags = flags; - this.predictable = computePredictable(); - } - - private boolean computePredictable() { - //System.out.println("computePredictable "+outerClass+" "+this.name); - String[] parse = parseInnerClassName(thisClass.stringValue()); - if (parse == null) return false; - String pkgOuter = parse[0]; - //String number = parse[1]; - String lname = parse[2]; - String haveName = (this.name == null) ? null : this.name.stringValue(); - String haveOuter = (outerClass == null) ? null : outerClass.stringValue(); - boolean lpredictable = (lname == haveName && pkgOuter == haveOuter); - //System.out.println("computePredictable => "+predictable); - return lpredictable; - } - - public boolean equals(Object o) { - if (o == null || o.getClass() != InnerClass.class) - return false; - InnerClass that = (InnerClass)o; - return eq(this.thisClass, that.thisClass) - && eq(this.outerClass, that.outerClass) - && eq(this.name, that.name) - && this.flags == that.flags; - } - private static boolean eq(Object x, Object y) { - return (x == null)? y == null: x.equals(y); - } - public int hashCode() { - return thisClass.hashCode(); - } - public int compareTo(InnerClass that) { - return this.thisClass.compareTo(that.thisClass); - } - - protected void visitRefs(int mode, Collection refs) { - refs.add(thisClass); - if (mode == Constants.VRM_CLASSIC || !predictable) { - // If the name can be demangled, the package omits - // the products of demangling. Otherwise, include them. - refs.add(outerClass); - refs.add(name); - } - } - - public String toString() { - return thisClass.stringValue(); - } - } - - // Helper for building InnerClasses attributes. - private static - void visitInnerClassRefs(Collection innerClasses, int mode, Collection refs) { - if (innerClasses == null) { - return; // no attribute; nothing to do - } - if (mode == Constants.VRM_CLASSIC) { - refs.add(getRefString("InnerClasses")); - } - if (innerClasses.size() > 0) { - // Count the entries themselves: - for (InnerClass c : innerClasses) { - c.visitRefs(mode, refs); - } - } - } - - static String[] parseInnerClassName(String n) { - //System.out.println("parseInnerClassName "+n); - String pkgOuter, number, name; - int dollar1, dollar2; // pointers to $ in the pattern - // parse n = (/)*($)?($)? - int nlen = n.length(); - int pkglen = lastIndexOf(SLASH_MIN, SLASH_MAX, n, n.length()) + 1; - dollar2 = lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, n, n.length()); - if (dollar2 < pkglen) return null; - if (isDigitString(n, dollar2+1, nlen)) { - // n = (/)*$ - number = n.substring(dollar2+1, nlen); - name = null; - dollar1 = dollar2; - } else if ((dollar1 - = lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, n, dollar2-1)) - > pkglen - && isDigitString(n, dollar1+1, dollar2)) { - // n = (/)*$$ - number = n.substring(dollar1+1, dollar2); - name = n.substring(dollar2+1, nlen).intern(); - } else { - // n = (/)*$ - dollar1 = dollar2; - number = null; - name = n.substring(dollar2+1, nlen).intern(); - } - if (number == null) - pkgOuter = n.substring(0, dollar1).intern(); - else - pkgOuter = null; - //System.out.println("parseInnerClassName parses "+pkgOuter+" "+number+" "+name); - return new String[] { pkgOuter, number, name }; - } - - private static final int SLASH_MIN = '.'; - private static final int SLASH_MAX = '/'; - private static final int DOLLAR_MIN = 0; - private static final int DOLLAR_MAX = '-'; - static { - assert(lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, "x$$y$", 4) == 2); - assert(lastIndexOf(SLASH_MIN, SLASH_MAX, "x//y/", 4) == 2); - } - - private static int lastIndexOf(int chMin, int chMax, String str, int pos) { - for (int i = pos; --i >= 0; ) { - int ch = str.charAt(i); - if (ch >= chMin && ch <= chMax) { - return i; - } - } - return -1; - } - - private static boolean isDigitString(String x, int beg, int end) { - if (beg == end) return false; // null string - for (int i = beg; i < end; i++) { - char ch = x.charAt(i); - if (!(ch >= '0' && ch <= '9')) return false; - } - return true; - } - - static String getObviousSourceFile(String className) { - String n = className; - int pkglen = lastIndexOf(SLASH_MIN, SLASH_MAX, n, n.length()) + 1; - n = n.substring(pkglen); - int cutoff = n.length(); - for (;;) { - // Work backwards, finding all '$', '#', etc. - int dollar2 = lastIndexOf(DOLLAR_MIN, DOLLAR_MAX, n, cutoff-1); - if (dollar2 < 0) - break; - cutoff = dollar2; - if (cutoff == 0) - break; - } - String obvious = n.substring(0, cutoff)+".java"; - return obvious; - } -/* - static { - assert(getObviousSourceFile("foo").equals("foo.java")); - assert(getObviousSourceFile("foo/bar").equals("bar.java")); - assert(getObviousSourceFile("foo/bar$baz").equals("bar.java")); - assert(getObviousSourceFile("foo/bar#baz#1").equals("bar.java")); - assert(getObviousSourceFile("foo.bar.baz#1").equals("baz.java")); - } -*/ - - static Utf8Entry getRefString(String s) { - return ConstantPool.getUtf8Entry(s); - } - - static LiteralEntry getRefLiteral(Comparable s) { - return ConstantPool.getLiteralEntry(s); - } - - void stripAttributeKind(String what) { - // what is one of { Debug, Compile, Constant, Exceptions, InnerClasses } - if (verbose > 0) - Utils.log.info("Stripping "+what.toLowerCase()+" data and attributes..."); - switch (what) { - case "Debug": - strip("SourceFile"); - strip("LineNumberTable"); - strip("LocalVariableTable"); - strip("LocalVariableTypeTable"); - break; - case "Compile": - // Keep the inner classes normally. - // Although they have no effect on execution, - // the Reflection API exposes them, and JCK checks them. - // NO: // strip("InnerClasses"); - strip("Deprecated"); - strip("Synthetic"); - break; - case "Exceptions": - // Keep the exceptions normally. - // Although they have no effect on execution, - // the Reflection API exposes them, and JCK checks them. - strip("Exceptions"); - break; - case "Constant": - stripConstantFields(); - break; - } - } - - public void trimToSize() { - classes.trimToSize(); - for (Class c : classes) { - c.trimToSize(); - } - files.trimToSize(); - } - - public void strip(String attrName) { - for (Class c : classes) { - c.strip(attrName); - } - } - - public void stripConstantFields() { - for (Class c : classes) { - for (Iterator j = c.fields.iterator(); j.hasNext(); ) { - Class.Field f = j.next(); - if (Modifier.isFinal(f.flags) - // do not strip non-static finals: - && Modifier.isStatic(f.flags) - && f.getAttribute("ConstantValue") != null - && !f.getName().startsWith("serial")) { - if (verbose > 2) { - Utils.log.fine(">> Strip "+this+" ConstantValue"); - j.remove(); - } - } - } - } - } - - protected void visitRefs(int mode, Collection refs) { - for ( Class c : classes) { - c.visitRefs(mode, refs); - } - if (mode != Constants.VRM_CLASSIC) { - for (File f : files) { - f.visitRefs(mode, refs); - } - visitInnerClassRefs(allInnerClasses, mode, refs); - } - } - - // Use this before writing the package file. - // It sorts files into a new order which seems likely to - // compress better. It also moves classes to the end of the - // file order. It also removes JAR directory entries, which - // are useless. - void reorderFiles(boolean keepClassOrder, boolean stripDirectories) { - // First reorder the classes, if that is allowed. - if (!keepClassOrder) { - // In one test with rt.jar, this trick gained 0.7% - Collections.sort(classes); - } - - // Remove stubs from resources; maybe we'll add them on at the end, - // if there are some non-trivial ones. The best case is that - // modtimes and options are not transmitted, and the stub files - // for class files do not need to be transmitted at all. - // Also - List stubs = getClassStubs(); - for (Iterator i = files.iterator(); i.hasNext(); ) { - File file = i.next(); - if (file.isClassStub() || - (stripDirectories && file.isDirectory())) { - i.remove(); - } - } - - // Sort the remaining non-class files. - // We sort them by file type. - // This keeps files of similar format near each other. - // Put class files at the end, keeping their fixed order. - // Be sure the JAR file's required manifest stays at the front. (4893051) - Collections.sort(files, new Comparator() { - public int compare(File r0, File r1) { - // Get the file name. - String f0 = r0.nameString; - String f1 = r1.nameString; - if (f0.equals(f1)) return 0; - if (JarFile.MANIFEST_NAME.equals(f0)) return 0-1; - if (JarFile.MANIFEST_NAME.equals(f1)) return 1-0; - // Extract file basename. - String n0 = f0.substring(1+f0.lastIndexOf('/')); - String n1 = f1.substring(1+f1.lastIndexOf('/')); - // Extract basename extension. - String x0 = n0.substring(1+n0.lastIndexOf('.')); - String x1 = n1.substring(1+n1.lastIndexOf('.')); - int r; - // Primary sort key is file extension. - r = x0.compareTo(x1); - if (r != 0) return r; - r = f0.compareTo(f1); - return r; - } - }); - - // Add back the class stubs after sorting, before trimStubs. - files.addAll(stubs); - } - - void trimStubs() { - // Restore enough non-trivial stubs to carry the needed class modtimes. - for (ListIterator i = files.listIterator(files.size()); i.hasPrevious(); ) { - File file = i.previous(); - if (!file.isTrivialClassStub()) { - if (verbose > 1) - Utils.log.fine("Keeping last non-trivial "+file); - break; - } - if (verbose > 2) - Utils.log.fine("Removing trivial "+file); - i.remove(); - } - - if (verbose > 0) { - Utils.log.info("Transmitting "+files.size()+" files, including per-file data for "+getClassStubs().size()+" classes out of "+classes.size()); - } - } - - // Use this before writing the package file. - void buildGlobalConstantPool(Set requiredEntries) { - if (verbose > 1) - Utils.log.fine("Checking for unused CP entries"); - requiredEntries.add(getRefString("")); // uconditionally present - visitRefs(Constants.VRM_PACKAGE, requiredEntries); - ConstantPool.completeReferencesIn(requiredEntries, false); - if (verbose > 1) - Utils.log.fine("Sorting CP entries"); - Index cpAllU = ConstantPool.makeIndex("unsorted", requiredEntries); - Index[] byTagU = ConstantPool.partitionByTag(cpAllU); - for (int i = 0; i < ConstantPool.TAGS_IN_ORDER.length; i++) { - byte tag = ConstantPool.TAGS_IN_ORDER[i]; - // Work on all entries of a given kind. - Index ix = byTagU[tag]; - if (ix == null) continue; - ConstantPool.sort(ix); - cp.initIndexByTag(tag, ix); - byTagU[tag] = null; // done with it - } - for (int i = 0; i < byTagU.length; i++) { - Index ix = byTagU[i]; - assert(ix == null); // all consumed - } - for (int i = 0; i < ConstantPool.TAGS_IN_ORDER.length; i++) { - byte tag = ConstantPool.TAGS_IN_ORDER[i]; - Index ix = cp.getIndexByTag(tag); - assert(ix.assertIsSorted()); - if (verbose > 2) Utils.log.fine(ix.dumpString()); - } - } - - // Use this before writing the class files. - void ensureAllClassFiles() { - Set fileSet = new HashSet<>(files); - for (Class cls : classes) { - // Add to the end of ths list: - if (!fileSet.contains(cls.file)) - files.add(cls.file); - } - } - - static final List noObjects = Arrays.asList(new Object[0]); - static final List noFields = Arrays.asList(new Class.Field[0]); - static final List noMethods = Arrays.asList(new Class.Method[0]); - static final List noInnerClasses = Arrays.asList(new InnerClass[0]); - - protected static final class Version { - - public final short major; - public final short minor; - - private Version(short major, short minor) { - this.major = major; - this.minor = minor; - } - - public String toString() { - return major + "." + minor; - } - - public boolean equals(Object that) { - return that instanceof Version - && major == ((Version)that).major - && minor == ((Version)that).minor; - } - - public int intValue() { - return (major << 16) + minor; - } - - public int hashCode() { - return (major << 16) + 7 + minor; - } - - public static Version of(int major, int minor) { - return new Version((short)major, (short)minor); - } - - public static Version of(byte[] bytes) { - int minor = ((bytes[0] & 0xFF) << 8) | (bytes[1] & 0xFF); - int major = ((bytes[2] & 0xFF) << 8) | (bytes[3] & 0xFF); - return new Version((short)major, (short)minor); - } - - public static Version of(int major_minor) { - short minor = (short)major_minor; - short major = (short)(major_minor >>> 16); - return new Version(major, minor); - } - - public static Version makeVersion(PropMap props, String partialKey) { - int min = props.getInteger(Utils.COM_PREFIX - + partialKey + ".minver", -1); - int maj = props.getInteger(Utils.COM_PREFIX - + partialKey + ".majver", -1); - return min >= 0 && maj >= 0 ? Version.of(maj, min) : null; - } - public byte[] asBytes() { - byte[] bytes = { - (byte) (minor >> 8), (byte) minor, - (byte) (major >> 8), (byte) major - }; - return bytes; - } - public int compareTo(Version that) { - return this.intValue() - that.intValue(); - } - - public boolean lessThan(Version that) { - return compareTo(that) < 0 ; - } - - public boolean greaterThan(Version that) { - return compareTo(that) > 0 ; - } - } -} diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/PackageReader.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/PackageReader.java deleted file mode 100644 index ede5caedc..000000000 --- a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/PackageReader.java +++ /dev/null @@ -1,2371 +0,0 @@ -/* - * Copyright (c) 2001, 2016, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package net.fabricmc.shade.com.sun.java.util.jar.pack; - -import java.io.ByteArrayOutputStream; -import java.io.EOFException; -import java.io.PrintStream; -import java.io.FilterInputStream; -import java.io.BufferedInputStream; -import java.io.InputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Map; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashSet; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.ListIterator; -import java.util.Set; - -/** - * Reader for a package file. - * - * @see PackageWriter - * @author John Rose - */ -class PackageReader extends BandStructure { - Package pkg; - byte[] bytes; - LimitedBuffer in; - Package.Version packageVersion; - - PackageReader(Package pkg, InputStream in) throws IOException { - this.pkg = pkg; - this.in = new LimitedBuffer(in); - } - - /** A buffered input stream which is careful not to - * read its underlying stream ahead of a given mark, - * called the 'readLimit'. This property declares - * the maximum number of characters that future reads - * can consume from the underlying stream. - */ - static - class LimitedBuffer extends BufferedInputStream { - long served; // total number of charburgers served - int servedPos; // ...as of this value of super.pos - long limit; // current declared limit - long buffered; - public boolean atLimit() { - boolean z = (getBytesServed() == limit); - assert(!z || limit == buffered); - return z; - } - public long getBytesServed() { - return served + (pos - servedPos); - } - public void setReadLimit(long newLimit) { - if (newLimit == -1) - limit = -1; - else - limit = getBytesServed() + newLimit; - } - public long getReadLimit() { - if (limit == -1) - return limit; - else - return limit - getBytesServed(); - } - public int read() throws IOException { - if (pos < count) { - // fast path - return buf[pos++] & 0xFF; - } - served += (pos - servedPos); - int ch = super.read(); - servedPos = pos; - if (ch >= 0) served += 1; - assert(served <= limit || limit == -1); - return ch; - } - public int read(byte b[], int off, int len) throws IOException { - served += (pos - servedPos); - int nr = super.read(b, off, len); - servedPos = pos; - if (nr >= 0) served += nr; - //assert(served <= limit || limit == -1); - return nr; - } - public long skip(long n) throws IOException { - throw new RuntimeException("no skipping"); - } - LimitedBuffer(InputStream originalIn) { - super(null, 1<<14); - servedPos = pos; - super.in = new FilterInputStream(originalIn) { - public int read() throws IOException { - if (buffered == limit) - return -1; - ++buffered; - return super.read(); - } - public int read(byte b[], int off, int len) throws IOException { - if (buffered == limit) - return -1; - if (limit != -1) { - long remaining = limit - buffered; - if (len > remaining) - len = (int)remaining; - } - int nr = super.read(b, off, len); - if (nr >= 0) buffered += nr; - return nr; - } - }; - } - } - - void read() throws IOException { - boolean ok = false; - try { - // pack200_archive: - // file_header - // *band_headers :BYTE1 - // cp_bands - // attr_definition_bands - // ic_bands - // class_bands - // bc_bands - // file_bands - readFileHeader(); - readBandHeaders(); - readConstantPool(); // cp_bands - readAttrDefs(); - readInnerClasses(); - Package.Class[] classes = readClasses(); - readByteCodes(); - readFiles(); // file_bands - assert(archiveSize1 == 0 || in.atLimit()); - assert(archiveSize1 == 0 || - in.getBytesServed() == archiveSize0+archiveSize1); - all_bands.doneDisbursing(); - - // As a post-pass, build constant pools and inner classes. - for (int i = 0; i < classes.length; i++) { - reconstructClass(classes[i]); - } - - ok = true; - } catch (Exception ee) { - Utils.log.warning("Error on input: "+ee, ee); - if (verbose > 0) - Utils.log.info("Stream offsets:"+ - " served="+in.getBytesServed()+ - " buffered="+in.buffered+ - " limit="+in.limit); - //if (verbose > 0) ee.printStackTrace(); - if (ee instanceof IOException) throw (IOException)ee; - if (ee instanceof RuntimeException) throw (RuntimeException)ee; - throw new Error("error unpacking", ee); - } - } - - // Temporary count values, until band decoding gets rolling. - int[] tagCount = new int[Constants.CONSTANT_Limit]; - int numFiles; - int numAttrDefs; - int numInnerClasses; - int numClasses; - - void readFileHeader() throws IOException { - // file_header: - // archive_magic archive_header - readArchiveMagic(); - readArchiveHeader(); - } - - // Local routine used to parse fixed-format scalars - // in the file_header: - private int getMagicInt32() throws IOException { - int res = 0; - for (int i = 0; i < 4; i++) { - res <<= 8; - res |= (archive_magic.getByte() & 0xFF); - } - return res; - } - - static final int MAGIC_BYTES = 4; - - void readArchiveMagic() throws IOException { - // Read a minimum of bytes in the first gulp. - in.setReadLimit(MAGIC_BYTES + AH_LENGTH_MIN); - - // archive_magic: - // #archive_magic_word :BYTE1[4] - archive_magic.expectLength(MAGIC_BYTES); - archive_magic.readFrom(in); - - // read and check magic numbers: - int magic = getMagicInt32(); - if (pkg.magic != magic) { - throw new IOException("Unexpected package magic number: got " - + magic + "; expected " + pkg.magic); - } - archive_magic.doneDisbursing(); - } - - // Fixed 6211177, converted to throw IOException - void checkArchiveVersion() throws IOException { - Package.Version versionFound = null; - for (Package.Version v : new Package.Version[] { - Constants.JAVA8_PACKAGE_VERSION, - Constants.JAVA7_PACKAGE_VERSION, - Constants.JAVA6_PACKAGE_VERSION, - Constants.JAVA5_PACKAGE_VERSION - }) { - if (packageVersion.equals(v)) { - versionFound = v; - break; - } - } - if (versionFound == null) { - String expVer = Constants.JAVA8_PACKAGE_VERSION.toString() - + "OR" - + Constants.JAVA7_PACKAGE_VERSION.toString() - + " OR " - + Constants.JAVA6_PACKAGE_VERSION.toString() - + " OR " - + Constants.JAVA5_PACKAGE_VERSION.toString(); - throw new IOException("Unexpected package minor version: got " - + packageVersion.toString() + "; expected " + expVer); - } - } - - void readArchiveHeader() throws IOException { - // archive_header: - // #archive_minver :UNSIGNED5[1] - // #archive_majver :UNSIGNED5[1] - // #archive_options :UNSIGNED5[1] - // (archive_file_counts) ** (#have_file_headers) - // (archive_special_counts) ** (#have_special_formats) - // cp_counts - // class_counts - // - // archive_file_counts: - // #archive_size_hi :UNSIGNED5[1] - // #archive_size_lo :UNSIGNED5[1] - // #archive_next_count :UNSIGNED5[1] - // #archive_modtime :UNSIGNED5[1] - // #file_count :UNSIGNED5[1] - // - // class_counts: - // #ic_count :UNSIGNED5[1] - // #default_class_minver :UNSIGNED5[1] - // #default_class_majver :UNSIGNED5[1] - // #class_count :UNSIGNED5[1] - // - // archive_special_counts: - // #band_headers_size :UNSIGNED5[1] - // #attr_definition_count :UNSIGNED5[1] - // - archive_header_0.expectLength(AH_LENGTH_0); - archive_header_0.readFrom(in); - - int minver = archive_header_0.getInt(); - int majver = archive_header_0.getInt(); - packageVersion = Package.Version.of(majver, minver); - checkArchiveVersion(); - this.initHighestClassVersion(Constants.JAVA7_MAX_CLASS_VERSION); - - archiveOptions = archive_header_0.getInt(); - archive_header_0.doneDisbursing(); - - // detect archive optional fields in archive header - boolean haveSpecial = testBit(archiveOptions, Constants.AO_HAVE_SPECIAL_FORMATS); - boolean haveFiles = testBit(archiveOptions, Constants.AO_HAVE_FILE_HEADERS); - boolean haveNumbers = testBit(archiveOptions, Constants.AO_HAVE_CP_NUMBERS); - boolean haveCPExtra = testBit(archiveOptions, Constants.AO_HAVE_CP_EXTRAS); - initAttrIndexLimit(); - - // now we are ready to use the data: - archive_header_S.expectLength(haveFiles? AH_LENGTH_S : 0); - archive_header_S.readFrom(in); - if (haveFiles) { - long sizeHi = archive_header_S.getInt(); - long sizeLo = archive_header_S.getInt(); - archiveSize1 = (sizeHi << 32) + ((sizeLo << 32) >>> 32); - // Set the limit, now, up to the file_bits. - in.setReadLimit(archiveSize1); // for debug only - } else { - archiveSize1 = 0; - in.setReadLimit(-1); // remove limitation - } - archive_header_S.doneDisbursing(); - archiveSize0 = in.getBytesServed(); - - int remainingHeaders = AH_LENGTH_MIN - AH_LENGTH_0 - AH_LENGTH_S; - if (haveFiles) remainingHeaders += AH_FILE_HEADER_LEN; - if (haveSpecial) remainingHeaders += AH_SPECIAL_FORMAT_LEN; - if (haveNumbers) remainingHeaders += AH_CP_NUMBER_LEN; - if (haveCPExtra) remainingHeaders += AH_CP_EXTRA_LEN; - archive_header_1.expectLength(remainingHeaders); - archive_header_1.readFrom(in); - - if (haveFiles) { - archiveNextCount = archive_header_1.getInt(); - pkg.default_modtime = archive_header_1.getInt(); - numFiles = archive_header_1.getInt(); - } else { - archiveNextCount = 0; - numFiles = 0; - } - - if (haveSpecial) { - band_headers.expectLength(archive_header_1.getInt()); - numAttrDefs = archive_header_1.getInt(); - } else { - band_headers.expectLength(0); - numAttrDefs = 0; - } - - readConstantPoolCounts(haveNumbers, haveCPExtra); - - numInnerClasses = archive_header_1.getInt(); - - minver = (short) archive_header_1.getInt(); - majver = (short) archive_header_1.getInt(); - pkg.defaultClassVersion = Package.Version.of(majver, minver); - numClasses = archive_header_1.getInt(); - - archive_header_1.doneDisbursing(); - - // set some derived archive bits - if (testBit(archiveOptions, Constants.AO_DEFLATE_HINT)) { - pkg.default_options |= Constants.FO_DEFLATE_HINT; - } - } - - void readBandHeaders() throws IOException { - band_headers.readFrom(in); - bandHeaderBytePos = 1; // Leave room to pushback the initial XB byte. - bandHeaderBytes = new byte[bandHeaderBytePos + band_headers.length()]; - for (int i = bandHeaderBytePos; i < bandHeaderBytes.length; i++) { - bandHeaderBytes[i] = (byte) band_headers.getByte(); - } - band_headers.doneDisbursing(); - } - - void readConstantPoolCounts(boolean haveNumbers, boolean haveCPExtra) throws IOException { - // size the constant pools: - for (int k = 0; k < ConstantPool.TAGS_IN_ORDER.length; k++) { - // cp_counts: - // #cp_Utf8_count :UNSIGNED5[1] - // (cp_number_counts) ** (#have_cp_numbers) - // #cp_String_count :UNSIGNED5[1] - // #cp_Class_count :UNSIGNED5[1] - // #cp_Signature_count :UNSIGNED5[1] - // #cp_Descr_count :UNSIGNED5[1] - // #cp_Field_count :UNSIGNED5[1] - // #cp_Method_count :UNSIGNED5[1] - // #cp_Imethod_count :UNSIGNED5[1] - // (cp_attr_counts) ** (#have_cp_attr_counts) - // - // cp_number_counts: - // #cp_Int_count :UNSIGNED5[1] - // #cp_Float_count :UNSIGNED5[1] - // #cp_Long_count :UNSIGNED5[1] - // #cp_Double_count :UNSIGNED5[1] - // - // cp_extra_counts: - // #cp_MethodHandle_count :UNSIGNED5[1] - // #cp_MethodType_count :UNSIGNED5[1] - // #cp_InvokeDynamic_count :UNSIGNED5[1] - // #cp_BootstrapMethod_count :UNSIGNED5[1] - // - byte tag = ConstantPool.TAGS_IN_ORDER[k]; - if (!haveNumbers) { - // These four counts are optional. - switch (tag) { - case Constants.CONSTANT_Integer: - case Constants.CONSTANT_Float: - case Constants.CONSTANT_Long: - case Constants.CONSTANT_Double: - continue; - } - } - if (!haveCPExtra) { - // These four counts are optional. - switch (tag) { - case Constants.CONSTANT_MethodHandle: - case Constants.CONSTANT_MethodType: - case Constants.CONSTANT_InvokeDynamic: - case Constants.CONSTANT_BootstrapMethod: - continue; - } - } - tagCount[tag] = archive_header_1.getInt(); - } - } - - protected ConstantPool.Index getCPIndex(byte tag) { - return pkg.cp.getIndexByTag(tag); - } - ConstantPool.Index initCPIndex(byte tag, ConstantPool.Entry[] cpMap) { - if (verbose > 3) { - for (int i = 0; i < cpMap.length; i++) { - Utils.log.fine("cp.add "+cpMap[i]); - } - } - ConstantPool.Index index = ConstantPool.makeIndex(ConstantPool.tagName(tag), cpMap); - if (verbose > 1) Utils.log.fine("Read "+index); - pkg.cp.initIndexByTag(tag, index); - return index; - } - - void checkLegacy(String bandname) { - if (packageVersion.lessThan(Constants.JAVA7_PACKAGE_VERSION)) { - throw new RuntimeException("unexpected band " + bandname); - } - } - void readConstantPool() throws IOException { - // cp_bands: - // cp_Utf8 - // *cp_Int :UDELTA5 - // *cp_Float :UDELTA5 - // cp_Long - // cp_Double - // *cp_String :UDELTA5 (cp_Utf8) - // *cp_Class :UDELTA5 (cp_Utf8) - // cp_Signature - // cp_Descr - // cp_Field - // cp_Method - // cp_Imethod - - if (verbose > 0) Utils.log.info("Reading CP"); - - for (int k = 0; k < ConstantPool.TAGS_IN_ORDER.length; k++) { - byte tag = ConstantPool.TAGS_IN_ORDER[k]; - int len = tagCount[tag]; - - ConstantPool.Entry[] cpMap = new ConstantPool.Entry[len]; - if (verbose > 0) - Utils.log.info("Reading "+cpMap.length+" "+ConstantPool.tagName(tag)+" entries..."); - - switch (tag) { - case Constants.CONSTANT_Utf8: - readUtf8Bands(cpMap); - break; - case Constants.CONSTANT_Integer: - cp_Int.expectLength(cpMap.length); - cp_Int.readFrom(in); - for (int i = 0; i < cpMap.length; i++) { - int x = cp_Int.getInt(); // coding handles signs OK - cpMap[i] = ConstantPool.getLiteralEntry(x); - } - cp_Int.doneDisbursing(); - break; - case Constants.CONSTANT_Float: - cp_Float.expectLength(cpMap.length); - cp_Float.readFrom(in); - for (int i = 0; i < cpMap.length; i++) { - int x = cp_Float.getInt(); - float fx = Float.intBitsToFloat(x); - cpMap[i] = ConstantPool.getLiteralEntry(fx); - } - cp_Float.doneDisbursing(); - break; - case Constants.CONSTANT_Long: - // cp_Long: - // *cp_Long_hi :UDELTA5 - // *cp_Long_lo :DELTA5 - cp_Long_hi.expectLength(cpMap.length); - cp_Long_hi.readFrom(in); - cp_Long_lo.expectLength(cpMap.length); - cp_Long_lo.readFrom(in); - for (int i = 0; i < cpMap.length; i++) { - long hi = cp_Long_hi.getInt(); - long lo = cp_Long_lo.getInt(); - long x = (hi << 32) + ((lo << 32) >>> 32); - cpMap[i] = ConstantPool.getLiteralEntry(x); - } - cp_Long_hi.doneDisbursing(); - cp_Long_lo.doneDisbursing(); - break; - case Constants.CONSTANT_Double: - // cp_Double: - // *cp_Double_hi :UDELTA5 - // *cp_Double_lo :DELTA5 - cp_Double_hi.expectLength(cpMap.length); - cp_Double_hi.readFrom(in); - cp_Double_lo.expectLength(cpMap.length); - cp_Double_lo.readFrom(in); - for (int i = 0; i < cpMap.length; i++) { - long hi = cp_Double_hi.getInt(); - long lo = cp_Double_lo.getInt(); - long x = (hi << 32) + ((lo << 32) >>> 32); - double dx = Double.longBitsToDouble(x); - cpMap[i] = ConstantPool.getLiteralEntry(dx); - } - cp_Double_hi.doneDisbursing(); - cp_Double_lo.doneDisbursing(); - break; - case Constants.CONSTANT_String: - cp_String.expectLength(cpMap.length); - cp_String.readFrom(in); - cp_String.setIndex(getCPIndex(Constants.CONSTANT_Utf8)); - for (int i = 0; i < cpMap.length; i++) { - cpMap[i] = ConstantPool.getLiteralEntry(cp_String.getRef().stringValue()); - } - cp_String.doneDisbursing(); - break; - case Constants.CONSTANT_Class: - cp_Class.expectLength(cpMap.length); - cp_Class.readFrom(in); - cp_Class.setIndex(getCPIndex(Constants.CONSTANT_Utf8)); - for (int i = 0; i < cpMap.length; i++) { - cpMap[i] = ConstantPool.getClassEntry(cp_Class.getRef().stringValue()); - } - cp_Class.doneDisbursing(); - break; - case Constants.CONSTANT_Signature: - readSignatureBands(cpMap); - break; - case Constants.CONSTANT_NameandType: - // cp_Descr: - // *cp_Descr_type :DELTA5 (cp_Signature) - // *cp_Descr_name :UDELTA5 (cp_Utf8) - cp_Descr_name.expectLength(cpMap.length); - cp_Descr_name.readFrom(in); - cp_Descr_name.setIndex(getCPIndex(Constants.CONSTANT_Utf8)); - cp_Descr_type.expectLength(cpMap.length); - cp_Descr_type.readFrom(in); - cp_Descr_type.setIndex(getCPIndex(Constants.CONSTANT_Signature)); - for (int i = 0; i < cpMap.length; i++) { - ConstantPool.Entry ref = cp_Descr_name.getRef(); - ConstantPool.Entry ref2 = cp_Descr_type.getRef(); - cpMap[i] = ConstantPool.getDescriptorEntry((ConstantPool.Utf8Entry)ref, - (ConstantPool.SignatureEntry)ref2); - } - cp_Descr_name.doneDisbursing(); - cp_Descr_type.doneDisbursing(); - break; - case Constants.CONSTANT_Fieldref: - readMemberRefs(tag, cpMap, cp_Field_class, cp_Field_desc); - break; - case Constants.CONSTANT_Methodref: - readMemberRefs(tag, cpMap, cp_Method_class, cp_Method_desc); - break; - case Constants.CONSTANT_InterfaceMethodref: - readMemberRefs(tag, cpMap, cp_Imethod_class, cp_Imethod_desc); - break; - case Constants.CONSTANT_MethodHandle: - if (cpMap.length > 0) { - checkLegacy(cp_MethodHandle_refkind.name()); - } - cp_MethodHandle_refkind.expectLength(cpMap.length); - cp_MethodHandle_refkind.readFrom(in); - cp_MethodHandle_member.expectLength(cpMap.length); - cp_MethodHandle_member.readFrom(in); - cp_MethodHandle_member.setIndex(getCPIndex(Constants.CONSTANT_AnyMember)); - for (int i = 0; i < cpMap.length; i++) { - byte refKind = (byte) cp_MethodHandle_refkind.getInt(); - ConstantPool.MemberEntry memRef = (ConstantPool.MemberEntry) cp_MethodHandle_member.getRef(); - cpMap[i] = ConstantPool.getMethodHandleEntry(refKind, memRef); - } - cp_MethodHandle_refkind.doneDisbursing(); - cp_MethodHandle_member.doneDisbursing(); - break; - case Constants.CONSTANT_MethodType: - if (cpMap.length > 0) { - checkLegacy(cp_MethodType.name()); - } - cp_MethodType.expectLength(cpMap.length); - cp_MethodType.readFrom(in); - cp_MethodType.setIndex(getCPIndex(Constants.CONSTANT_Signature)); - for (int i = 0; i < cpMap.length; i++) { - ConstantPool.SignatureEntry typeRef = (ConstantPool.SignatureEntry) cp_MethodType.getRef(); - cpMap[i] = ConstantPool.getMethodTypeEntry(typeRef); - } - cp_MethodType.doneDisbursing(); - break; - case Constants.CONSTANT_InvokeDynamic: - if (cpMap.length > 0) { - checkLegacy(cp_InvokeDynamic_spec.name()); - } - cp_InvokeDynamic_spec.expectLength(cpMap.length); - cp_InvokeDynamic_spec.readFrom(in); - cp_InvokeDynamic_spec.setIndex(getCPIndex(Constants.CONSTANT_BootstrapMethod)); - cp_InvokeDynamic_desc.expectLength(cpMap.length); - cp_InvokeDynamic_desc.readFrom(in); - cp_InvokeDynamic_desc.setIndex(getCPIndex(Constants.CONSTANT_NameandType)); - for (int i = 0; i < cpMap.length; i++) { - ConstantPool.BootstrapMethodEntry bss = (ConstantPool.BootstrapMethodEntry) cp_InvokeDynamic_spec.getRef(); - ConstantPool.DescriptorEntry descr = (ConstantPool.DescriptorEntry) cp_InvokeDynamic_desc.getRef(); - cpMap[i] = ConstantPool.getInvokeDynamicEntry(bss, descr); - } - cp_InvokeDynamic_spec.doneDisbursing(); - cp_InvokeDynamic_desc.doneDisbursing(); - break; - case Constants.CONSTANT_BootstrapMethod: - if (cpMap.length > 0) { - checkLegacy(cp_BootstrapMethod_ref.name()); - } - cp_BootstrapMethod_ref.expectLength(cpMap.length); - cp_BootstrapMethod_ref.readFrom(in); - cp_BootstrapMethod_ref.setIndex(getCPIndex(Constants.CONSTANT_MethodHandle)); - cp_BootstrapMethod_arg_count.expectLength(cpMap.length); - cp_BootstrapMethod_arg_count.readFrom(in); - int totalArgCount = cp_BootstrapMethod_arg_count.getIntTotal(); - cp_BootstrapMethod_arg.expectLength(totalArgCount); - cp_BootstrapMethod_arg.readFrom(in); - cp_BootstrapMethod_arg.setIndex(getCPIndex(Constants.CONSTANT_LoadableValue)); - for (int i = 0; i < cpMap.length; i++) { - ConstantPool.MethodHandleEntry bsm = (ConstantPool.MethodHandleEntry) cp_BootstrapMethod_ref.getRef(); - int argc = cp_BootstrapMethod_arg_count.getInt(); - ConstantPool.Entry[] argRefs = new ConstantPool.Entry[argc]; - for (int j = 0; j < argc; j++) { - argRefs[j] = cp_BootstrapMethod_arg.getRef(); - } - cpMap[i] = ConstantPool.getBootstrapMethodEntry(bsm, argRefs); - } - cp_BootstrapMethod_ref.doneDisbursing(); - cp_BootstrapMethod_arg_count.doneDisbursing(); - cp_BootstrapMethod_arg.doneDisbursing(); - break; - default: - throw new AssertionError("unexpected CP tag in package"); - } - - ConstantPool.Index index = initCPIndex(tag, cpMap); - - if (optDumpBands) { - try (PrintStream ps = new PrintStream(getDumpStream(index, ".idx"))) { - printArrayTo(ps, index.cpMap, 0, index.cpMap.length); - } - } - } - - cp_bands.doneDisbursing(); - - if (optDumpBands || verbose > 1) { - for (byte tag = Constants.CONSTANT_GroupFirst; tag < Constants.CONSTANT_GroupLimit; tag++) { - ConstantPool.Index index = pkg.cp.getIndexByTag(tag); - if (index == null || index.isEmpty()) continue; - ConstantPool.Entry[] cpMap = index.cpMap; - if (verbose > 1) - Utils.log.info("Index group "+ConstantPool.tagName(tag)+" contains "+cpMap.length+" entries."); - if (optDumpBands) { - try (PrintStream ps = new PrintStream(getDumpStream(index.debugName, tag, ".gidx", index))) { - printArrayTo(ps, cpMap, 0, cpMap.length, true); - } - } - } - } - - setBandIndexes(); - } - - void readUtf8Bands(ConstantPool.Entry[] cpMap) throws IOException { - // cp_Utf8: - // *cp_Utf8_prefix :DELTA5 - // *cp_Utf8_suffix :UNSIGNED5 - // *cp_Utf8_chars :CHAR3 - // *cp_Utf8_big_suffix :DELTA5 - // (*cp_Utf8_big_chars :DELTA5) - // ** length(cp_Utf8_big_suffix) - int len = cpMap.length; - if (len == 0) - return; // nothing to read - - // Bands have implicit leading zeroes, for the empty string: - final int SUFFIX_SKIP_1 = 1; - final int PREFIX_SKIP_2 = 2; - - // First band: Read lengths of shared prefixes. - cp_Utf8_prefix.expectLength(Math.max(0, len - PREFIX_SKIP_2)); - cp_Utf8_prefix.readFrom(in); - - // Second band: Read lengths of unshared suffixes: - cp_Utf8_suffix.expectLength(Math.max(0, len - SUFFIX_SKIP_1)); - cp_Utf8_suffix.readFrom(in); - - char[][] suffixChars = new char[len][]; - int bigSuffixCount = 0; - - // Third band: Read the char values in the unshared suffixes: - cp_Utf8_chars.expectLength(cp_Utf8_suffix.getIntTotal()); - cp_Utf8_chars.readFrom(in); - for (int i = 0; i < len; i++) { - int suffix = (i < SUFFIX_SKIP_1)? 0: cp_Utf8_suffix.getInt(); - if (suffix == 0 && i >= SUFFIX_SKIP_1) { - // chars are packed in cp_Utf8_big_chars - bigSuffixCount += 1; - continue; - } - suffixChars[i] = new char[suffix]; - for (int j = 0; j < suffix; j++) { - int ch = cp_Utf8_chars.getInt(); - assert(ch == (char)ch); - suffixChars[i][j] = (char)ch; - } - } - cp_Utf8_chars.doneDisbursing(); - - // Fourth band: Go back and size the specially packed strings. - int maxChars = 0; - cp_Utf8_big_suffix.expectLength(bigSuffixCount); - cp_Utf8_big_suffix.readFrom(in); - cp_Utf8_suffix.resetForSecondPass(); - for (int i = 0; i < len; i++) { - int suffix = (i < SUFFIX_SKIP_1)? 0: cp_Utf8_suffix.getInt(); - int prefix = (i < PREFIX_SKIP_2)? 0: cp_Utf8_prefix.getInt(); - if (suffix == 0 && i >= SUFFIX_SKIP_1) { - assert(suffixChars[i] == null); - suffix = cp_Utf8_big_suffix.getInt(); - } else { - assert(suffixChars[i] != null); - } - if (maxChars < prefix + suffix) - maxChars = prefix + suffix; - } - char[] buf = new char[maxChars]; - - // Fifth band(s): Get the specially packed characters. - cp_Utf8_suffix.resetForSecondPass(); - cp_Utf8_big_suffix.resetForSecondPass(); - for (int i = 0; i < len; i++) { - if (i < SUFFIX_SKIP_1) continue; - int suffix = cp_Utf8_suffix.getInt(); - if (suffix != 0) continue; // already input - suffix = cp_Utf8_big_suffix.getInt(); - suffixChars[i] = new char[suffix]; - if (suffix == 0) { - // Do not bother to add an empty "(Utf8_big_0)" band. - continue; - } - IntBand packed = cp_Utf8_big_chars.newIntBand("(Utf8_big_"+i+")"); - packed.expectLength(suffix); - packed.readFrom(in); - for (int j = 0; j < suffix; j++) { - int ch = packed.getInt(); - assert(ch == (char)ch); - suffixChars[i][j] = (char)ch; - } - packed.doneDisbursing(); - } - cp_Utf8_big_chars.doneDisbursing(); - - // Finally, sew together all the prefixes and suffixes. - cp_Utf8_prefix.resetForSecondPass(); - cp_Utf8_suffix.resetForSecondPass(); - cp_Utf8_big_suffix.resetForSecondPass(); - for (int i = 0; i < len; i++) { - int prefix = (i < PREFIX_SKIP_2)? 0: cp_Utf8_prefix.getInt(); - int suffix = (i < SUFFIX_SKIP_1)? 0: cp_Utf8_suffix.getInt(); - if (suffix == 0 && i >= SUFFIX_SKIP_1) - suffix = cp_Utf8_big_suffix.getInt(); - - // by induction, the buffer is already filled with the prefix - System.arraycopy(suffixChars[i], 0, buf, prefix, suffix); - - cpMap[i] = ConstantPool.getUtf8Entry(new String(buf, 0, prefix+suffix)); - } - - cp_Utf8_prefix.doneDisbursing(); - cp_Utf8_suffix.doneDisbursing(); - cp_Utf8_big_suffix.doneDisbursing(); - } - - Map utf8Signatures; - - void readSignatureBands(ConstantPool.Entry[] cpMap) throws IOException { - // cp_Signature: - // *cp_Signature_form :DELTA5 (cp_Utf8) - // *cp_Signature_classes :UDELTA5 (cp_Class) - cp_Signature_form.expectLength(cpMap.length); - cp_Signature_form.readFrom(in); - cp_Signature_form.setIndex(getCPIndex(Constants.CONSTANT_Utf8)); - int[] numSigClasses = new int[cpMap.length]; - for (int i = 0; i < cpMap.length; i++) { - ConstantPool.Utf8Entry formRef = (ConstantPool.Utf8Entry) cp_Signature_form.getRef(); - numSigClasses[i] = ConstantPool.countClassParts(formRef); - } - cp_Signature_form.resetForSecondPass(); - cp_Signature_classes.expectLength(getIntTotal(numSigClasses)); - cp_Signature_classes.readFrom(in); - cp_Signature_classes.setIndex(getCPIndex(Constants.CONSTANT_Class)); - utf8Signatures = new HashMap<>(); - for (int i = 0; i < cpMap.length; i++) { - ConstantPool.Utf8Entry formRef = (ConstantPool.Utf8Entry) cp_Signature_form.getRef(); - ConstantPool.ClassEntry[] classRefs = new ConstantPool.ClassEntry[numSigClasses[i]]; - for (int j = 0; j < classRefs.length; j++) { - classRefs[j] = (ConstantPool.ClassEntry) cp_Signature_classes.getRef(); - } - ConstantPool.SignatureEntry se = ConstantPool.getSignatureEntry(formRef, classRefs); - cpMap[i] = se; - utf8Signatures.put(se.asUtf8Entry(), se); - } - cp_Signature_form.doneDisbursing(); - cp_Signature_classes.doneDisbursing(); - } - - void readMemberRefs(byte tag, ConstantPool.Entry[] cpMap, CPRefBand cp_class, CPRefBand cp_desc) throws IOException { - // cp_Field: - // *cp_Field_class :DELTA5 (cp_Class) - // *cp_Field_desc :UDELTA5 (cp_Descr) - // cp_Method: - // *cp_Method_class :DELTA5 (cp_Class) - // *cp_Method_desc :UDELTA5 (cp_Descr) - // cp_Imethod: - // *cp_Imethod_class :DELTA5 (cp_Class) - // *cp_Imethod_desc :UDELTA5 (cp_Descr) - cp_class.expectLength(cpMap.length); - cp_class.readFrom(in); - cp_class.setIndex(getCPIndex(Constants.CONSTANT_Class)); - cp_desc.expectLength(cpMap.length); - cp_desc.readFrom(in); - cp_desc.setIndex(getCPIndex(Constants.CONSTANT_NameandType)); - for (int i = 0; i < cpMap.length; i++) { - ConstantPool.ClassEntry mclass = (ConstantPool.ClassEntry) cp_class.getRef(); - ConstantPool.DescriptorEntry mdescr = (ConstantPool.DescriptorEntry) cp_desc.getRef(); - cpMap[i] = ConstantPool.getMemberEntry(tag, mclass, mdescr); - } - cp_class.doneDisbursing(); - cp_desc.doneDisbursing(); - } - - void readFiles() throws IOException { - // file_bands: - // *file_name :UNSIGNED5 (cp_Utf8) - // *file_size_hi :UNSIGNED5 - // *file_size_lo :UNSIGNED5 - // *file_modtime :DELTA5 - // *file_options :UNSIGNED5 - // *file_bits :BYTE1 - if (verbose > 0) - Utils.log.info(" ...building "+numFiles+" files..."); - file_name.expectLength(numFiles); - file_size_lo.expectLength(numFiles); - int options = archiveOptions; - boolean haveSizeHi = testBit(options, Constants.AO_HAVE_FILE_SIZE_HI); - boolean haveModtime = testBit(options, Constants.AO_HAVE_FILE_MODTIME); - boolean haveOptions = testBit(options, Constants.AO_HAVE_FILE_OPTIONS); - if (haveSizeHi) - file_size_hi.expectLength(numFiles); - if (haveModtime) - file_modtime.expectLength(numFiles); - if (haveOptions) - file_options.expectLength(numFiles); - - file_name.readFrom(in); - file_size_hi.readFrom(in); - file_size_lo.readFrom(in); - file_modtime.readFrom(in); - file_options.readFrom(in); - file_bits.setInputStreamFrom(in); - - Iterator nextClass = pkg.getClasses().iterator(); - - // Compute file lengths before reading any file bits. - long totalFileLength = 0; - long[] fileLengths = new long[numFiles]; - for (int i = 0; i < numFiles; i++) { - long size = ((long)file_size_lo.getInt() << 32) >>> 32; - if (haveSizeHi) - size += (long)file_size_hi.getInt() << 32; - fileLengths[i] = size; - totalFileLength += size; - } - assert(in.getReadLimit() == -1 || in.getReadLimit() == totalFileLength); - - byte[] buf = new byte[1<<16]; - for (int i = 0; i < numFiles; i++) { - // %%% Use a big temp file for file bits? - ConstantPool.Utf8Entry name = (ConstantPool.Utf8Entry) file_name.getRef(); - long size = fileLengths[i]; - Package.File file = pkg.new File(name); - file.modtime = pkg.default_modtime; - file.options = pkg.default_options; - if (haveModtime) - file.modtime += file_modtime.getInt(); - if (haveOptions) - file.options |= file_options.getInt(); - if (verbose > 1) - Utils.log.fine("Reading "+size+" bytes of "+name.stringValue()); - long toRead = size; - while (toRead > 0) { - int nr = buf.length; - if (nr > toRead) nr = (int) toRead; - nr = file_bits.getInputStream().read(buf, 0, nr); - if (nr < 0) throw new EOFException(); - file.addBytes(buf, 0, nr); - toRead -= nr; - } - pkg.addFile(file); - if (file.isClassStub()) { - assert(file.getFileLength() == 0); - Package.Class cls = nextClass.next(); - cls.initFile(file); - } - } - - // Do the rest of the classes. - while (nextClass.hasNext()) { - Package.Class cls = nextClass.next(); - cls.initFile(null); // implicitly initialize to a trivial one - cls.file.modtime = pkg.default_modtime; - } - - file_name.doneDisbursing(); - file_size_hi.doneDisbursing(); - file_size_lo.doneDisbursing(); - file_modtime.doneDisbursing(); - file_options.doneDisbursing(); - file_bits.doneDisbursing(); - file_bands.doneDisbursing(); - - if (archiveSize1 != 0 && !in.atLimit()) { - throw new RuntimeException("Predicted archive_size "+ - archiveSize1+" != "+ - (in.getBytesServed()-archiveSize0)); - } - } - - void readAttrDefs() throws IOException { - // attr_definition_bands: - // *attr_definition_headers :BYTE1 - // *attr_definition_name :UNSIGNED5 (cp_Utf8) - // *attr_definition_layout :UNSIGNED5 (cp_Utf8) - attr_definition_headers.expectLength(numAttrDefs); - attr_definition_name.expectLength(numAttrDefs); - attr_definition_layout.expectLength(numAttrDefs); - attr_definition_headers.readFrom(in); - attr_definition_name.readFrom(in); - attr_definition_layout.readFrom(in); - try (PrintStream dump = !optDumpBands ? null - : new PrintStream(getDumpStream(attr_definition_headers, ".def"))) - { - for (int i = 0; i < numAttrDefs; i++) { - int header = attr_definition_headers.getByte(); - ConstantPool.Utf8Entry name = (ConstantPool.Utf8Entry) attr_definition_name.getRef(); - ConstantPool.Utf8Entry layout = (ConstantPool.Utf8Entry) attr_definition_layout.getRef(); - int ctype = (header & ADH_CONTEXT_MASK); - int index = (header >> ADH_BIT_SHIFT) - ADH_BIT_IS_LSB; - Attribute.Layout def = new Attribute.Layout(ctype, - name.stringValue(), - layout.stringValue()); - // Check layout string for Java 6 extensions. - String pvLayout = def.layoutForClassVersion(getHighestClassVersion()); - if (!pvLayout.equals(def.layout())) { - throw new IOException("Bad attribute layout in archive: "+def.layout()); - } - this.setAttributeLayoutIndex(def, index); - if (dump != null) dump.println(index+" "+def); - } - } - attr_definition_headers.doneDisbursing(); - attr_definition_name.doneDisbursing(); - attr_definition_layout.doneDisbursing(); - // Attribute layouts define bands, one per layout element. - // Create them now, all at once. - makeNewAttributeBands(); - attr_definition_bands.doneDisbursing(); - } - - void readInnerClasses() throws IOException { - // ic_bands: - // *ic_this_class :UDELTA5 (cp_Class) - // *ic_flags :UNSIGNED5 - // *ic_outer_class :DELTA5 (null or cp_Class) - // *ic_name :DELTA5 (null or cp_Utf8) - ic_this_class.expectLength(numInnerClasses); - ic_this_class.readFrom(in); - ic_flags.expectLength(numInnerClasses); - ic_flags.readFrom(in); - int longICCount = 0; - for (int i = 0; i < numInnerClasses; i++) { - int flags = ic_flags.getInt(); - boolean longForm = (flags & Constants.ACC_IC_LONG_FORM) != 0; - if (longForm) { - longICCount += 1; - } - } - ic_outer_class.expectLength(longICCount); - ic_outer_class.readFrom(in); - ic_name.expectLength(longICCount); - ic_name.readFrom(in); - ic_flags.resetForSecondPass(); - List icList = new ArrayList<>(numInnerClasses); - for (int i = 0; i < numInnerClasses; i++) { - int flags = ic_flags.getInt(); - boolean longForm = (flags & Constants.ACC_IC_LONG_FORM) != 0; - flags &= ~Constants.ACC_IC_LONG_FORM; - ConstantPool.ClassEntry thisClass = (ConstantPool.ClassEntry) ic_this_class.getRef(); - ConstantPool.ClassEntry outerClass; - ConstantPool.Utf8Entry thisName; - if (longForm) { - outerClass = (ConstantPool.ClassEntry) ic_outer_class.getRef(); - thisName = (ConstantPool.Utf8Entry) ic_name.getRef(); - } else { - String n = thisClass.stringValue(); - String[] parse = Package.parseInnerClassName(n); - assert(parse != null); - String pkgOuter = parse[0]; - //String number = parse[1]; - String name = parse[2]; - if (pkgOuter == null) - outerClass = null; - else - outerClass = ConstantPool.getClassEntry(pkgOuter); - if (name == null) - thisName = null; - else - thisName = ConstantPool.getUtf8Entry(name); - } - Package.InnerClass ic = - new Package.InnerClass(thisClass, outerClass, thisName, flags); - assert(longForm || ic.predictable); - icList.add(ic); - } - ic_flags.doneDisbursing(); - ic_this_class.doneDisbursing(); - ic_outer_class.doneDisbursing(); - ic_name.doneDisbursing(); - pkg.setAllInnerClasses(icList); - ic_bands.doneDisbursing(); - } - - void readLocalInnerClasses(Package.Class cls) throws IOException { - int nc = class_InnerClasses_N.getInt(); - List localICs = new ArrayList<>(nc); - for (int i = 0; i < nc; i++) { - ConstantPool.ClassEntry thisClass = (ConstantPool.ClassEntry) class_InnerClasses_RC.getRef(); - int flags = class_InnerClasses_F.getInt(); - if (flags == 0) { - // A zero flag means copy a global IC here. - Package.InnerClass ic = pkg.getGlobalInnerClass(thisClass); - assert(ic != null); // must be a valid global IC reference - localICs.add(ic); - } else { - if (flags == Constants.ACC_IC_LONG_FORM) - flags = 0; // clear the marker bit - ConstantPool.ClassEntry outer = (ConstantPool.ClassEntry) class_InnerClasses_outer_RCN.getRef(); - ConstantPool.Utf8Entry name = (ConstantPool.Utf8Entry) class_InnerClasses_name_RUN.getRef(); - localICs.add(new Package.InnerClass(thisClass, outer, name, flags)); - } - } - cls.setInnerClasses(localICs); - // cls.expandLocalICs may add more tuples to ics also, - // or may even delete tuples. - // We cannot do that now, because we do not know the - // full contents of the local constant pool yet. - } - - static final int NO_FLAGS_YET = 0; // placeholder for later flag read-in - - Package.Class[] readClasses() throws IOException { - // class_bands: - // *class_this :DELTA5 (cp_Class) - // *class_super :DELTA5 (cp_Class) - // *class_interface_count :DELTA5 - // *class_interface :DELTA5 (cp_Class) - // ...(member bands)... - // class_attr_bands - // code_bands - Package.Class[] classes = new Package.Class[numClasses]; - if (verbose > 0) - Utils.log.info(" ...building "+classes.length+" classes..."); - - class_this.expectLength(numClasses); - class_super.expectLength(numClasses); - class_interface_count.expectLength(numClasses); - - class_this.readFrom(in); - class_super.readFrom(in); - class_interface_count.readFrom(in); - class_interface.expectLength(class_interface_count.getIntTotal()); - class_interface.readFrom(in); - for (int i = 0; i < classes.length; i++) { - ConstantPool.ClassEntry thisClass = (ConstantPool.ClassEntry) class_this.getRef(); - ConstantPool.ClassEntry superClass = (ConstantPool.ClassEntry) class_super.getRef(); - ConstantPool.ClassEntry[] interfaces = new ConstantPool.ClassEntry[class_interface_count.getInt()]; - for (int j = 0; j < interfaces.length; j++) { - interfaces[j] = (ConstantPool.ClassEntry) class_interface.getRef(); - } - // Packer encoded rare case of null superClass as thisClass: - if (superClass == thisClass) superClass = null; - Package.Class cls = pkg.new Class(NO_FLAGS_YET, - thisClass, superClass, interfaces); - classes[i] = cls; - } - class_this.doneDisbursing(); - class_super.doneDisbursing(); - class_interface_count.doneDisbursing(); - class_interface.doneDisbursing(); - readMembers(classes); - countAndReadAttrs(Constants.ATTR_CONTEXT_CLASS, Arrays.asList(classes)); - pkg.trimToSize(); - readCodeHeaders(); - //code_bands.doneDisbursing(); // still need to read code attrs - //class_bands.doneDisbursing(); // still need to read code attrs - return classes; - } - - private int getOutputIndex(ConstantPool.Entry e) { - // Output CPs do not contain signatures. - assert(e.tag != Constants.CONSTANT_Signature); - int k = pkg.cp.untypedIndexOf(e); - // In the output ordering, input signatures can serve - // in place of Utf8s. - if (k >= 0) - return k; - if (e.tag == Constants.CONSTANT_Utf8) { - ConstantPool.Entry se = utf8Signatures.get(e); - return pkg.cp.untypedIndexOf(se); - } - return -1; - } - - Comparator entryOutputOrder = new Comparator() { - public int compare(ConstantPool.Entry e0, ConstantPool.Entry e1) { - int k0 = getOutputIndex(e0); - int k1 = getOutputIndex(e1); - if (k0 >= 0 && k1 >= 0) - // If both have keys, use the keys. - return k0 - k1; - if (k0 == k1) - // If neither have keys, use their native tags & spellings. - return e0.compareTo(e1); - // Otherwise, the guy with the key comes first. - return (k0 >= 0)? 0-1: 1-0; - } - }; - - void reconstructClass(Package.Class cls) { - if (verbose > 1) Utils.log.fine("reconstruct "+cls); - - // check for local .ClassFile.version - Attribute retroVersion = cls.getAttribute(attrClassFileVersion); - if (retroVersion != null) { - cls.removeAttribute(retroVersion); - cls.version = parseClassFileVersionAttr(retroVersion); - } else { - cls.version = pkg.defaultClassVersion; - } - - // Replace null SourceFile by "obvious" string. - cls.expandSourceFile(); - - // record the local cp: - cls.setCPMap(reconstructLocalCPMap(cls)); - } - - ConstantPool.Entry[] reconstructLocalCPMap(Package.Class cls) { - Set ldcRefs = ldcRefMap.get(cls); - Set cpRefs = new HashSet<>(); - - // look for constant pool entries: - cls.visitRefs(Constants.VRM_CLASSIC, cpRefs); - - ArrayList bsms = new ArrayList<>(); - // flesh out the local constant pool - ConstantPool.completeReferencesIn(cpRefs, true, bsms); - - // add the bsm and references as required - if (!bsms.isEmpty()) { - cls.addAttribute(Package.attrBootstrapMethodsEmpty.canonicalInstance()); - cpRefs.add(Package.getRefString("BootstrapMethods")); - Collections.sort(bsms); - cls.setBootstrapMethods(bsms); - } - - // Now that we know all our local class references, - // compute the InnerClasses attribute. - // An InnerClasses attribute usually gets added here, - // although it might already have been present. - int changed = cls.expandLocalICs(); - - if (changed != 0) { - if (changed > 0) { - // Just visit the expanded InnerClasses attr. - cls.visitInnerClassRefs(Constants.VRM_CLASSIC, cpRefs); - } else { - // Have to recompute from scratch, because of deletions. - cpRefs.clear(); - cls.visitRefs(Constants.VRM_CLASSIC, cpRefs); - } - - // flesh out the local constant pool, again - ConstantPool.completeReferencesIn(cpRefs, true, bsms); - } - - // construct a local constant pool - int numDoubles = 0; - for (ConstantPool.Entry e : cpRefs) { - if (e.isDoubleWord()) numDoubles++; - } - ConstantPool.Entry[] cpMap = new ConstantPool.Entry[1+numDoubles+cpRefs.size()]; - int fillp = 1; - - // Add all ldc operands first. - if (ldcRefs != null) { - assert(cpRefs.containsAll(ldcRefs)); - for (ConstantPool.Entry e : ldcRefs) { - cpMap[fillp++] = e; - } - assert(fillp == 1+ldcRefs.size()); - cpRefs.removeAll(ldcRefs); - ldcRefs = null; // done with it - } - - // Next add all the two-byte references. - Set wideRefs = cpRefs; - cpRefs = null; // do not use! - int narrowLimit = fillp; - for (ConstantPool.Entry e : wideRefs) { - cpMap[fillp++] = e; - } - assert(fillp == narrowLimit+wideRefs.size()); - Arrays.sort(cpMap, 1, narrowLimit, entryOutputOrder); - Arrays.sort(cpMap, narrowLimit, fillp, entryOutputOrder); - - if (verbose > 3) { - Utils.log.fine("CP of "+this+" {"); - for (int i = 0; i < fillp; i++) { - ConstantPool.Entry e = cpMap[i]; - Utils.log.fine(" "+((e==null)?-1:getOutputIndex(e)) - +" : "+e); - } - Utils.log.fine("}"); - } - - // Now repack backwards, introducing null elements. - int revp = cpMap.length; - for (int i = fillp; --i >= 1; ) { - ConstantPool.Entry e = cpMap[i]; - if (e.isDoubleWord()) - cpMap[--revp] = null; - cpMap[--revp] = e; - } - assert(revp == 1); // do not process the initial null - - return cpMap; - } - - void readMembers(Package.Class[] classes) throws IOException { - // class_bands: - // ... - // *class_field_count :DELTA5 - // *class_method_count :DELTA5 - // - // *field_descr :DELTA5 (cp_Descr) - // field_attr_bands - // - // *method_descr :MDELTA5 (cp_Descr) - // method_attr_bands - // ... - assert(classes.length == numClasses); - class_field_count.expectLength(numClasses); - class_method_count.expectLength(numClasses); - class_field_count.readFrom(in); - class_method_count.readFrom(in); - - // Make a pre-pass over field and method counts to size the descrs: - int totalNF = class_field_count.getIntTotal(); - int totalNM = class_method_count.getIntTotal(); - field_descr.expectLength(totalNF); - method_descr.expectLength(totalNM); - if (verbose > 1) Utils.log.fine("expecting #fields="+totalNF+ - " and #methods="+totalNM+" in #classes="+numClasses); - - List fields = new ArrayList<>(totalNF); - field_descr.readFrom(in); - for (int i = 0; i < classes.length; i++) { - Package.Class c = classes[i]; - int nf = class_field_count.getInt(); - for (int j = 0; j < nf; j++) { - Package.Class.Field f = c.new Field(NO_FLAGS_YET, (ConstantPool.DescriptorEntry) - field_descr.getRef()); - fields.add(f); - } - } - class_field_count.doneDisbursing(); - field_descr.doneDisbursing(); - countAndReadAttrs(Constants.ATTR_CONTEXT_FIELD, fields); - fields = null; // release to GC - - List methods = new ArrayList<>(totalNM); - method_descr.readFrom(in); - for (int i = 0; i < classes.length; i++) { - Package.Class c = classes[i]; - int nm = class_method_count.getInt(); - for (int j = 0; j < nm; j++) { - Package.Class.Method m = c.new Method(NO_FLAGS_YET, (ConstantPool.DescriptorEntry) - method_descr.getRef()); - methods.add(m); - } - } - class_method_count.doneDisbursing(); - method_descr.doneDisbursing(); - countAndReadAttrs(Constants.ATTR_CONTEXT_METHOD, methods); - - // Up to this point, Code attributes look like empty attributes. - // Now we start to special-case them. The empty canonical Code - // attributes stay in the method attribute lists, however. - allCodes = buildCodeAttrs(methods); - } - - Code[] allCodes; - List codesWithFlags; - Map> ldcRefMap = new HashMap<>(); - - Code[] buildCodeAttrs(List methods) { - List codes = new ArrayList<>(methods.size()); - for (Package.Class.Method m : methods) { - if (m.getAttribute(attrCodeEmpty) != null) { - m.code = new Code(m); - codes.add(m.code); - } - } - Code[] a = new Code[codes.size()]; - codes.toArray(a); - return a; - } - - void readCodeHeaders() throws IOException { - // code_bands: - // *code_headers :BYTE1 - // - // *code_max_stack :UNSIGNED5 - // *code_max_na_locals :UNSIGNED5 - // *code_handler_count :UNSIGNED5 - // ... - // code_attr_bands - boolean attrsOK = testBit(archiveOptions, Constants.AO_HAVE_ALL_CODE_FLAGS); - code_headers.expectLength(allCodes.length); - code_headers.readFrom(in); - List longCodes = new ArrayList<>(allCodes.length / 10); - for (int i = 0; i < allCodes.length; i++) { - Code c = allCodes[i]; - int sc = code_headers.getByte(); - assert(sc == (sc & 0xFF)); - if (verbose > 2) - Utils.log.fine("codeHeader "+c+" = "+sc); - if (sc == LONG_CODE_HEADER) { - // We will read ms/ml/nh/flags from bands shortly. - longCodes.add(c); - continue; - } - // Short code header is the usual case: - c.setMaxStack( shortCodeHeader_max_stack(sc) ); - c.setMaxNALocals( shortCodeHeader_max_na_locals(sc) ); - c.setHandlerCount( shortCodeHeader_handler_count(sc) ); - assert(shortCodeHeader(c) == sc); - } - code_headers.doneDisbursing(); - code_max_stack.expectLength(longCodes.size()); - code_max_na_locals.expectLength(longCodes.size()); - code_handler_count.expectLength(longCodes.size()); - - // Do the long headers now. - code_max_stack.readFrom(in); - code_max_na_locals.readFrom(in); - code_handler_count.readFrom(in); - for (Code c : longCodes) { - c.setMaxStack( code_max_stack.getInt() ); - c.setMaxNALocals( code_max_na_locals.getInt() ); - c.setHandlerCount( code_handler_count.getInt() ); - } - code_max_stack.doneDisbursing(); - code_max_na_locals.doneDisbursing(); - code_handler_count.doneDisbursing(); - - readCodeHandlers(); - - if (attrsOK) { - // Code attributes are common (debug info not stripped). - codesWithFlags = Arrays.asList(allCodes); - } else { - // Code attributes are very sparse (debug info is stripped). - codesWithFlags = longCodes; - } - countAttrs(Constants.ATTR_CONTEXT_CODE, codesWithFlags); - // do readAttrs later, after BCs are scanned - } - - void readCodeHandlers() throws IOException { - // code_bands: - // ... - // *code_handler_start_P :BCI5 - // *code_handler_end_PO :BRANCH5 - // *code_handler_catch_PO :BRANCH5 - // *code_handler_class_RCN :UNSIGNED5 (null or cp_Class) - // ... - int nh = 0; - for (int i = 0; i < allCodes.length; i++) { - Code c = allCodes[i]; - nh += c.getHandlerCount(); - } - - ValueBand[] code_handler_bands = { - code_handler_start_P, - code_handler_end_PO, - code_handler_catch_PO, - code_handler_class_RCN - }; - - for (int i = 0; i < code_handler_bands.length; i++) { - code_handler_bands[i].expectLength(nh); - code_handler_bands[i].readFrom(in); - } - - for (int i = 0; i < allCodes.length; i++) { - Code c = allCodes[i]; - for (int j = 0, jmax = c.getHandlerCount(); j < jmax; j++) { - c.handler_class[j] = code_handler_class_RCN.getRef(); - // For now, just record the raw BCI codes. - // We must wait until we have instruction boundaries. - c.handler_start[j] = code_handler_start_P.getInt(); - c.handler_end[j] = code_handler_end_PO.getInt(); - c.handler_catch[j] = code_handler_catch_PO.getInt(); - } - } - for (int i = 0; i < code_handler_bands.length; i++) { - code_handler_bands[i].doneDisbursing(); - } - } - - void fixupCodeHandlers() { - // Actually decode (renumber) the BCIs now. - for (int i = 0; i < allCodes.length; i++) { - Code c = allCodes[i]; - for (int j = 0, jmax = c.getHandlerCount(); j < jmax; j++) { - int sum = c.handler_start[j]; - c.handler_start[j] = c.decodeBCI(sum); - sum += c.handler_end[j]; - c.handler_end[j] = c.decodeBCI(sum); - sum += c.handler_catch[j]; - c.handler_catch[j] = c.decodeBCI(sum); - } - } - } - - // Generic routines for reading attributes of - // classes, fields, methods, and codes. - // The holders is a global list, already collected, - // of attribute "customers". - void countAndReadAttrs(int ctype, Collection holders) - throws IOException { - // class_attr_bands: - // *class_flags :UNSIGNED5 - // *class_attr_count :UNSIGNED5 - // *class_attr_indexes :UNSIGNED5 - // *class_attr_calls :UNSIGNED5 - // *class_Signature_RS :UNSIGNED5 (cp_Signature) - // class_metadata_bands - // *class_SourceFile_RU :UNSIGNED5 (cp_Utf8) - // *class_EnclosingMethod_RM :UNSIGNED5 (cp_Method) - // ic_local_bands - // *class_ClassFile_version_minor_H :UNSIGNED5 - // *class_ClassFile_version_major_H :UNSIGNED5 - // class_type_metadata_bands - // - // field_attr_bands: - // *field_flags :UNSIGNED5 - // *field_attr_count :UNSIGNED5 - // *field_attr_indexes :UNSIGNED5 - // *field_attr_calls :UNSIGNED5 - // *field_Signature_RS :UNSIGNED5 (cp_Signature) - // field_metadata_bands - // *field_ConstantValue_KQ :UNSIGNED5 (cp_Int, etc.; see note) - // field_type_metadata_bands - // - // method_attr_bands: - // *method_flags :UNSIGNED5 - // *method_attr_count :UNSIGNED5 - // *method_attr_indexes :UNSIGNED5 - // *method_attr_calls :UNSIGNED5 - // *method_Signature_RS :UNSIGNED5 (cp_Signature) - // method_metadata_bands - // *method_Exceptions_N :UNSIGNED5 - // *method_Exceptions_RC :UNSIGNED5 (cp_Class) - // *method_MethodParameters_NB: BYTE1 - // *method_MethodParameters_RUN: UNSIGNED5 (cp_Utf8) - // *method_MethodParameters_FH: UNSIGNED5 (flag) - // method_type_metadata_bands - // - // code_attr_bands: - // *code_flags :UNSIGNED5 - // *code_attr_count :UNSIGNED5 - // *code_attr_indexes :UNSIGNED5 - // *code_attr_calls :UNSIGNED5 - // *code_LineNumberTable_N :UNSIGNED5 - // *code_LineNumberTable_bci_P :BCI5 - // *code_LineNumberTable_line :UNSIGNED5 - // *code_LocalVariableTable_N :UNSIGNED5 - // *code_LocalVariableTable_bci_P :BCI5 - // *code_LocalVariableTable_span_O :BRANCH5 - // *code_LocalVariableTable_name_RU :UNSIGNED5 (cp_Utf8) - // *code_LocalVariableTable_type_RS :UNSIGNED5 (cp_Signature) - // *code_LocalVariableTable_slot :UNSIGNED5 - // code_type_metadata_bands - - countAttrs(ctype, holders); - readAttrs(ctype, holders); - } - - // Read flags and count the attributes that are to be placed - // on the given holders. - void countAttrs(int ctype, Collection holders) - throws IOException { - // Here, xxx stands for one of class, field, method, code. - MultiBand xxx_attr_bands = attrBands[ctype]; - long flagMask = attrFlagMask[ctype]; - if (verbose > 1) { - Utils.log.fine("scanning flags and attrs for "+ - Attribute.contextName(ctype)+"["+holders.size()+"]"); - } - - // Fetch the attribute layout definitions which govern the bands - // we are about to read. - List defList = attrDefs.get(ctype); - Attribute.Layout[] defs = new Attribute.Layout[defList.size()]; - defList.toArray(defs); - IntBand xxx_flags_hi = getAttrBand(xxx_attr_bands, AB_FLAGS_HI); - IntBand xxx_flags_lo = getAttrBand(xxx_attr_bands, AB_FLAGS_LO); - IntBand xxx_attr_count = getAttrBand(xxx_attr_bands, AB_ATTR_COUNT); - IntBand xxx_attr_indexes = getAttrBand(xxx_attr_bands, AB_ATTR_INDEXES); - IntBand xxx_attr_calls = getAttrBand(xxx_attr_bands, AB_ATTR_CALLS); - - // Count up the number of holders which have overflow attrs. - int overflowMask = attrOverflowMask[ctype]; - int overflowHolderCount = 0; - boolean haveLongFlags = haveFlagsHi(ctype); - xxx_flags_hi.expectLength(haveLongFlags? holders.size(): 0); - xxx_flags_hi.readFrom(in); - xxx_flags_lo.expectLength(holders.size()); - xxx_flags_lo.readFrom(in); - assert((flagMask & overflowMask) == overflowMask); - for (Attribute.Holder h : holders) { - int flags = xxx_flags_lo.getInt(); - h.flags = flags; - if ((flags & overflowMask) != 0) - overflowHolderCount += 1; - } - - // For each holder with overflow attrs, read a count. - xxx_attr_count.expectLength(overflowHolderCount); - xxx_attr_count.readFrom(in); - xxx_attr_indexes.expectLength(xxx_attr_count.getIntTotal()); - xxx_attr_indexes.readFrom(in); - - // Now it's time to check flag bits that indicate attributes. - // We accumulate (a) a list of attribute types for each holder - // (class/field/method/code), and also we accumulate (b) a total - // count for each attribute type. - int[] totalCounts = new int[defs.length]; - for (Attribute.Holder h : holders) { - assert(h.attributes == null); - // System.out.println("flags="+h.flags+" using fm="+flagMask); - long attrBits = ((h.flags & flagMask) << 32) >>> 32; - // Clean up the flags now. - h.flags -= (int)attrBits; // strip attr bits - assert(h.flags == (char)h.flags); // 16 bits only now - assert((ctype != Constants.ATTR_CONTEXT_CODE) || h.flags == 0); - if (haveLongFlags) - attrBits += (long)xxx_flags_hi.getInt() << 32; - if (attrBits == 0) continue; // no attrs on this guy - - int noa = 0; // number of overflow attrs - long overflowBit = (attrBits & overflowMask); - assert(overflowBit >= 0); - attrBits -= overflowBit; - if (overflowBit != 0) { - noa = xxx_attr_count.getInt(); - } - - int nfa = 0; // number of flag attrs - long bits = attrBits; - for (int ai = 0; bits != 0; ai++) { - if ((bits & (1L< ha = new ArrayList<>(nfa + noa); - h.attributes = ha; - bits = attrBits; // iterate again - for (int ai = 0; bits != 0; ai++) { - if ((bits & (1L< 0; noa--) { - int ai = xxx_attr_indexes.getInt(); - totalCounts[ai] += 1; - // This definition index is live in this holder. - if (defs[ai] == null) badAttrIndex(ai, ctype); - Attribute canonical = defs[ai].canonicalInstance(); - ha.add(canonical); - } - } - - xxx_flags_hi.doneDisbursing(); - xxx_flags_lo.doneDisbursing(); - xxx_attr_count.doneDisbursing(); - xxx_attr_indexes.doneDisbursing(); - - // Now each holder has a list of canonical attribute instances. - // For layouts with no elements, we are done. However, for - // layouts with bands, we must replace each canonical (empty) - // instance with a value-bearing one, initialized from the - // appropriate bands. - - // Make a small pass to detect and read backward call counts. - int callCounts = 0; - for (boolean predef = true; ; predef = false) { - for (int ai = 0; ai < defs.length; ai++) { - Attribute.Layout def = defs[ai]; - if (def == null) continue; // unused index - if (predef != isPredefinedAttr(ctype, ai)) - continue; // wrong pass - int totalCount = totalCounts[ai]; - if (totalCount == 0) - continue; // irrelevant - Attribute.Layout.Element[] cbles = def.getCallables(); - for (int j = 0; j < cbles.length; j++) { - assert(cbles[j].kind == Attribute.EK_CBLE); - if (cbles[j].flagTest(Attribute.EF_BACK)) - callCounts += 1; - } - } - if (!predef) break; - } - xxx_attr_calls.expectLength(callCounts); - xxx_attr_calls.readFrom(in); - - // Finally, size all the attribute bands. - for (boolean predef = true; ; predef = false) { - for (int ai = 0; ai < defs.length; ai++) { - Attribute.Layout def = defs[ai]; - if (def == null) continue; // unused index - if (predef != isPredefinedAttr(ctype, ai)) - continue; // wrong pass - int totalCount = totalCounts[ai]; - Band[] ab = attrBandTable.get(def); - if (def == attrInnerClassesEmpty) { - // Special case. - // Size the bands as if using the following layout: - // [RCH TI[ (0)[] ()[RCNH RUNH] ]]. - class_InnerClasses_N.expectLength(totalCount); - class_InnerClasses_N.readFrom(in); - int tupleCount = class_InnerClasses_N.getIntTotal(); - class_InnerClasses_RC.expectLength(tupleCount); - class_InnerClasses_RC.readFrom(in); - class_InnerClasses_F.expectLength(tupleCount); - class_InnerClasses_F.readFrom(in); - // Drop remaining columns wherever flags are zero: - tupleCount -= class_InnerClasses_F.getIntCount(0); - class_InnerClasses_outer_RCN.expectLength(tupleCount); - class_InnerClasses_outer_RCN.readFrom(in); - class_InnerClasses_name_RUN.expectLength(tupleCount); - class_InnerClasses_name_RUN.readFrom(in); - } else if (!optDebugBands && totalCount == 0) { - // Expect no elements at all. Skip quickly. however if we - // are debugging bands, read all bands regardless - for (int j = 0; j < ab.length; j++) { - ab[j].doneWithUnusedBand(); - } - } else { - // Read these bands in sequence. - boolean hasCallables = def.hasCallables(); - if (!hasCallables) { - readAttrBands(def.elems, totalCount, new int[0], ab); - } else { - Attribute.Layout.Element[] cbles = def.getCallables(); - // At first, record initial calls. - // Later, forward calls may also accumulate here: - int[] forwardCounts = new int[cbles.length]; - forwardCounts[0] = totalCount; - for (int j = 0; j < cbles.length; j++) { - assert(cbles[j].kind == Attribute.EK_CBLE); - int entryCount = forwardCounts[j]; - forwardCounts[j] = -1; // No more, please! - if (totalCount > 0 && cbles[j].flagTest(Attribute.EF_BACK)) - entryCount += xxx_attr_calls.getInt(); - readAttrBands(cbles[j].body, entryCount, forwardCounts, ab); - } - } - // mark them read, to satisfy asserts - if (optDebugBands && totalCount == 0) { - for (int j = 0; j < ab.length; j++) { - ab[j].doneDisbursing(); - } - } - } - } - if (!predef) break; - } - xxx_attr_calls.doneDisbursing(); - } - - void badAttrIndex(int ai, int ctype) throws IOException { - throw new IOException("Unknown attribute index "+ai+" for "+ - Constants.ATTR_CONTEXT_NAME[ctype]+" attribute"); - } - - void readAttrs(int ctype, Collection holders) - throws IOException { - // Decode band values into attributes. - Set sawDefs = new HashSet<>(); - ByteArrayOutputStream buf = new ByteArrayOutputStream(); - for (final Attribute.Holder h : holders) { - if (h.attributes == null) continue; - for (ListIterator j = h.attributes.listIterator(); j.hasNext(); ) { - Attribute a = j.next(); - Attribute.Layout def = a.layout(); - if (def.bandCount == 0) { - if (def == attrInnerClassesEmpty) { - // Special logic to read this attr. - readLocalInnerClasses((Package.Class) h); - continue; - } - // Canonical empty attr works fine (e.g., Synthetic). - continue; - } - sawDefs.add(def); - boolean isCV = (ctype == Constants.ATTR_CONTEXT_FIELD && def == attrConstantValue); - if (isCV) setConstantValueIndex((Package.Class.Field)h); - if (verbose > 2) - Utils.log.fine("read "+a+" in "+h); - final Band[] ab = attrBandTable.get(def); - // Read one attribute of type def from ab into a byte array. - buf.reset(); - Object fixups = a.unparse(new Attribute.ValueStream() { - public int getInt(int bandIndex) { - return ((IntBand) ab[bandIndex]).getInt(); - } - public ConstantPool.Entry getRef(int bandIndex) { - return ((CPRefBand) ab[bandIndex]).getRef(); - } - public int decodeBCI(int bciCode) { - Code code = (Code) h; - return code.decodeBCI(bciCode); - } - }, buf); - // Replace the canonical attr with the one just read. - j.set(a.addContent(buf.toByteArray(), fixups)); - if (isCV) setConstantValueIndex(null); // clean up - } - } - - // Mark the bands we just used as done disbursing. - for (Attribute.Layout def : sawDefs) { - if (def == null) continue; // unused index - Band[] ab = attrBandTable.get(def); - for (int j = 0; j < ab.length; j++) { - ab[j].doneDisbursing(); - } - } - - if (ctype == Constants.ATTR_CONTEXT_CLASS) { - class_InnerClasses_N.doneDisbursing(); - class_InnerClasses_RC.doneDisbursing(); - class_InnerClasses_F.doneDisbursing(); - class_InnerClasses_outer_RCN.doneDisbursing(); - class_InnerClasses_name_RUN.doneDisbursing(); - } - - MultiBand xxx_attr_bands = attrBands[ctype]; - for (int i = 0; i < xxx_attr_bands.size(); i++) { - Band b = xxx_attr_bands.get(i); - if (b instanceof MultiBand) - b.doneDisbursing(); - } - xxx_attr_bands.doneDisbursing(); - } - - private - void readAttrBands(Attribute.Layout.Element[] elems, - int count, int[] forwardCounts, - Band[] ab) - throws IOException { - for (int i = 0; i < elems.length; i++) { - Attribute.Layout.Element e = elems[i]; - Band eBand = null; - if (e.hasBand()) { - eBand = ab[e.bandIndex]; - eBand.expectLength(count); - eBand.readFrom(in); - } - switch (e.kind) { - case Attribute.EK_REPL: - // Recursive call. - int repCount = ((IntBand)eBand).getIntTotal(); - // Note: getIntTotal makes an extra pass over this band. - readAttrBands(e.body, repCount, forwardCounts, ab); - break; - case Attribute.EK_UN: - int remainingCount = count; - for (int j = 0; j < e.body.length; j++) { - int caseCount; - if (j == e.body.length-1) { - caseCount = remainingCount; - } else { - caseCount = 0; - for (int j0 = j; - (j == j0) - || (j < e.body.length - && e.body[j].flagTest(Attribute.EF_BACK)); - j++) { - caseCount += ((IntBand)eBand).getIntCount(e.body[j].value); - } - --j; // back up to last occurrence of this body - } - remainingCount -= caseCount; - readAttrBands(e.body[j].body, caseCount, forwardCounts, ab); - } - assert(remainingCount == 0); - break; - case Attribute.EK_CALL: - assert(e.body.length == 1); - assert(e.body[0].kind == Attribute.EK_CBLE); - if (!e.flagTest(Attribute.EF_BACK)) { - // Backward calls are pre-counted, but forwards are not. - // Push the present count forward. - assert(forwardCounts[e.value] >= 0); - forwardCounts[e.value] += count; - } - break; - case Attribute.EK_CBLE: - assert(false); - break; - } - } - } - - void readByteCodes() throws IOException { - // bc_bands: - // *bc_codes :BYTE1 - // *bc_case_count :UNSIGNED5 - // *bc_case_value :DELTA5 - // *bc_byte :BYTE1 - // *bc_short :DELTA5 - // *bc_local :UNSIGNED5 - // *bc_label :BRANCH5 - // *bc_intref :DELTA5 (cp_Int) - // *bc_floatref :DELTA5 (cp_Float) - // *bc_longref :DELTA5 (cp_Long) - // *bc_doubleref :DELTA5 (cp_Double) - // *bc_stringref :DELTA5 (cp_String) - // *bc_classref :UNSIGNED5 (current class or cp_Class) - // *bc_fieldref :DELTA5 (cp_Field) - // *bc_methodref :UNSIGNED5 (cp_Method) - // *bc_imethodref :DELTA5 (cp_Imethod) - // *bc_thisfield :UNSIGNED5 (cp_Field, only for current class) - // *bc_superfield :UNSIGNED5 (cp_Field, only for current super) - // *bc_thismethod :UNSIGNED5 (cp_Method, only for current class) - // *bc_supermethod :UNSIGNED5 (cp_Method, only for current super) - // *bc_initref :UNSIGNED5 (cp_Field, only for most recent new) - // *bc_escref :UNSIGNED5 (cp_All) - // *bc_escrefsize :UNSIGNED5 - // *bc_escsize :UNSIGNED5 - // *bc_escbyte :BYTE1 - bc_codes.elementCountForDebug = allCodes.length; - bc_codes.setInputStreamFrom(in); - readByteCodeOps(); // reads from bc_codes and bc_case_count - bc_codes.doneDisbursing(); - - // All the operand bands have now been sized. Read them all in turn. - Band[] operand_bands = { - bc_case_value, - bc_byte, bc_short, - bc_local, bc_label, - bc_intref, bc_floatref, - bc_longref, bc_doubleref, bc_stringref, - bc_loadablevalueref, - bc_classref, bc_fieldref, - bc_methodref, bc_imethodref, - bc_indyref, - bc_thisfield, bc_superfield, - bc_thismethod, bc_supermethod, - bc_initref, - bc_escref, bc_escrefsize, bc_escsize - }; - for (int i = 0; i < operand_bands.length; i++) { - operand_bands[i].readFrom(in); - } - bc_escbyte.expectLength(bc_escsize.getIntTotal()); - bc_escbyte.readFrom(in); - - expandByteCodeOps(); - - // Done fetching values from operand bands: - bc_case_count.doneDisbursing(); - for (int i = 0; i < operand_bands.length; i++) { - operand_bands[i].doneDisbursing(); - } - bc_escbyte.doneDisbursing(); - bc_bands.doneDisbursing(); - - // We must delay the parsing of Code attributes until we - // have a complete model of bytecodes, for BCI encodings. - readAttrs(Constants.ATTR_CONTEXT_CODE, codesWithFlags); - // Ditto for exception handlers in codes. - fixupCodeHandlers(); - // Now we can finish with class_bands; cf. readClasses(). - code_bands.doneDisbursing(); - class_bands.doneDisbursing(); - } - - private void readByteCodeOps() throws IOException { - // scratch buffer for collecting code:: - byte[] buf = new byte[1<<12]; - // record of all switch opcodes (these are variable-length) - List allSwitchOps = new ArrayList<>(); - for (int k = 0; k < allCodes.length; k++) { - Code c = allCodes[k]; - scanOneMethod: - for (int i = 0; ; i++) { - int bc = bc_codes.getByte(); - if (i + 10 > buf.length) buf = realloc(buf); - buf[i] = (byte)bc; - boolean isWide = false; - if (bc == Constants._wide) { - bc = bc_codes.getByte(); - buf[++i] = (byte)bc; - isWide = true; - } - assert(bc == (0xFF & bc)); - // Adjust expectations of various band sizes. - switch (bc) { - case Constants._tableswitch: - case Constants._lookupswitch: - bc_case_count.expectMoreLength(1); - allSwitchOps.add(bc); - break; - case Constants._iinc: - bc_local.expectMoreLength(1); - if (isWide) - bc_short.expectMoreLength(1); - else - bc_byte.expectMoreLength(1); - break; - case Constants._sipush: - bc_short.expectMoreLength(1); - break; - case Constants._bipush: - bc_byte.expectMoreLength(1); - break; - case Constants._newarray: - bc_byte.expectMoreLength(1); - break; - case Constants._multianewarray: - assert(getCPRefOpBand(bc) == bc_classref); - bc_classref.expectMoreLength(1); - bc_byte.expectMoreLength(1); - break; - case Constants._ref_escape: - bc_escrefsize.expectMoreLength(1); - bc_escref.expectMoreLength(1); - break; - case Constants._byte_escape: - bc_escsize.expectMoreLength(1); - // bc_escbyte will have to be counted too - break; - default: - if (Instruction.isInvokeInitOp(bc)) { - bc_initref.expectMoreLength(1); - break; - } - if (Instruction.isSelfLinkerOp(bc)) { - CPRefBand bc_which = selfOpRefBand(bc); - bc_which.expectMoreLength(1); - break; - } - if (Instruction.isBranchOp(bc)) { - bc_label.expectMoreLength(1); - break; - } - if (Instruction.isCPRefOp(bc)) { - CPRefBand bc_which = getCPRefOpBand(bc); - bc_which.expectMoreLength(1); - assert(bc != Constants._multianewarray); // handled elsewhere - break; - } - if (Instruction.isLocalSlotOp(bc)) { - bc_local.expectMoreLength(1); - break; - } - break; - case Constants._end_marker: - { - // Transfer from buf to a more permanent place: - c.bytes = realloc(buf, i); - break scanOneMethod; - } - } - } - } - - // To size instruction bands correctly, we need info on switches: - bc_case_count.readFrom(in); - for (Integer i : allSwitchOps) { - int bc = i.intValue(); - int caseCount = bc_case_count.getInt(); - bc_label.expectMoreLength(1+caseCount); // default label + cases - bc_case_value.expectMoreLength(bc == Constants._tableswitch ? 1 : caseCount); - } - bc_case_count.resetForSecondPass(); - } - - private void expandByteCodeOps() throws IOException { - // scratch buffer for collecting code: - byte[] buf = new byte[1<<12]; - // scratch buffer for collecting instruction boundaries: - int[] insnMap = new int[1<<12]; - // list of label carriers, for label decoding post-pass: - int[] labels = new int[1<<10]; - // scratch buffer for registering CP refs: - Fixups fixupBuf = new Fixups(); - - for (int k = 0; k < allCodes.length; k++) { - Code code = allCodes[k]; - byte[] codeOps = code.bytes; - code.bytes = null; // just for now, while we accumulate bits - - Package.Class curClass = code.thisClass(); - - Set ldcRefSet = ldcRefMap.get(curClass); - if (ldcRefSet == null) - ldcRefMap.put(curClass, ldcRefSet = new HashSet<>()); - - ConstantPool.ClassEntry thisClass = curClass.thisClass; - ConstantPool.ClassEntry superClass = curClass.superClass; - ConstantPool.ClassEntry newClass = null; // class of last _new opcode - - int pc = 0; // fill pointer in buf; actual bytecode PC - int numInsns = 0; - int numLabels = 0; - boolean hasEscs = false; - fixupBuf.clear(); - for (int i = 0; i < codeOps.length; i++) { - int bc = Instruction.getByte(codeOps, i); - int curPC = pc; - insnMap[numInsns++] = curPC; - if (pc + 10 > buf.length) buf = realloc(buf); - if (numInsns+10 > insnMap.length) insnMap = realloc(insnMap); - if (numLabels+10 > labels.length) labels = realloc(labels); - boolean isWide = false; - if (bc == Constants._wide) { - buf[pc++] = (byte) bc; - bc = Instruction.getByte(codeOps, ++i); - isWide = true; - } - switch (bc) { - case Constants._tableswitch: // apc: (df, lo, hi, (hi-lo+1)*(label)) - case Constants._lookupswitch: // apc: (df, nc, nc*(case, label)) - { - int caseCount = bc_case_count.getInt(); - while ((pc + 30 + caseCount*8) > buf.length) - buf = realloc(buf); - buf[pc++] = (byte) bc; - //initialize apc, df, lo, hi bytes to reasonable bits: - Arrays.fill(buf, pc, pc+30, (byte)0); - Instruction.Switch isw = (Instruction.Switch) - Instruction.at(buf, curPC); - //isw.setDefaultLabel(getLabel(bc_label, code, curPC)); - isw.setCaseCount(caseCount); - if (bc == Constants._tableswitch) { - isw.setCaseValue(0, bc_case_value.getInt()); - } else { - for (int j = 0; j < caseCount; j++) { - isw.setCaseValue(j, bc_case_value.getInt()); - } - } - // Make our getLabel calls later. - labels[numLabels++] = curPC; - pc = isw.getNextPC(); - continue; - } - case Constants._iinc: - { - buf[pc++] = (byte) bc; - int local = bc_local.getInt(); - int delta; - if (isWide) { - delta = bc_short.getInt(); - Instruction.setShort(buf, pc, local); pc += 2; - Instruction.setShort(buf, pc, delta); pc += 2; - } else { - delta = (byte) bc_byte.getByte(); - buf[pc++] = (byte)local; - buf[pc++] = (byte)delta; - } - continue; - } - case Constants._sipush: - { - int val = bc_short.getInt(); - buf[pc++] = (byte) bc; - Instruction.setShort(buf, pc, val); pc += 2; - continue; - } - case Constants._bipush: - case Constants._newarray: - { - int val = bc_byte.getByte(); - buf[pc++] = (byte) bc; - buf[pc++] = (byte) val; - continue; - } - case Constants._ref_escape: - { - // Note that insnMap has one entry for this. - hasEscs = true; - int size = bc_escrefsize.getInt(); - ConstantPool.Entry ref = bc_escref.getRef(); - if (size == 1) ldcRefSet.add(ref); - int fmt; - switch (size) { - case 1: fixupBuf.addU1(pc, ref); break; - case 2: fixupBuf.addU2(pc, ref); break; - default: assert(false); fmt = 0; - } - buf[pc+0] = buf[pc+1] = 0; - pc += size; - } - continue; - case Constants._byte_escape: - { - // Note that insnMap has one entry for all these bytes. - hasEscs = true; - int size = bc_escsize.getInt(); - while ((pc + size) > buf.length) - buf = realloc(buf); - while (size-- > 0) { - buf[pc++] = (byte) bc_escbyte.getByte(); - } - } - continue; - default: - if (Instruction.isInvokeInitOp(bc)) { - int idx = (bc - Constants._invokeinit_op); - int origBC = Constants._invokespecial; - ConstantPool.ClassEntry classRef; - switch (idx) { - case Constants._invokeinit_self_option: - classRef = thisClass; break; - case Constants._invokeinit_super_option: - classRef = superClass; break; - default: - assert(idx == Constants._invokeinit_new_option); - classRef = newClass; break; - } - buf[pc++] = (byte) origBC; - int coding = bc_initref.getInt(); - // Find the nth overloading of in classRef. - ConstantPool.MemberEntry ref = pkg.cp.getOverloadingForIndex(Constants.CONSTANT_Methodref, classRef, "", coding); - fixupBuf.addU2(pc, ref); - buf[pc+0] = buf[pc+1] = 0; - pc += 2; - assert(Instruction.opLength(origBC) == (pc - curPC)); - continue; - } - if (Instruction.isSelfLinkerOp(bc)) { - int idx = (bc - Constants._self_linker_op); - boolean isSuper = (idx >= Constants._self_linker_super_flag); - if (isSuper) idx -= Constants._self_linker_super_flag; - boolean isAload = (idx >= Constants._self_linker_aload_flag); - if (isAload) idx -= Constants._self_linker_aload_flag; - int origBC = Constants._first_linker_op + idx; - boolean isField = Instruction.isFieldOp(origBC); - CPRefBand bc_which; - ConstantPool.ClassEntry which_cls = isSuper ? superClass : thisClass; - ConstantPool.Index which_ix; - if (isField) { - bc_which = isSuper ? bc_superfield : bc_thisfield; - which_ix = pkg.cp.getMemberIndex(Constants.CONSTANT_Fieldref, which_cls); - } else { - bc_which = isSuper ? bc_supermethod : bc_thismethod; - which_ix = pkg.cp.getMemberIndex(Constants.CONSTANT_Methodref, which_cls); - } - assert(bc_which == selfOpRefBand(bc)); - ConstantPool.MemberEntry ref = (ConstantPool.MemberEntry) bc_which.getRef(which_ix); - if (isAload) { - buf[pc++] = (byte) Constants._aload_0; - curPC = pc; - // Note: insnMap keeps the _aload_0 separate. - insnMap[numInsns++] = curPC; - } - buf[pc++] = (byte) origBC; - fixupBuf.addU2(pc, ref); - buf[pc+0] = buf[pc+1] = 0; - pc += 2; - assert(Instruction.opLength(origBC) == (pc - curPC)); - continue; - } - if (Instruction.isBranchOp(bc)) { - buf[pc++] = (byte) bc; - assert(!isWide); // no wide prefix for branches - int nextPC = curPC + Instruction.opLength(bc); - // Make our getLabel calls later. - labels[numLabels++] = curPC; - //Instruction.at(buf, curPC).setBranchLabel(getLabel(bc_label, code, curPC)); - while (pc < nextPC) buf[pc++] = 0; - continue; - } - if (Instruction.isCPRefOp(bc)) { - CPRefBand bc_which = getCPRefOpBand(bc); - ConstantPool.Entry ref = bc_which.getRef(); - if (ref == null) { - if (bc_which == bc_classref) { - // Shorthand for class self-references. - ref = thisClass; - } else { - assert(false); - } - } - int origBC = bc; - int size = 2; - switch (bc) { - case Constants._invokestatic_int: - origBC = Constants._invokestatic; - break; - case Constants._invokespecial_int: - origBC = Constants._invokespecial; - break; - case Constants._ildc: - case Constants._cldc: - case Constants._fldc: - case Constants._sldc: - case Constants._qldc: - origBC = Constants._ldc; - size = 1; - ldcRefSet.add(ref); - break; - case Constants._ildc_w: - case Constants._cldc_w: - case Constants._fldc_w: - case Constants._sldc_w: - case Constants._qldc_w: - origBC = Constants._ldc_w; - break; - case Constants._lldc2_w: - case Constants._dldc2_w: - origBC = Constants._ldc2_w; - break; - case Constants._new: - newClass = (ConstantPool.ClassEntry) ref; - break; - } - buf[pc++] = (byte) origBC; - int fmt; - switch (size) { - case 1: fixupBuf.addU1(pc, ref); break; - case 2: fixupBuf.addU2(pc, ref); break; - default: assert(false); fmt = 0; - } - buf[pc+0] = buf[pc+1] = 0; - pc += size; - if (origBC == Constants._multianewarray) { - // Copy the trailing byte also. - int val = bc_byte.getByte(); - buf[pc++] = (byte) val; - } else if (origBC == Constants._invokeinterface) { - int argSize = ((ConstantPool.MemberEntry)ref).descRef.typeRef.computeSize(true); - buf[pc++] = (byte)( 1 + argSize ); - buf[pc++] = 0; - } else if (origBC == Constants._invokedynamic) { - buf[pc++] = 0; - buf[pc++] = 0; - } - assert(Instruction.opLength(origBC) == (pc - curPC)); - continue; - } - if (Instruction.isLocalSlotOp(bc)) { - buf[pc++] = (byte) bc; - int local = bc_local.getInt(); - if (isWide) { - Instruction.setShort(buf, pc, local); - pc += 2; - if (bc == Constants._iinc) { - int iVal = bc_short.getInt(); - Instruction.setShort(buf, pc, iVal); - pc += 2; - } - } else { - Instruction.setByte(buf, pc, local); - pc += 1; - if (bc == Constants._iinc) { - int iVal = bc_byte.getByte(); - Instruction.setByte(buf, pc, iVal); - pc += 1; - } - } - assert(Instruction.opLength(bc) == (pc - curPC)); - continue; - } - // Random bytecode. Just copy it. - if (bc >= Constants._bytecode_limit) - Utils.log.warning("unrecognized bytescode "+bc - +" "+Instruction.byteName(bc)); - assert(bc < Constants._bytecode_limit); - buf[pc++] = (byte) bc; - assert(Instruction.opLength(bc) == (pc - curPC)); - continue; - } - } - // now make a permanent copy of the bytecodes - code.setBytes(realloc(buf, pc)); - code.setInstructionMap(insnMap, numInsns); - // fix up labels, now that code has its insnMap - Instruction ibr = null; // temporary branch instruction - for (int i = 0; i < numLabels; i++) { - int curPC = labels[i]; - // (Note: Passing ibr in allows reuse, a speed hack.) - ibr = Instruction.at(code.bytes, curPC, ibr); - if (ibr instanceof Instruction.Switch) { - Instruction.Switch isw = (Instruction.Switch) ibr; - isw.setDefaultLabel(getLabel(bc_label, code, curPC)); - int caseCount = isw.getCaseCount(); - for (int j = 0; j < caseCount; j++) { - isw.setCaseLabel(j, getLabel(bc_label, code, curPC)); - } - } else { - ibr.setBranchLabel(getLabel(bc_label, code, curPC)); - } - } - if (fixupBuf.size() > 0) { - if (verbose > 2) - Utils.log.fine("Fixups in code: "+fixupBuf); - code.addFixups(fixupBuf); - } - } - } -} diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/PackageWriter.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/PackageWriter.java deleted file mode 100644 index 795b28fdc..000000000 --- a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/PackageWriter.java +++ /dev/null @@ -1,1738 +0,0 @@ -/* - * Copyright (c) 2001, 2013, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package net.fabricmc.shade.com.sun.java.util.jar.pack; - -import java.io.IOException; -import java.io.OutputStream; -import java.io.PrintStream; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * Writer for a package file. - * @author John Rose - */ -class PackageWriter extends BandStructure { - Package pkg; - OutputStream finalOut; - Package.Version packageVersion; - - PackageWriter(Package pkg, OutputStream out) throws IOException { - this.pkg = pkg; - this.finalOut = out; - // Caller has specified maximum class file version in the package: - initHighestClassVersion(pkg.getHighestClassVersion()); - } - - void write() throws IOException { - boolean ok = false; - try { - if (verbose > 0) { - Utils.log.info("Setting up constant pool..."); - } - setup(); - - if (verbose > 0) { - Utils.log.info("Packing..."); - } - - // writeFileHeader() is done last, since it has ultimate counts - // writeBandHeaders() is called after all other bands are done - writeConstantPool(); - writeFiles(); - writeAttrDefs(); - writeInnerClasses(); - writeClassesAndByteCodes(); - writeAttrCounts(); - - if (verbose > 1) printCodeHist(); - - // choose codings (fill band_headers if needed) - if (verbose > 0) { - Utils.log.info("Coding..."); - } - all_bands.chooseBandCodings(); - - // now we can write the headers: - writeFileHeader(); - - writeAllBandsTo(finalOut); - - ok = true; - } catch (Exception ee) { - Utils.log.warning("Error on output: "+ee, ee); - //if (verbose > 0) ee.printStackTrace(); - // Write partial output only if we are verbose. - if (verbose > 0) finalOut.close(); - if (ee instanceof IOException) throw (IOException)ee; - if (ee instanceof RuntimeException) throw (RuntimeException)ee; - throw new Error("error packing", ee); - } - } - - Set requiredEntries; // for the CP - Map backCountTable; // for layout callables - int[][] attrCounts; // count attr. occurrences - - void setup() { - requiredEntries = new HashSet<>(); - setArchiveOptions(); - trimClassAttributes(); - collectAttributeLayouts(); - pkg.buildGlobalConstantPool(requiredEntries); - setBandIndexes(); - makeNewAttributeBands(); - collectInnerClasses(); - } - - /* - * Convenience function to choose an archive version based - * on the class file versions observed within the archive - * or set the user defined version preset via properties. - */ - void chooseDefaultPackageVersion() throws IOException { - if (pkg.packageVersion != null) { - packageVersion = pkg.packageVersion; - if (verbose > 0) { - Utils.log.info("package version overridden with: " - + packageVersion); - } - return; - } - - Package.Version highV = getHighestClassVersion(); - // set the package version now - if (highV.lessThan(Constants.JAVA6_MAX_CLASS_VERSION)) { - // There are only old classfiles in this segment or resources - packageVersion = Constants.JAVA5_PACKAGE_VERSION; - } else if (highV.equals(Constants.JAVA6_MAX_CLASS_VERSION) || - (highV.equals(Constants.JAVA7_MAX_CLASS_VERSION) && !pkg.cp.haveExtraTags())) { - // force down the package version if we have jdk7 classes without - // any Indy references, this is because jdk7 class file (51.0) without - // Indy is identical to jdk6 class file (50.0). - packageVersion = Constants.JAVA6_PACKAGE_VERSION; - } else if (highV.equals(Constants.JAVA7_MAX_CLASS_VERSION)) { - packageVersion = Constants.JAVA7_PACKAGE_VERSION; - } else { - // Normal case. Use the newest archive format, when available - packageVersion = Constants.JAVA8_PACKAGE_VERSION; - } - - if (verbose > 0) { - Utils.log.info("Highest version class file: " + highV - + " package version: " + packageVersion); - } - } - - void checkVersion() throws IOException { - assert(packageVersion != null); - - if (packageVersion.lessThan(Constants.JAVA7_PACKAGE_VERSION)) { - // this bit was reserved for future use in previous versions - if (testBit(archiveOptions, Constants.AO_HAVE_CP_EXTRAS)) { - throw new IOException("Format bits for Java 7 must be zero in previous releases"); - } - } - if (testBit(archiveOptions, Constants.AO_UNUSED_MBZ)) { - throw new IOException("High archive option bits are reserved and must be zero: " + Integer.toHexString(archiveOptions)); - } - } - - void setArchiveOptions() { - // Decide on some archive options early. - // Does not decide on: AO_HAVE_SPECIAL_FORMATS, - // AO_HAVE_CP_NUMBERS, AO_HAVE_FILE_HEADERS. - // Also, AO_HAVE_FILE_OPTIONS may be forced on later. - int minModtime = pkg.default_modtime; - int maxModtime = pkg.default_modtime; - int minOptions = -1; - int maxOptions = 0; - - // Import defaults from package (deflate hint, etc.). - archiveOptions |= pkg.default_options; - - for (Package.File file : pkg.files) { - int modtime = file.modtime; - int options = file.options; - - if (minModtime == Constants.NO_MODTIME) { - minModtime = maxModtime = modtime; - } else { - if (minModtime > modtime) minModtime = modtime; - if (maxModtime < modtime) maxModtime = modtime; - } - minOptions &= options; - maxOptions |= options; - } - if (pkg.default_modtime == Constants.NO_MODTIME) { - // Make everything else be a positive offset from here. - pkg.default_modtime = minModtime; - } - if (minModtime != Constants.NO_MODTIME && minModtime != maxModtime) { - // Put them into a band. - archiveOptions |= Constants.AO_HAVE_FILE_MODTIME; - } - // If the archive deflation is set do not bother with each file. - if (!testBit(archiveOptions,Constants.AO_DEFLATE_HINT) && minOptions != -1) { - if (testBit(minOptions, Constants.FO_DEFLATE_HINT)) { - // Every file has the deflate_hint set. - // Set it for the whole archive, and omit options. - archiveOptions |= Constants.AO_DEFLATE_HINT; - minOptions -= Constants.FO_DEFLATE_HINT; - maxOptions -= Constants.FO_DEFLATE_HINT; - } - pkg.default_options |= minOptions; - if (minOptions != maxOptions - || minOptions != pkg.default_options) { - archiveOptions |= Constants.AO_HAVE_FILE_OPTIONS; - } - } - // Decide on default version number (majority rule). - Map verCounts = new HashMap<>(); - int bestCount = 0; - Package.Version bestVersion = null; - for (Package.Class cls : pkg.classes) { - Package.Version version = cls.getVersion(); - int[] var = verCounts.get(version); - if (var == null) { - var = new int[1]; - verCounts.put(version, var); - } - int count = (var[0] += 1); - //System.out.println("version="+version+" count="+count); - if (bestCount < count) { - bestCount = count; - bestVersion = version; - } - } - verCounts.clear(); - if (bestVersion == null) bestVersion = Constants.JAVA_MIN_CLASS_VERSION; // degenerate case - pkg.defaultClassVersion = bestVersion; - if (verbose > 0) - Utils.log.info("Consensus version number in segment is " + bestVersion); - if (verbose > 0) - Utils.log.info("Highest version number in segment is " - + pkg.getHighestClassVersion()); - - // Now add explicit pseudo-attrs. to classes with odd versions. - for (Package.Class cls : pkg.classes) { - if (!cls.getVersion().equals(bestVersion)) { - Attribute a = makeClassFileVersionAttr(cls.getVersion()); - if (verbose > 1) { - Utils.log.fine("Version "+cls.getVersion() + " of " + cls - + " doesn't match package version " - + bestVersion); - } - // Note: Does not add in "natural" order. (Who cares?) - cls.addAttribute(a); - } - } - - // Decide if we are transmitting a huge resource file: - for (Package.File file : pkg.files) { - long len = file.getFileLength(); - if (len != (int)len) { - archiveOptions |= Constants.AO_HAVE_FILE_SIZE_HI; - if (verbose > 0) - Utils.log.info("Note: Huge resource file "+file.getFileName()+" forces 64-bit sizing"); - break; - } - } - - // Decide if code attributes typically have sub-attributes. - // In that case, to preserve compact 1-byte code headers, - // we must declare unconditional presence of code flags. - int cost0 = 0; - int cost1 = 0; - for (Package.Class cls : pkg.classes) { - for (Package.Class.Method m : cls.getMethods()) { - if (m.code != null) { - if (m.code.attributeSize() == 0) { - // cost of a useless unconditional flags byte - cost1 += 1; - } else if (shortCodeHeader(m.code) != LONG_CODE_HEADER) { - // cost of inflating a short header - cost0 += 3; - } - } - } - } - if (cost0 > cost1) { - archiveOptions |= Constants.AO_HAVE_ALL_CODE_FLAGS; - } - if (verbose > 0) - Utils.log.info("archiveOptions = " - +"0b"+Integer.toBinaryString(archiveOptions)); - } - - void writeFileHeader() throws IOException { - chooseDefaultPackageVersion(); - writeArchiveMagic(); - writeArchiveHeader(); - } - - // Local routine used to format fixed-format scalars - // in the file_header: - private void putMagicInt32(int val) throws IOException { - int res = val; - for (int i = 0; i < 4; i++) { - archive_magic.putByte(0xFF & (res >>> 24)); - res <<= 8; - } - } - - void writeArchiveMagic() throws IOException { - putMagicInt32(pkg.magic); - } - - void writeArchiveHeader() throws IOException { - // for debug only: number of words optimized away - int headerSizeForDebug = AH_LENGTH_MIN; - - // AO_HAVE_SPECIAL_FORMATS is set if non-default - // coding techniques are used, or if there are - // compressor-defined attributes transmitted. - boolean haveSpecial = testBit(archiveOptions, Constants.AO_HAVE_SPECIAL_FORMATS); - if (!haveSpecial) { - haveSpecial |= (band_headers.length() != 0); - haveSpecial |= (attrDefsWritten.length != 0); - if (haveSpecial) - archiveOptions |= Constants.AO_HAVE_SPECIAL_FORMATS; - } - if (haveSpecial) - headerSizeForDebug += AH_SPECIAL_FORMAT_LEN; - - // AO_HAVE_FILE_HEADERS is set if there is any - // file or segment envelope information present. - boolean haveFiles = testBit(archiveOptions, Constants.AO_HAVE_FILE_HEADERS); - if (!haveFiles) { - haveFiles |= (archiveNextCount > 0); - haveFiles |= (pkg.default_modtime != Constants.NO_MODTIME); - if (haveFiles) - archiveOptions |= Constants.AO_HAVE_FILE_HEADERS; - } - if (haveFiles) - headerSizeForDebug += AH_FILE_HEADER_LEN; - - // AO_HAVE_CP_NUMBERS is set if there are any numbers - // in the global constant pool. (Numbers are in 15% of classes.) - boolean haveNumbers = testBit(archiveOptions, Constants.AO_HAVE_CP_NUMBERS); - if (!haveNumbers) { - haveNumbers |= pkg.cp.haveNumbers(); - if (haveNumbers) - archiveOptions |= Constants.AO_HAVE_CP_NUMBERS; - } - if (haveNumbers) - headerSizeForDebug += AH_CP_NUMBER_LEN; - - // AO_HAVE_CP_EXTRAS is set if there are constant pool entries - // beyond the Java 6 version of the class file format. - boolean haveCPExtra = testBit(archiveOptions, Constants.AO_HAVE_CP_EXTRAS); - if (!haveCPExtra) { - haveCPExtra |= pkg.cp.haveExtraTags(); - if (haveCPExtra) - archiveOptions |= Constants.AO_HAVE_CP_EXTRAS; - } - if (haveCPExtra) - headerSizeForDebug += AH_CP_EXTRA_LEN; - - // the archiveOptions are all initialized, sanity check now!. - checkVersion(); - - archive_header_0.putInt(packageVersion.minor); - archive_header_0.putInt(packageVersion.major); - if (verbose > 0) - Utils.log.info("Package Version for this segment:" + packageVersion); - archive_header_0.putInt(archiveOptions); // controls header format - assert(archive_header_0.length() == AH_LENGTH_0); - - final int DUMMY = 0; - if (haveFiles) { - assert(archive_header_S.length() == AH_ARCHIVE_SIZE_HI); - archive_header_S.putInt(DUMMY); // (archiveSize1 >>> 32) - assert(archive_header_S.length() == AH_ARCHIVE_SIZE_LO); - archive_header_S.putInt(DUMMY); // (archiveSize1 >>> 0) - assert(archive_header_S.length() == AH_LENGTH_S); - } - - // Done with unsized part of header.... - - if (haveFiles) { - archive_header_1.putInt(archiveNextCount); // usually zero - archive_header_1.putInt(pkg.default_modtime); - archive_header_1.putInt(pkg.files.size()); - } else { - assert(pkg.files.isEmpty()); - } - - if (haveSpecial) { - archive_header_1.putInt(band_headers.length()); - archive_header_1.putInt(attrDefsWritten.length); - } else { - assert(band_headers.length() == 0); - assert(attrDefsWritten.length == 0); - } - - writeConstantPoolCounts(haveNumbers, haveCPExtra); - - archive_header_1.putInt(pkg.getAllInnerClasses().size()); - archive_header_1.putInt(pkg.defaultClassVersion.minor); - archive_header_1.putInt(pkg.defaultClassVersion.major); - archive_header_1.putInt(pkg.classes.size()); - - // Sanity: Make sure we came out to 29 (less optional fields): - assert(archive_header_0.length() + - archive_header_S.length() + - archive_header_1.length() - == headerSizeForDebug); - - // Figure out all the sizes now, first cut: - archiveSize0 = 0; - archiveSize1 = all_bands.outputSize(); - // Second cut: - archiveSize0 += archive_magic.outputSize(); - archiveSize0 += archive_header_0.outputSize(); - archiveSize0 += archive_header_S.outputSize(); - // Make the adjustments: - archiveSize1 -= archiveSize0; - - // Patch the header: - if (haveFiles) { - int archiveSizeHi = (int)(archiveSize1 >>> 32); - int archiveSizeLo = (int)(archiveSize1 >>> 0); - archive_header_S.patchValue(AH_ARCHIVE_SIZE_HI, archiveSizeHi); - archive_header_S.patchValue(AH_ARCHIVE_SIZE_LO, archiveSizeLo); - int zeroLen = UNSIGNED5.getLength(DUMMY); - archiveSize0 += UNSIGNED5.getLength(archiveSizeHi) - zeroLen; - archiveSize0 += UNSIGNED5.getLength(archiveSizeLo) - zeroLen; - } - if (verbose > 1) - Utils.log.fine("archive sizes: "+ - archiveSize0+"+"+archiveSize1); - assert(all_bands.outputSize() == archiveSize0+archiveSize1); - } - - void writeConstantPoolCounts(boolean haveNumbers, boolean haveCPExtra) throws IOException { - for (byte tag : ConstantPool.TAGS_IN_ORDER) { - int count = pkg.cp.getIndexByTag(tag).size(); - switch (tag) { - case Constants.CONSTANT_Utf8: - // The null string is always first. - if (count > 0) - assert(pkg.cp.getIndexByTag(tag).get(0) - == ConstantPool.getUtf8Entry("")); - break; - - case Constants.CONSTANT_Integer: - case Constants.CONSTANT_Float: - case Constants.CONSTANT_Long: - case Constants.CONSTANT_Double: - // Omit counts for numbers if possible. - if (!haveNumbers) { - assert(count == 0); - continue; - } - break; - - case Constants.CONSTANT_MethodHandle: - case Constants.CONSTANT_MethodType: - case Constants.CONSTANT_InvokeDynamic: - case Constants.CONSTANT_BootstrapMethod: - // Omit counts for newer entities if possible. - if (!haveCPExtra) { - assert(count == 0); - continue; - } - break; - } - archive_header_1.putInt(count); - } - } - - protected ConstantPool.Index getCPIndex(byte tag) { - return pkg.cp.getIndexByTag(tag); - } - -// (The following observations are out of date; they apply only to -// "banding" the constant pool itself. Later revisions of this algorithm -// applied the banding technique to every part of the package file, -// applying the benefits more broadly.) - -// Note: Keeping the data separate in passes (or "bands") allows the -// compressor to issue significantly shorter indexes for repeated data. -// The difference in zipped size is 4%, which is remarkable since the -// unzipped sizes are the same (only the byte order differs). - -// After moving similar data into bands, it becomes natural to delta-encode -// each band. (This is especially useful if we sort the constant pool first.) -// Delta encoding saves an extra 5% in the output size (13% of the CP itself). -// Because a typical delta usees much less data than a byte, the savings after -// zipping is even better: A zipped delta-encoded package is 8% smaller than -// a zipped non-delta-encoded package. Thus, in the zipped file, a banded, -// delta-encoded constant pool saves over 11% (of the total file size) compared -// with a zipped unbanded file. - - void writeConstantPool() throws IOException { - ConstantPool.IndexGroup cp = pkg.cp; - - if (verbose > 0) Utils.log.info("Writing CP"); - - for (byte tag : ConstantPool.TAGS_IN_ORDER) { - ConstantPool.Index index = cp.getIndexByTag(tag); - - ConstantPool.Entry[] cpMap = index.cpMap; - if (verbose > 0) - Utils.log.info("Writing "+cpMap.length+" "+ConstantPool.tagName(tag)+" entries..."); - - if (optDumpBands) { - try (PrintStream ps = new PrintStream(getDumpStream(index, ".idx"))) { - printArrayTo(ps, cpMap, 0, cpMap.length); - } - } - - switch (tag) { - case Constants.CONSTANT_Utf8: - writeUtf8Bands(cpMap); - break; - case Constants.CONSTANT_Integer: - for (int i = 0; i < cpMap.length; i++) { - ConstantPool.NumberEntry e = (ConstantPool.NumberEntry) cpMap[i]; - int x = ((Integer)e.numberValue()).intValue(); - cp_Int.putInt(x); - } - break; - case Constants.CONSTANT_Float: - for (int i = 0; i < cpMap.length; i++) { - ConstantPool.NumberEntry e = (ConstantPool.NumberEntry) cpMap[i]; - float fx = ((Float)e.numberValue()).floatValue(); - int x = Float.floatToIntBits(fx); - cp_Float.putInt(x); - } - break; - case Constants.CONSTANT_Long: - for (int i = 0; i < cpMap.length; i++) { - ConstantPool.NumberEntry e = (ConstantPool.NumberEntry) cpMap[i]; - long x = ((Long)e.numberValue()).longValue(); - cp_Long_hi.putInt((int)(x >>> 32)); - cp_Long_lo.putInt((int)(x >>> 0)); - } - break; - case Constants.CONSTANT_Double: - for (int i = 0; i < cpMap.length; i++) { - ConstantPool.NumberEntry e = (ConstantPool.NumberEntry) cpMap[i]; - double dx = ((Double)e.numberValue()).doubleValue(); - long x = Double.doubleToLongBits(dx); - cp_Double_hi.putInt((int)(x >>> 32)); - cp_Double_lo.putInt((int)(x >>> 0)); - } - break; - case Constants.CONSTANT_String: - for (int i = 0; i < cpMap.length; i++) { - ConstantPool.StringEntry e = (ConstantPool.StringEntry) cpMap[i]; - cp_String.putRef(e.ref); - } - break; - case Constants.CONSTANT_Class: - for (int i = 0; i < cpMap.length; i++) { - ConstantPool.ClassEntry e = (ConstantPool.ClassEntry) cpMap[i]; - cp_Class.putRef(e.ref); - } - break; - case Constants.CONSTANT_Signature: - writeSignatureBands(cpMap); - break; - case Constants.CONSTANT_NameandType: - for (int i = 0; i < cpMap.length; i++) { - ConstantPool.DescriptorEntry e = (ConstantPool.DescriptorEntry) cpMap[i]; - cp_Descr_name.putRef(e.nameRef); - cp_Descr_type.putRef(e.typeRef); - } - break; - case Constants.CONSTANT_Fieldref: - writeMemberRefs(tag, cpMap, cp_Field_class, cp_Field_desc); - break; - case Constants.CONSTANT_Methodref: - writeMemberRefs(tag, cpMap, cp_Method_class, cp_Method_desc); - break; - case Constants.CONSTANT_InterfaceMethodref: - writeMemberRefs(tag, cpMap, cp_Imethod_class, cp_Imethod_desc); - break; - case Constants.CONSTANT_MethodHandle: - for (int i = 0; i < cpMap.length; i++) { - ConstantPool.MethodHandleEntry e = (ConstantPool.MethodHandleEntry) cpMap[i]; - cp_MethodHandle_refkind.putInt(e.refKind); - cp_MethodHandle_member.putRef(e.memRef); - } - break; - case Constants.CONSTANT_MethodType: - for (int i = 0; i < cpMap.length; i++) { - ConstantPool.MethodTypeEntry e = (ConstantPool.MethodTypeEntry) cpMap[i]; - cp_MethodType.putRef(e.typeRef); - } - break; - case Constants.CONSTANT_InvokeDynamic: - for (int i = 0; i < cpMap.length; i++) { - ConstantPool.InvokeDynamicEntry e = (ConstantPool.InvokeDynamicEntry) cpMap[i]; - cp_InvokeDynamic_spec.putRef(e.bssRef); - cp_InvokeDynamic_desc.putRef(e.descRef); - } - break; - case Constants.CONSTANT_BootstrapMethod: - for (int i = 0; i < cpMap.length; i++) { - ConstantPool.BootstrapMethodEntry e = (ConstantPool.BootstrapMethodEntry) cpMap[i]; - cp_BootstrapMethod_ref.putRef(e.bsmRef); - cp_BootstrapMethod_arg_count.putInt(e.argRefs.length); - for (ConstantPool.Entry argRef : e.argRefs) { - cp_BootstrapMethod_arg.putRef(argRef); - } - } - break; - default: - throw new AssertionError("unexpected CP tag in package"); - } - } - if (optDumpBands || verbose > 1) { - for (byte tag = Constants.CONSTANT_GroupFirst; tag < Constants.CONSTANT_GroupLimit; tag++) { - ConstantPool.Index index = cp.getIndexByTag(tag); - if (index == null || index.isEmpty()) continue; - ConstantPool.Entry[] cpMap = index.cpMap; - if (verbose > 1) - Utils.log.info("Index group "+ConstantPool.tagName(tag)+" contains "+cpMap.length+" entries."); - if (optDumpBands) { - try (PrintStream ps = new PrintStream(getDumpStream(index.debugName, tag, ".gidx", index))) { - printArrayTo(ps, cpMap, 0, cpMap.length, true); - } - } - } - } - } - - void writeUtf8Bands(ConstantPool.Entry[] cpMap) throws IOException { - if (cpMap.length == 0) - return; // nothing to write - - // The first element must always be the empty string. - assert(cpMap[0].stringValue().equals("")); - final int SUFFIX_SKIP_1 = 1; - final int PREFIX_SKIP_2 = 2; - - // Fetch the char arrays, first of all. - char[][] chars = new char[cpMap.length][]; - for (int i = 0; i < chars.length; i++) { - chars[i] = cpMap[i].stringValue().toCharArray(); - } - - // First band: Write lengths of shared prefixes. - int[] prefixes = new int[cpMap.length]; // includes 2 skipped zeroes - char[] prevChars = {}; - for (int i = 0; i < chars.length; i++) { - int prefix = 0; - char[] curChars = chars[i]; - int limit = Math.min(curChars.length, prevChars.length); - while (prefix < limit && curChars[prefix] == prevChars[prefix]) - prefix++; - prefixes[i] = prefix; - if (i >= PREFIX_SKIP_2) - cp_Utf8_prefix.putInt(prefix); - else - assert(prefix == 0); - prevChars = curChars; - } - - // Second band: Write lengths of unshared suffixes. - // Third band: Write the char values in the unshared suffixes. - for (int i = 0; i < chars.length; i++) { - char[] str = chars[i]; - int prefix = prefixes[i]; - int suffix = str.length - prefixes[i]; - boolean isPacked = false; - if (suffix == 0) { - // Zero suffix length is special flag to indicate - // separate treatment in cp_Utf8_big bands. - // This suffix length never occurs naturally, - // except in the one case of a zero-length string. - // (If it occurs, it is the first, due to sorting.) - // The zero length string must, paradoxically, be - // encoded as a zero-length cp_Utf8_big band. - // This wastes exactly (& tolerably) one null byte. - isPacked = (i >= SUFFIX_SKIP_1); - // Do not bother to add an empty "(Utf8_big_0)" band. - // Also, the initial empty string does not require a band. - } else if (optBigStrings && effort > 1 && suffix > 100) { - int numWide = 0; - for (int n = 0; n < suffix; n++) { - if (str[prefix+n] > 127) { - numWide++; - } - } - if (numWide > 100) { - // Try packing the chars with an alternate encoding. - isPacked = tryAlternateEncoding(i, numWide, str, prefix); - } - } - if (i < SUFFIX_SKIP_1) { - // No output. - assert(!isPacked); - assert(suffix == 0); - } else if (isPacked) { - // Mark packed string with zero-length suffix count. - // This tells the unpacker to go elsewhere for the suffix bits. - // Fourth band: Write unshared suffix with alternate coding. - cp_Utf8_suffix.putInt(0); - cp_Utf8_big_suffix.putInt(suffix); - } else { - assert(suffix != 0); // would be ambiguous - // Normal string. Save suffix in third and fourth bands. - cp_Utf8_suffix.putInt(suffix); - for (int n = 0; n < suffix; n++) { - int ch = str[prefix+n]; - cp_Utf8_chars.putInt(ch); - } - } - } - if (verbose > 0) { - int normCharCount = cp_Utf8_chars.length(); - int packCharCount = cp_Utf8_big_chars.length(); - int charCount = normCharCount + packCharCount; - Utils.log.info("Utf8string #CHARS="+charCount+" #PACKEDCHARS="+packCharCount); - } - } - - private boolean tryAlternateEncoding(int i, int numWide, - char[] str, int prefix) { - int suffix = str.length - prefix; - int[] cvals = new int[suffix]; - for (int n = 0; n < suffix; n++) { - cvals[n] = str[prefix+n]; - } - CodingChooser cc = getCodingChooser(); - Coding bigRegular = cp_Utf8_big_chars.regularCoding; - String bandName = "(Utf8_big_"+i+")"; - int[] sizes = { 0, 0 }; - final int BYTE_SIZE = CodingChooser.BYTE_SIZE; - final int ZIP_SIZE = CodingChooser.ZIP_SIZE; - if (verbose > 1 || cc.verbose > 1) { - Utils.log.fine("--- chooseCoding "+bandName); - } - CodingMethod special = cc.choose(cvals, bigRegular, sizes); - Coding charRegular = cp_Utf8_chars.regularCoding; - if (verbose > 1) - Utils.log.fine("big string["+i+"] len="+suffix+" #wide="+numWide+" size="+sizes[BYTE_SIZE]+"/z="+sizes[ZIP_SIZE]+" coding "+special); - if (special != charRegular) { - int specialZipSize = sizes[ZIP_SIZE]; - int[] normalSizes = cc.computeSize(charRegular, cvals); - int normalZipSize = normalSizes[ZIP_SIZE]; - int minWin = Math.max(5, normalZipSize/1000); - if (verbose > 1) - Utils.log.fine("big string["+i+"] normalSize="+normalSizes[BYTE_SIZE]+"/z="+normalSizes[ZIP_SIZE]+" win="+(specialZipSize>> 32)); - if (haveModtime) - file_modtime.putInt(file.modtime - pkg.default_modtime); - if (haveOptions) - file_options.putInt(file.options); - file.writeTo(file_bits.collectorStream()); - if (verbose > 1) - Utils.log.fine("Wrote "+len+" bytes of "+file.name.stringValue()); - } - if (verbose > 0) - Utils.log.info("Wrote "+numFiles+" resource files"); - } - - void collectAttributeLayouts() { - maxFlags = new int[Constants.ATTR_CONTEXT_LIMIT]; - allLayouts = new FixedList<>(Constants.ATTR_CONTEXT_LIMIT); - for (int i = 0; i < Constants.ATTR_CONTEXT_LIMIT; i++) { - allLayouts.set(i, new HashMap<>()); - } - // Collect maxFlags and allLayouts. - for (Package.Class cls : pkg.classes) { - visitAttributeLayoutsIn(Constants.ATTR_CONTEXT_CLASS, cls); - for (Package.Class.Field f : cls.getFields()) { - visitAttributeLayoutsIn(Constants.ATTR_CONTEXT_FIELD, f); - } - for (Package.Class.Method m : cls.getMethods()) { - visitAttributeLayoutsIn(Constants.ATTR_CONTEXT_METHOD, m); - if (m.code != null) { - visitAttributeLayoutsIn(Constants.ATTR_CONTEXT_CODE, m.code); - } - } - } - // If there are many species of attributes, use 63-bit flags. - for (int i = 0; i < Constants.ATTR_CONTEXT_LIMIT; i++) { - int nl = allLayouts.get(i).size(); - boolean haveLongFlags = haveFlagsHi(i); - final int TOO_MANY_ATTRS = 32 /*int flag size*/ - - 12 /*typical flag bits in use*/ - + 4 /*typical number of OK overflows*/; - if (nl >= TOO_MANY_ATTRS) { // heuristic - int mask = 1<<(Constants.LG_AO_HAVE_XXX_FLAGS_HI+i); - archiveOptions |= mask; - haveLongFlags = true; - if (verbose > 0) - Utils.log.info("Note: Many "+Attribute.contextName(i)+" attributes forces 63-bit flags"); - } - if (verbose > 1) { - Utils.log.fine(Attribute.contextName(i)+".maxFlags = 0x"+Integer.toHexString(maxFlags[i])); - Utils.log.fine(Attribute.contextName(i)+".#layouts = "+nl); - } - assert(haveFlagsHi(i) == haveLongFlags); - } - initAttrIndexLimit(); - - // Standard indexes can never conflict with flag bits. Assert it. - for (int i = 0; i < Constants.ATTR_CONTEXT_LIMIT; i++) { - assert((attrFlagMask[i] & maxFlags[i]) == 0); - } - // Collect counts for both predefs. and custom defs. - // Decide on custom, local attribute definitions. - backCountTable = new HashMap<>(); - attrCounts = new int[Constants.ATTR_CONTEXT_LIMIT][]; - for (int i = 0; i < Constants.ATTR_CONTEXT_LIMIT; i++) { - // Now the remaining defs in allLayouts[i] need attr. indexes. - // Fill up unused flag bits with new defs. - // Unused bits are those which are not used by predefined attrs, - // and which are always clear in the classfiles. - long avHiBits = ~(maxFlags[i] | attrFlagMask[i]); - assert(attrIndexLimit[i] > 0); - assert(attrIndexLimit[i] < 64); // all bits fit into a Java long - avHiBits &= (1L< defMap = allLayouts.get(i); - @SuppressWarnings({"unchecked", "rawtypes"}) - Map.Entry[] layoutsAndCounts = - new Map.Entry[defMap.size()]; - defMap.entrySet().toArray(layoutsAndCounts); - // Sort by count, most frequent first. - // Predefs. participate in this sort, though it does not matter. - Arrays.sort(layoutsAndCounts, - new Comparator>() { - public int compare(Map.Entry e0, - Map.Entry e1) { - // Primary sort key is count, reversed. - int r = -(e0.getValue()[0] - e1.getValue()[0]); - if (r != 0) return r; - return e0.getKey().compareTo(e1.getKey()); - } - }); - attrCounts[i] = new int[attrIndexLimit[i]+layoutsAndCounts.length]; - for (int j = 0; j < layoutsAndCounts.length; j++) { - Map.Entry e = layoutsAndCounts[j]; - Attribute.Layout def = e.getKey(); - int count = e.getValue()[0]; - int index; - Integer predefIndex = attrIndexTable.get(def); - if (predefIndex != null) { - // The index is already set. - index = predefIndex.intValue(); - } else if (avHiBits != 0) { - while ((avHiBits & 1) == 0) { - avHiBits >>>= 1; - nextLoBit += 1; - } - avHiBits -= 1; // clear low bit; we are using it now - // Update attrIndexTable: - index = setAttributeLayoutIndex(def, nextLoBit); - } else { - // Update attrIndexTable: - index = setAttributeLayoutIndex(def, ATTR_INDEX_OVERFLOW); - } - - // Now that we know the index, record the count of this def. - attrCounts[i][index] = count; - - // For all callables in the def, keep a tally of back-calls. - Attribute.Layout.Element[] cbles = def.getCallables(); - final int[] bc = new int[cbles.length]; - for (int k = 0; k < cbles.length; k++) { - assert(cbles[k].kind == Attribute.EK_CBLE); - if (!cbles[k].flagTest(Attribute.EF_BACK)) { - bc[k] = -1; // no count to accumulate here - } - } - backCountTable.put(def, bc); - - if (predefIndex == null) { - // Make sure the package CP can name the local attribute. - ConstantPool.Entry ne = ConstantPool.getUtf8Entry(def.name()); - String layout = def.layoutForClassVersion(getHighestClassVersion()); - ConstantPool.Entry le = ConstantPool.getUtf8Entry(layout); - requiredEntries.add(ne); - requiredEntries.add(le); - if (verbose > 0) { - if (index < attrIndexLimit[i]) - Utils.log.info("Using free flag bit 1<<"+index+" for "+count+" occurrences of "+def); - else - Utils.log.info("Using overflow index "+index+" for "+count+" occurrences of "+def); - } - } - } - } - // Later, when emitting attr_definition_bands, we will look at - // attrDefSeen and attrDefs at position 32/63 and beyond. - // The attrIndexTable will provide elements of xxx_attr_indexes bands. - - // Done with scratch variables: - maxFlags = null; - allLayouts = null; - } - - // Scratch variables for processing attributes and flags. - int[] maxFlags; - List> allLayouts; - - void visitAttributeLayoutsIn(int ctype, Attribute.Holder h) { - // Make note of which flags appear in the class file. - // Set them in maxFlags. - maxFlags[ctype] |= h.flags; - for (Attribute a : h.getAttributes()) { - Attribute.Layout def = a.layout(); - Map defMap = allLayouts.get(ctype); - int[] count = defMap.get(def); - if (count == null) { - defMap.put(def, count = new int[1]); - } - if (count[0] < Integer.MAX_VALUE) { - count[0] += 1; - } - } - } - - Attribute.Layout[] attrDefsWritten; - - void writeAttrDefs() throws IOException { - List defList = new ArrayList<>(); - for (int i = 0; i < Constants.ATTR_CONTEXT_LIMIT; i++) { - int limit = attrDefs.get(i).size(); - for (int j = 0; j < limit; j++) { - int header = i; // ctype - if (j < attrIndexLimit[i]) { - header |= ((j + ADH_BIT_IS_LSB) << ADH_BIT_SHIFT); - assert(header < 0x100); // must fit into a byte - // (...else header is simply ctype, with zero high bits.) - if (!testBit(attrDefSeen[i], 1L<() { - public int compare(Object[] a0, Object[] a1) { - // Primary sort key is attr def header. - @SuppressWarnings("unchecked") - int r = ((Comparable)a0[0]).compareTo(a1[0]); - if (r != 0) return r; - Integer ind0 = attrIndexTable.get(a0[1]); - Integer ind1 = attrIndexTable.get(a1[1]); - // Secondary sort key is attribute index. - // (This must be so, in order to keep overflow attr order.) - assert(ind0 != null); - assert(ind1 != null); - return ind0.compareTo(ind1); - } - }); - attrDefsWritten = new Attribute.Layout[numAttrDefs]; - try (PrintStream dump = !optDumpBands ? null - : new PrintStream(getDumpStream(attr_definition_headers, ".def"))) - { - int[] indexForDebug = Arrays.copyOf(attrIndexLimit, Constants.ATTR_CONTEXT_LIMIT); - for (int i = 0; i < defs.length; i++) { - int header = ((Integer)defs[i][0]).intValue(); - Attribute.Layout def = (Attribute.Layout) defs[i][1]; - attrDefsWritten[i] = def; - assert((header & ADH_CONTEXT_MASK) == def.ctype()); - attr_definition_headers.putByte(header); - attr_definition_name.putRef(ConstantPool.getUtf8Entry(def.name())); - String layout = def.layoutForClassVersion(getHighestClassVersion()); - attr_definition_layout.putRef(ConstantPool.getUtf8Entry(layout)); - // Check that we are transmitting that correct attribute index: - boolean debug = false; - assert(debug = true); - if (debug) { - int hdrIndex = (header >> ADH_BIT_SHIFT) - ADH_BIT_IS_LSB; - if (hdrIndex < 0) hdrIndex = indexForDebug[def.ctype()]++; - int realIndex = (attrIndexTable.get(def)).intValue(); - assert(hdrIndex == realIndex); - } - if (dump != null) { - int index = (header >> ADH_BIT_SHIFT) - ADH_BIT_IS_LSB; - dump.println(index+" "+def); - } - } - } - } - - void writeAttrCounts() throws IOException { - // Write the four xxx_attr_calls bands. - for (int ctype = 0; ctype < Constants.ATTR_CONTEXT_LIMIT; ctype++) { - MultiBand xxx_attr_bands = attrBands[ctype]; - IntBand xxx_attr_calls = getAttrBand(xxx_attr_bands, AB_ATTR_CALLS); - Attribute.Layout[] defs = new Attribute.Layout[attrDefs.get(ctype).size()]; - attrDefs.get(ctype).toArray(defs); - for (boolean predef = true; ; predef = false) { - for (int ai = 0; ai < defs.length; ai++) { - Attribute.Layout def = defs[ai]; - if (def == null) continue; // unused index - if (predef != isPredefinedAttr(ctype, ai)) - continue; // wrong pass - int totalCount = attrCounts[ctype][ai]; - if (totalCount == 0) - continue; // irrelevant - int[] bc = backCountTable.get(def); - for (int j = 0; j < bc.length; j++) { - if (bc[j] >= 0) { - int backCount = bc[j]; - bc[j] = -1; // close out; do not collect further counts - xxx_attr_calls.putInt(backCount); - assert(def.getCallables()[j].flagTest(Attribute.EF_BACK)); - } else { - assert(!def.getCallables()[j].flagTest(Attribute.EF_BACK)); - } - } - } - if (!predef) break; - } - } - } - - void trimClassAttributes() { - for (Package.Class cls : pkg.classes) { - // Replace "obvious" SourceFile attrs by null. - cls.minimizeSourceFile(); - // BootstrapMethods should never have been inserted. - assert(cls.getAttribute(Package.attrBootstrapMethodsEmpty) == null); - } - } - - void collectInnerClasses() { - // Capture inner classes, removing them from individual classes. - // Irregular inner classes must stay local, though. - Map allICMap = new HashMap<>(); - // First, collect a consistent global set. - for (Package.Class cls : pkg.classes) { - if (!cls.hasInnerClasses()) continue; - for (Package.InnerClass ic : cls.getInnerClasses()) { - Package.InnerClass pic = allICMap.put(ic.thisClass, ic); - if (pic != null && !pic.equals(ic) && pic.predictable) { - // Different ICs. Choose the better to make global. - allICMap.put(pic.thisClass, pic); - } - } - } - - Package.InnerClass[] allICs = new Package.InnerClass[allICMap.size()]; - allICMap.values().toArray(allICs); - allICMap = null; // done with it - - // Note: The InnerClasses attribute must be in a valid order, - // so that A$B always occurs earlier than A$B$C. This is an - // important side-effect of sorting lexically by class name. - Arrays.sort(allICs); // put in canonical order - pkg.setAllInnerClasses(Arrays.asList(allICs)); - - // Next, empty out of every local set the consistent entries. - // Calculate whether there is any remaining need to have a local - // set, and whether it needs to be locked. - for (Package.Class cls : pkg.classes) { - cls.minimizeLocalICs(); - } - } - - void writeInnerClasses() throws IOException { - for (Package.InnerClass ic : pkg.getAllInnerClasses()) { - int flags = ic.flags; - assert((flags & Constants.ACC_IC_LONG_FORM) == 0); - if (!ic.predictable) { - flags |= Constants.ACC_IC_LONG_FORM; - } - ic_this_class.putRef(ic.thisClass); - ic_flags.putInt(flags); - if (!ic.predictable) { - ic_outer_class.putRef(ic.outerClass); - ic_name.putRef(ic.name); - } - } - } - - /** If there are any extra InnerClasses entries to write which are - * not already implied by the global table, put them into a - * local attribute. This is expected to be rare. - */ - void writeLocalInnerClasses(Package.Class cls) throws IOException { - List localICs = cls.getInnerClasses(); - class_InnerClasses_N.putInt(localICs.size()); - for(Package.InnerClass ic : localICs) { - class_InnerClasses_RC.putRef(ic.thisClass); - // Is it redundant with the global version? - if (ic.equals(pkg.getGlobalInnerClass(ic.thisClass))) { - // A zero flag means copy a global IC here. - class_InnerClasses_F.putInt(0); - } else { - int flags = ic.flags; - if (flags == 0) - flags = Constants.ACC_IC_LONG_FORM; // force it to be non-zero - class_InnerClasses_F.putInt(flags); - class_InnerClasses_outer_RCN.putRef(ic.outerClass); - class_InnerClasses_name_RUN.putRef(ic.name); - } - } - } - - void writeClassesAndByteCodes() throws IOException { - Package.Class[] classes = new Package.Class[pkg.classes.size()]; - pkg.classes.toArray(classes); - // Note: This code respects the order in which caller put classes. - if (verbose > 0) - Utils.log.info(" ...scanning "+classes.length+" classes..."); - - int nwritten = 0; - for (int i = 0; i < classes.length; i++) { - // Collect the class body, sans bytecodes. - Package.Class cls = classes[i]; - if (verbose > 1) - Utils.log.fine("Scanning "+cls); - - ConstantPool.ClassEntry thisClass = cls.thisClass; - ConstantPool.ClassEntry superClass = cls.superClass; - ConstantPool.ClassEntry[] interfaces = cls.interfaces; - // Encode rare case of null superClass as thisClass: - assert(superClass != thisClass); // bad class file!? - if (superClass == null) superClass = thisClass; - class_this.putRef(thisClass); - class_super.putRef(superClass); - class_interface_count.putInt(cls.interfaces.length); - for (int j = 0; j < interfaces.length; j++) { - class_interface.putRef(interfaces[j]); - } - - writeMembers(cls); - writeAttrs(Constants.ATTR_CONTEXT_CLASS, cls, cls); - - nwritten++; - if (verbose > 0 && (nwritten % 1000) == 0) - Utils.log.info("Have scanned "+nwritten+" classes..."); - } - } - - void writeMembers(Package.Class cls) throws IOException { - List fields = cls.getFields(); - class_field_count.putInt(fields.size()); - for (Package.Class.Field f : fields) { - field_descr.putRef(f.getDescriptor()); - writeAttrs(Constants.ATTR_CONTEXT_FIELD, f, cls); - } - - List methods = cls.getMethods(); - class_method_count.putInt(methods.size()); - for (Package.Class.Method m : methods) { - method_descr.putRef(m.getDescriptor()); - writeAttrs(Constants.ATTR_CONTEXT_METHOD, m, cls); - assert((m.code != null) == (m.getAttribute(attrCodeEmpty) != null)); - if (m.code != null) { - writeCodeHeader(m.code); - writeByteCodes(m.code); - } - } - } - - void writeCodeHeader(Code c) throws IOException { - boolean attrsOK = testBit(archiveOptions, Constants.AO_HAVE_ALL_CODE_FLAGS); - int na = c.attributeSize(); - int sc = shortCodeHeader(c); - if (!attrsOK && na > 0) - // We must write flags, and can only do so for long headers. - sc = LONG_CODE_HEADER; - if (verbose > 2) { - int siglen = c.getMethod().getArgumentSize(); - Utils.log.fine("Code sizes info "+c.max_stack+" "+c.max_locals+" "+c.getHandlerCount()+" "+siglen+" "+na+(sc > 0 ? " SHORT="+sc : "")); - } - code_headers.putByte(sc); - if (sc == LONG_CODE_HEADER) { - code_max_stack.putInt(c.getMaxStack()); - code_max_na_locals.putInt(c.getMaxNALocals()); - code_handler_count.putInt(c.getHandlerCount()); - } else { - assert(attrsOK || na == 0); - assert(c.getHandlerCount() < shortCodeHeader_h_limit); - } - writeCodeHandlers(c); - if (sc == LONG_CODE_HEADER || attrsOK) - writeAttrs(Constants.ATTR_CONTEXT_CODE, c, c.thisClass()); - } - - void writeCodeHandlers(Code c) throws IOException { - int sum, del; - for (int j = 0, jmax = c.getHandlerCount(); j < jmax; j++) { - code_handler_class_RCN.putRef(c.handler_class[j]); // null OK - // Encode end as offset from start, and catch as offset from end, - // because they are strongly correlated. - sum = c.encodeBCI(c.handler_start[j]); - code_handler_start_P.putInt(sum); - del = c.encodeBCI(c.handler_end[j]) - sum; - code_handler_end_PO.putInt(del); - sum += del; - del = c.encodeBCI(c.handler_catch[j]) - sum; - code_handler_catch_PO.putInt(del); - } - } - - // Generic routines for writing attributes and flags of - // classes, fields, methods, and codes. - void writeAttrs(int ctype, - final Attribute.Holder h, - Package.Class cls) throws IOException { - MultiBand xxx_attr_bands = attrBands[ctype]; - IntBand xxx_flags_hi = getAttrBand(xxx_attr_bands, AB_FLAGS_HI); - IntBand xxx_flags_lo = getAttrBand(xxx_attr_bands, AB_FLAGS_LO); - boolean haveLongFlags = haveFlagsHi(ctype); - assert(attrIndexLimit[ctype] == (haveLongFlags? 63: 32)); - if (h.attributes == null) { - xxx_flags_lo.putInt(h.flags); // no extra bits to set here - if (haveLongFlags) - xxx_flags_hi.putInt(0); - return; - } - if (verbose > 3) - Utils.log.fine("Transmitting attrs for "+h+" flags="+Integer.toHexString(h.flags)); - - long flagMask = attrFlagMask[ctype]; // which flags are attr bits? - long flagsToAdd = 0; - int overflowCount = 0; - for (Attribute a : h.attributes) { - Attribute.Layout def = a.layout(); - int index = (attrIndexTable.get(def)).intValue(); - assert(attrDefs.get(ctype).get(index) == def); - if (verbose > 3) - Utils.log.fine("add attr @"+index+" "+a+" in "+h); - if (index < attrIndexLimit[ctype] && testBit(flagMask, 1L< 3) - Utils.log.fine("Adding flag bit 1<<"+index+" in "+Long.toHexString(flagMask)); - assert(!testBit(h.flags, 1L< 3) - Utils.log.fine("Adding overflow attr #"+overflowCount); - IntBand xxx_attr_indexes = getAttrBand(xxx_attr_bands, AB_ATTR_INDEXES); - xxx_attr_indexes.putInt(index); - // System.out.println("overflow @"+index); - } - if (def.bandCount == 0) { - if (def == attrInnerClassesEmpty) { - // Special logic to write this attr. - writeLocalInnerClasses((Package.Class) h); - continue; - } - // Empty attr; nothing more to write here. - continue; - } - assert(a.fixups == null); - final Band[] ab = attrBandTable.get(def); - assert(ab != null); - assert(ab.length == def.bandCount); - final int[] bc = backCountTable.get(def); - assert(bc != null); - assert(bc.length == def.getCallables().length); - // Write one attribute of type def into ab. - if (verbose > 2) Utils.log.fine("writing "+a+" in "+h); - boolean isCV = (ctype == Constants.ATTR_CONTEXT_FIELD && def == attrConstantValue); - if (isCV) setConstantValueIndex((Package.Class.Field)h); - a.parse(cls, a.bytes(), 0, a.size(), - new Attribute.ValueStream() { - public void putInt(int bandIndex, int value) { - ((IntBand) ab[bandIndex]).putInt(value); - } - public void putRef(int bandIndex, ConstantPool.Entry ref) { - ((CPRefBand) ab[bandIndex]).putRef(ref); - } - public int encodeBCI(int bci) { - Code code = (Code) h; - return code.encodeBCI(bci); - } - public void noteBackCall(int whichCallable) { - assert(bc[whichCallable] >= 0); - bc[whichCallable] += 1; - } - }); - if (isCV) setConstantValueIndex(null); // clean up - } - - if (overflowCount > 0) { - IntBand xxx_attr_count = getAttrBand(xxx_attr_bands, AB_ATTR_COUNT); - xxx_attr_count.putInt(overflowCount); - } - - xxx_flags_lo.putInt(h.flags | (int)flagsToAdd); - if (haveLongFlags) - xxx_flags_hi.putInt((int)(flagsToAdd >>> 32)); - else - assert((flagsToAdd >>> 32) == 0); - assert((h.flags & flagsToAdd) == 0) - : (h+".flags=" - +Integer.toHexString(h.flags)+"^" - +Long.toHexString(flagsToAdd)); - } - - // temporary scratch variables for processing code blocks - private Code curCode; - private Package.Class curClass; - private ConstantPool.Entry[] curCPMap; - private void beginCode(Code c) { - assert(curCode == null); - curCode = c; - curClass = c.m.thisClass(); - curCPMap = c.getCPMap(); - } - private void endCode() { - curCode = null; - curClass = null; - curCPMap = null; - } - - // Return an _invokeinit_op variant, if the instruction matches one, - // else -1. - private int initOpVariant(Instruction i, ConstantPool.Entry newClass) { - if (i.getBC() != Constants._invokespecial) return -1; - ConstantPool.MemberEntry ref = (ConstantPool.MemberEntry) i.getCPRef(curCPMap); - if ("".equals(ref.descRef.nameRef.stringValue()) == false) - return -1; - ConstantPool.ClassEntry refClass = ref.classRef; - if (refClass == curClass.thisClass) - return Constants._invokeinit_op+Constants._invokeinit_self_option; - if (refClass == curClass.superClass) - return Constants._invokeinit_op+Constants._invokeinit_super_option; - if (refClass == newClass) - return Constants._invokeinit_op+Constants._invokeinit_new_option; - return -1; - } - - // Return a _self_linker_op variant, if the instruction matches one, - // else -1. - private int selfOpVariant(Instruction i) { - int bc = i.getBC(); - if (!(bc >= Constants._first_linker_op && bc <= Constants._last_linker_op)) return -1; - ConstantPool.MemberEntry ref = (ConstantPool.MemberEntry) i.getCPRef(curCPMap); - // do not optimize this case, simply fall back to regular coding - if ((bc == Constants._invokespecial || bc == Constants._invokestatic) && - ref.tagEquals(Constants.CONSTANT_InterfaceMethodref)) - return -1; - ConstantPool.ClassEntry refClass = ref.classRef; - int self_bc = Constants._self_linker_op + (bc - Constants._first_linker_op); - if (refClass == curClass.thisClass) - return self_bc; - if (refClass == curClass.superClass) - return self_bc + Constants._self_linker_super_flag; - return -1; - } - - void writeByteCodes(Code code) throws IOException { - beginCode(code); - ConstantPool.IndexGroup cp = pkg.cp; - - // true if the previous instruction is an aload to absorb - boolean prevAload = false; - - // class of most recent new; helps compress calls - ConstantPool.Entry newClass = null; - - for (Instruction i = code.instructionAt(0); i != null; i = i.next()) { - // %%% Add a stress mode which issues _ref/_byte_escape. - if (verbose > 3) Utils.log.fine(i.toString()); - - if (i.isNonstandard()) { - // Crash and burn with a complaint if there are funny - // bytecodes in this class file. - String complaint = code.getMethod() - +" contains an unrecognized bytecode "+i - +"; please use the pass-file option on this class."; - Utils.log.warning(complaint); - throw new IOException(complaint); - } - - if (i.isWide()) { - if (verbose > 1) { - Utils.log.fine("_wide opcode in "+code); - Utils.log.fine(i.toString()); - } - bc_codes.putByte(Constants._wide); - codeHist[Constants._wide]++; - } - - int bc = i.getBC(); - - // Begin "bc_linker" compression. - if (bc == Constants._aload_0) { - // Try to group aload_0 with a following operation. - Instruction ni = code.instructionAt(i.getNextPC()); - if (selfOpVariant(ni) >= 0) { - prevAload = true; - continue; - } - } - - // Test for invocations: - int init_bc = initOpVariant(i, newClass); - if (init_bc >= 0) { - if (prevAload) { - // get rid of it - bc_codes.putByte(Constants._aload_0); - codeHist[Constants._aload_0]++; - prevAload = false; //used up - } - // Write special bytecode. - bc_codes.putByte(init_bc); - codeHist[init_bc]++; - ConstantPool.MemberEntry ref = (ConstantPool.MemberEntry) i.getCPRef(curCPMap); - // Write operand to a separate band. - int coding = cp.getOverloadingIndex(ref); - bc_initref.putInt(coding); - continue; - } - - int self_bc = selfOpVariant(i); - if (self_bc >= 0) { - boolean isField = Instruction.isFieldOp(bc); - boolean isSuper = (self_bc >= Constants._self_linker_op+Constants._self_linker_super_flag); - boolean isAload = prevAload; - prevAload = false; //used up - if (isAload) - self_bc += Constants._self_linker_aload_flag; - // Write special bytecode. - bc_codes.putByte(self_bc); - codeHist[self_bc]++; - // Write field or method ref to a separate band. - ConstantPool.MemberEntry ref = (ConstantPool.MemberEntry) i.getCPRef(curCPMap); - CPRefBand bc_which = selfOpRefBand(self_bc); - ConstantPool.Index which_ix = cp.getMemberIndex(ref.tag, ref.classRef); - bc_which.putRef(ref, which_ix); - continue; - } - assert(!prevAload); - // End "bc_linker" compression. - - // Normal bytecode. - codeHist[bc]++; - switch (bc) { - case Constants._tableswitch: // apc: (df, lo, hi, (hi-lo+1)*(label)) - case Constants._lookupswitch: // apc: (df, nc, nc*(case, label)) - bc_codes.putByte(bc); - Instruction.Switch isw = (Instruction.Switch) i; - // Note that we do not write the alignment bytes. - int apc = isw.getAlignedPC(); - int npc = isw.getNextPC(); - // write a length specification into the bytecode stream - int caseCount = isw.getCaseCount(); - bc_case_count.putInt(caseCount); - putLabel(bc_label, code, i.getPC(), isw.getDefaultLabel()); - for (int j = 0; j < caseCount; j++) { - putLabel(bc_label, code, i.getPC(), isw.getCaseLabel(j)); - } - // Transmit case values in their own band. - if (bc == Constants._tableswitch) { - bc_case_value.putInt(isw.getCaseValue(0)); - } else { - for (int j = 0; j < caseCount; j++) { - bc_case_value.putInt(isw.getCaseValue(j)); - } - } - // Done with the switch. - continue; - } - - int branch = i.getBranchLabel(); - if (branch >= 0) { - bc_codes.putByte(bc); - putLabel(bc_label, code, i.getPC(), branch); - continue; - } - ConstantPool.Entry ref = i.getCPRef(curCPMap); - if (ref != null) { - if (bc == Constants._new) newClass = ref; - if (bc == Constants._ldc) ldcHist[ref.tag]++; - CPRefBand bc_which; - int vbc = bc; - switch (i.getCPTag()) { - case Constants.CONSTANT_LoadableValue: - switch (ref.tag) { - case Constants.CONSTANT_Integer: - bc_which = bc_intref; - switch (bc) { - case Constants._ldc: vbc = Constants._ildc; break; - case Constants._ldc_w: vbc = Constants._ildc_w; break; - default: assert(false); - } - break; - case Constants.CONSTANT_Float: - bc_which = bc_floatref; - switch (bc) { - case Constants._ldc: vbc = Constants._fldc; break; - case Constants._ldc_w: vbc = Constants._fldc_w; break; - default: assert(false); - } - break; - case Constants.CONSTANT_Long: - bc_which = bc_longref; - assert(bc == Constants._ldc2_w); - vbc = Constants._lldc2_w; - break; - case Constants.CONSTANT_Double: - bc_which = bc_doubleref; - assert(bc == Constants._ldc2_w); - vbc = Constants._dldc2_w; - break; - case Constants.CONSTANT_String: - bc_which = bc_stringref; - switch (bc) { - case Constants._ldc: vbc = Constants._sldc; break; - case Constants._ldc_w: vbc = Constants._sldc_w; break; - default: assert(false); - } - break; - case Constants.CONSTANT_Class: - bc_which = bc_classref; - switch (bc) { - case Constants._ldc: vbc = Constants._cldc; break; - case Constants._ldc_w: vbc = Constants._cldc_w; break; - default: assert(false); - } - break; - default: - // CONSTANT_MethodHandle, etc. - if (getHighestClassVersion().lessThan(Constants.JAVA7_MAX_CLASS_VERSION)) { - throw new IOException("bad class file major version for Java 7 ldc"); - } - bc_which = bc_loadablevalueref; - switch (bc) { - case Constants._ldc: vbc = Constants._qldc; break; - case Constants._ldc_w: vbc = Constants._qldc_w; break; - default: assert(false); - } - } - break; - case Constants.CONSTANT_Class: - // Use a special shorthand for the current class: - if (ref == curClass.thisClass) ref = null; - bc_which = bc_classref; break; - case Constants.CONSTANT_Fieldref: - bc_which = bc_fieldref; break; - case Constants.CONSTANT_Methodref: - if (ref.tagEquals(Constants.CONSTANT_InterfaceMethodref)) { - if (bc == Constants._invokespecial) - vbc = Constants._invokespecial_int; - if (bc == Constants._invokestatic) - vbc = Constants._invokestatic_int; - bc_which = bc_imethodref; - } else { - bc_which = bc_methodref; - } - break; - case Constants.CONSTANT_InterfaceMethodref: - bc_which = bc_imethodref; break; - case Constants.CONSTANT_InvokeDynamic: - bc_which = bc_indyref; break; - default: - bc_which = null; - assert(false); - } - if (ref != null && bc_which.index != null && !bc_which.index.contains(ref)) { - // Crash and burn with a complaint if there are funny - // references for this bytecode instruction. - // Example: invokestatic of a CONSTANT_InterfaceMethodref. - String complaint = code.getMethod() + - " contains a bytecode " + i + - " with an unsupported constant reference; please use the pass-file option on this class."; - Utils.log.warning(complaint); - throw new IOException(complaint); - } - bc_codes.putByte(vbc); - bc_which.putRef(ref); - // handle trailing junk - if (bc == Constants._multianewarray) { - assert(i.getConstant() == code.getByte(i.getPC()+3)); - // Just dump the byte into the bipush pile - bc_byte.putByte(0xFF & i.getConstant()); - } else if (bc == Constants._invokeinterface) { - assert(i.getLength() == 5); - // Make sure the discarded bytes are sane: - assert(i.getConstant() == (1+((ConstantPool.MemberEntry)ref).descRef.typeRef.computeSize(true)) << 8); - } else if (bc == Constants._invokedynamic) { - if (getHighestClassVersion().lessThan(Constants.JAVA7_MAX_CLASS_VERSION)) { - throw new IOException("bad class major version for Java 7 invokedynamic"); - } - assert(i.getLength() == 5); - assert(i.getConstant() == 0); // last 2 bytes MBZ - } else { - // Make sure there is nothing else to write. - assert(i.getLength() == ((bc == Constants._ldc)?2:3)); - } - continue; - } - int slot = i.getLocalSlot(); - if (slot >= 0) { - bc_codes.putByte(bc); - bc_local.putInt(slot); - int con = i.getConstant(); - if (bc == Constants._iinc) { - if (!i.isWide()) { - bc_byte.putByte(0xFF & con); - } else { - bc_short.putInt(0xFFFF & con); - } - } else { - assert(con == 0); - } - continue; - } - // Generic instruction. Copy the body. - bc_codes.putByte(bc); - int pc = i.getPC()+1; - int npc = i.getNextPC(); - if (pc < npc) { - // Do a few remaining multi-byte instructions. - switch (bc) { - case Constants._sipush: - bc_short.putInt(0xFFFF & i.getConstant()); - break; - case Constants._bipush: - bc_byte.putByte(0xFF & i.getConstant()); - break; - case Constants._newarray: - bc_byte.putByte(0xFF & i.getConstant()); - break; - default: - assert(false); // that's it - } - } - } - bc_codes.putByte(Constants._end_marker); - bc_codes.elementCountForDebug++; - codeHist[Constants._end_marker]++; - endCode(); - } - - int[] codeHist = new int[1<<8]; - int[] ldcHist = new int[20]; - void printCodeHist() { - assert(verbose > 0); - String[] hist = new String[codeHist.length]; - int totalBytes = 0; - for (int bc = 0; bc < codeHist.length; bc++) { - totalBytes += codeHist[bc]; - } - for (int bc = 0; bc < codeHist.length; bc++) { - if (codeHist[bc] == 0) { hist[bc] = ""; continue; } - String iname = Instruction.byteName(bc); - String count = "" + codeHist[bc]; - count = " ".substring(count.length()) + count; - String pct = "" + (codeHist[bc] * 10000 / totalBytes); - while (pct.length() < 4) { - pct = "0" + pct; - } - pct = pct.substring(0, pct.length()-2) + "." + pct.substring(pct.length()-2); - hist[bc] = count + " " + pct + "% " + iname; - } - Arrays.sort(hist); - System.out.println("Bytecode histogram ["+totalBytes+"]"); - for (int i = hist.length; --i >= 0; ) { - if ("".equals(hist[i])) continue; - System.out.println(hist[i]); - } - for (int tag = 0; tag < ldcHist.length; tag++) { - int count = ldcHist[tag]; - if (count == 0) continue; - System.out.println("ldc "+ConstantPool.tagName(tag)+" "+count); - } - } -} diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/PackerImpl.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/PackerImpl.java deleted file mode 100644 index 617d8851a..000000000 --- a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/PackerImpl.java +++ /dev/null @@ -1,607 +0,0 @@ -/* - * Copyright (c) 2003, 2015, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package net.fabricmc.shade.com.sun.java.util.jar.pack; - -import java.io.BufferedInputStream; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.ListIterator; -import java.util.Map; -import java.util.SortedMap; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; -import java.util.jar.JarInputStream; -import net.fabricmc.shade.java.util.jar.Pack200; - - -/* - * Implementation of the Pack provider. - * - * @author John Rose - * @author Kumar Srinivasan - */ - -@SuppressWarnings({"removal"}) -public class PackerImpl extends TLGlobals implements Pack200.Packer { - - /** - * Constructs a Packer object and sets the initial state of - * the packer engines. - */ - public PackerImpl() {} - - /** - * Get the set of options for the pack and unpack engines. - * @return A sorted association of option key strings to option values. - */ - public SortedMap properties() { - return props; - } - - //Driver routines - - /** - * Takes a JarFile and converts into a pack-stream. - *

- * Closes its input but not its output. (Pack200 archives are appendable.) - * @param in a JarFile - * @param out an OutputStream - * @exception IOException if an error is encountered. - */ - public synchronized void pack(JarFile in, OutputStream out) throws IOException { - assert(Utils.currentInstance.get() == null); - try { - Utils.currentInstance.set(this); - if ("0".equals(props.getProperty(Pack200.Packer.EFFORT))) { - Utils.copyJarFile(in, out); - } else { - (new DoPack()).run(in, out); - } - } finally { - Utils.currentInstance.set(null); - in.close(); - } - } - - /** - * Takes a JarInputStream and converts into a pack-stream. - *

- * Closes its input but not its output. (Pack200 archives are appendable.) - *

- * The modification time and deflation hint attributes are not available, - * for the jar-manifest file and the directory containing the file. - * - * @see #MODIFICATION_TIME - * @param in a JarInputStream - * @param out an OutputStream - * @exception IOException if an error is encountered. - */ - public synchronized void pack(JarInputStream in, OutputStream out) throws IOException { - assert(Utils.currentInstance.get() == null); - try { - Utils.currentInstance.set(this); - if ("0".equals(props.getProperty(Pack200.Packer.EFFORT))) { - Utils.copyJarFile(in, out); - } else { - (new DoPack()).run(in, out); - } - } finally { - Utils.currentInstance.set(null); - in.close(); - } - } - - // All the worker bees..... - // The packer worker. - private class DoPack { - final int verbose = props.getInteger(Utils.DEBUG_VERBOSE); - - { - props.setInteger(Pack200.Packer.PROGRESS, 0); - if (verbose > 0) Utils.log.info(props.toString()); - } - - // Here's where the bits are collected before getting packed, we also - // initialize the version numbers now. - final Package pkg = new Package( - Package.Version.makeVersion(props, "min.class"), - Package.Version.makeVersion(props, "max.class"), - Package.Version.makeVersion(props, "package")); - - final String unknownAttrCommand; - { - String uaMode = props.getProperty(Pack200.Packer.UNKNOWN_ATTRIBUTE, Pack200.Packer.PASS); - if (!(Pack200.Packer.STRIP.equals(uaMode) || - Pack200.Packer.PASS.equals(uaMode) || - Pack200.Packer.ERROR.equals(uaMode))) { - throw new RuntimeException("Bad option: " + Pack200.Packer.UNKNOWN_ATTRIBUTE + " = " + uaMode); - } - unknownAttrCommand = uaMode.intern(); - } - final String classFormatCommand; - { - String fmtMode = props.getProperty(Utils.CLASS_FORMAT_ERROR, Pack200.Packer.PASS); - if (!(Pack200.Packer.PASS.equals(fmtMode) || - Pack200.Packer.ERROR.equals(fmtMode))) { - throw new RuntimeException("Bad option: " + Utils.CLASS_FORMAT_ERROR + " = " + fmtMode); - } - classFormatCommand = fmtMode.intern(); - } - - final Map attrDefs; - final Map attrCommands; - { - Map lattrDefs = new HashMap<>(); - Map lattrCommands = new HashMap<>(); - String[] keys = { - Pack200.Packer.CLASS_ATTRIBUTE_PFX, - Pack200.Packer.FIELD_ATTRIBUTE_PFX, - Pack200.Packer.METHOD_ATTRIBUTE_PFX, - Pack200.Packer.CODE_ATTRIBUTE_PFX - }; - int[] ctypes = { - Constants.ATTR_CONTEXT_CLASS, - Constants.ATTR_CONTEXT_FIELD, - Constants.ATTR_CONTEXT_METHOD, - Constants.ATTR_CONTEXT_CODE - }; - for (int i = 0; i < ctypes.length; i++) { - String pfx = keys[i]; - Map map = props.prefixMap(pfx); - for (String key : map.keySet()) { - assert(key.startsWith(pfx)); - String name = key.substring(pfx.length()); - String layout = props.getProperty(key); - Attribute.Layout lkey = Attribute.keyForLookup(ctypes[i], name); - if (Pack200.Packer.STRIP.equals(layout) || - Pack200.Packer.PASS.equals(layout) || - Pack200.Packer.ERROR.equals(layout)) { - lattrCommands.put(lkey, layout.intern()); - } else { - Attribute.define(lattrDefs, ctypes[i], name, layout); - if (verbose > 1) { - Utils.log.fine("Added layout for "+Constants.ATTR_CONTEXT_NAME[i]+" attribute "+name+" = "+layout); - } - assert(lattrDefs.containsKey(lkey)); - } - } - } - this.attrDefs = (lattrDefs.isEmpty()) ? null : lattrDefs; - this.attrCommands = (lattrCommands.isEmpty()) ? null : lattrCommands; - } - - final boolean keepFileOrder - = props.getBoolean(Pack200.Packer.KEEP_FILE_ORDER); - final boolean keepClassOrder - = props.getBoolean(Utils.PACK_KEEP_CLASS_ORDER); - - final boolean keepModtime - = Pack200.Packer.KEEP.equals(props.getProperty(Pack200.Packer.MODIFICATION_TIME)); - final boolean latestModtime - = Pack200.Packer.LATEST.equals(props.getProperty(Pack200.Packer.MODIFICATION_TIME)); - final boolean keepDeflateHint - = Pack200.Packer.KEEP.equals(props.getProperty(Pack200.Packer.DEFLATE_HINT)); - { - if (!keepModtime && !latestModtime) { - int modtime = props.getTime(Pack200.Packer.MODIFICATION_TIME); - if (modtime != Constants.NO_MODTIME) { - pkg.default_modtime = modtime; - } - } - if (!keepDeflateHint) { - boolean deflate_hint = props.getBoolean(Pack200.Packer.DEFLATE_HINT); - if (deflate_hint) { - pkg.default_options |= Constants.AO_DEFLATE_HINT; - } - } - } - - long totalOutputSize = 0; - int segmentCount = 0; - long segmentTotalSize = 0; - long segmentSize = 0; // running counter - final long segmentLimit; - { - long limit; - if (props.getProperty(Pack200.Packer.SEGMENT_LIMIT, "").equals("")) - limit = -1; - else - limit = props.getLong(Pack200.Packer.SEGMENT_LIMIT); - limit = Math.min(Integer.MAX_VALUE, limit); - limit = Math.max(-1, limit); - if (limit == -1) - limit = Long.MAX_VALUE; - segmentLimit = limit; - } - - final List passFiles; // parsed pack.pass.file options - { - // Which class files will be passed through? - passFiles = props.getProperties(Pack200.Packer.PASS_FILE_PFX); - for (ListIterator i = passFiles.listIterator(); i.hasNext(); ) { - String file = i.next(); - if (file == null) { i.remove(); continue; } - file = Utils.getJarEntryName(file); // normalize '\\' to '/' - if (file.endsWith("/")) - file = file.substring(0, file.length()-1); - i.set(file); - } - if (verbose > 0) Utils.log.info("passFiles = " + passFiles); - } - - { - // Hook for testing: Forces use of special archive modes. - int opt = props.getInteger(Utils.COM_PREFIX+"archive.options"); - if (opt != 0) - pkg.default_options |= opt; - } - - // (Done collecting options from props.) - - // Get a new package, based on the old one. - private void makeNextPackage() { - pkg.reset(); - } - - final class InFile { - final String name; - final JarFile jf; - final JarEntry je; - final File f; - int modtime = Constants.NO_MODTIME; - int options; - InFile(String name) { - this.name = Utils.getJarEntryName(name); - this.f = new File(name); - this.jf = null; - this.je = null; - int timeSecs = getModtime(f.lastModified()); - if (keepModtime && timeSecs != Constants.NO_MODTIME) { - this.modtime = timeSecs; - } else if (latestModtime && timeSecs > pkg.default_modtime) { - pkg.default_modtime = timeSecs; - } - } - InFile(JarFile jf, JarEntry je) { - this.name = Utils.getJarEntryName(je.getName()); - this.f = null; - this.jf = jf; - this.je = je; - int timeSecs = (int) je.getTime(); - if (keepModtime && timeSecs != Constants.NO_MODTIME) { - this.modtime = timeSecs; - } else if (latestModtime && timeSecs > pkg.default_modtime) { - pkg.default_modtime = timeSecs; - } - if (keepDeflateHint && je.getMethod() == JarEntry.DEFLATED) { - options |= Constants.FO_DEFLATE_HINT; - } - } - InFile(JarEntry je) { - this(null, je); - } - boolean isClassFile() { - if (!name.endsWith(".class") || name.endsWith("module-info.class")) { - return false; - } - for (String prefix = name;;) { - if (passFiles.contains(prefix)) { - return false; - } - int chop = prefix.lastIndexOf('/'); - if (chop < 0) { - break; - } - prefix = prefix.substring(0, chop); - } - return true; - } - boolean isMetaInfFile() { - return name.startsWith("/" + Utils.METAINF) - || name.startsWith(Utils.METAINF); - } - boolean mustProcess() { - return !isMetaInfFile() && isClassFile(); - } - long getInputLength() { - long len = (je != null)? je.getSize(): f.length(); - assert(len >= 0) : this+".len="+len; - // Bump size by pathname length and modtime/def-hint bytes. - return Math.max(0, len) + name.length() + 5; - } - int getModtime(long timeMillis) { - // Convert milliseconds to seconds. - long seconds = (timeMillis+500) / 1000; - if ((int)seconds == seconds) { - return (int)seconds; - } else { - Utils.log.warning("overflow in modtime for "+f); - return Constants.NO_MODTIME; - } - } - void copyTo(Package.File file) { - if (modtime != Constants.NO_MODTIME) - file.modtime = modtime; - file.options |= options; - } - InputStream getInputStream() throws IOException { - if (jf != null) - return jf.getInputStream(je); - else - return new FileInputStream(f); - } - - public String toString() { - return name; - } - } - - private int nread = 0; // used only if (verbose > 0) - private void noteRead(InFile f) { - nread++; - if (verbose > 2) - Utils.log.fine("...read "+f.name); - if (verbose > 0 && (nread % 1000) == 0) - Utils.log.info("Have read "+nread+" files..."); - } - - void run(JarInputStream in, OutputStream out) throws IOException { - // First thing we do is get the manifest, as JIS does - // not provide the Manifest as an entry. - if (in.getManifest() != null) { - ByteArrayOutputStream tmp = new ByteArrayOutputStream(); - in.getManifest().write(tmp); - InputStream tmpIn = new ByteArrayInputStream(tmp.toByteArray()); - pkg.addFile(readFile(JarFile.MANIFEST_NAME, tmpIn)); - } - for (JarEntry je; (je = in.getNextJarEntry()) != null; ) { - InFile inFile = new InFile(je); - - String name = inFile.name; - Package.File bits = readFile(name, in); - Package.File file = null; - // (5078608) : discount the resource files in META-INF - // from segment computation. - long inflen = (inFile.isMetaInfFile()) - ? 0L - : inFile.getInputLength(); - - if ((segmentSize += inflen) > segmentLimit) { - segmentSize -= inflen; - int nextCount = -1; // don't know; it's a stream - flushPartial(out, nextCount); - } - if (verbose > 1) { - Utils.log.fine("Reading " + name); - } - - assert(je.isDirectory() == name.endsWith("/")); - - if (inFile.mustProcess()) { - file = readClass(name, bits.getInputStream()); - } - if (file == null) { - file = bits; - pkg.addFile(file); - } - inFile.copyTo(file); - noteRead(inFile); - } - flushAll(out); - } - - void run(JarFile in, OutputStream out) throws IOException { - List inFiles = scanJar(in); - - if (verbose > 0) - Utils.log.info("Reading " + inFiles.size() + " files..."); - - int numDone = 0; - for (InFile inFile : inFiles) { - String name = inFile.name; - // (5078608) : discount the resource files completely from segmenting - long inflen = (inFile.isMetaInfFile()) - ? 0L - : inFile.getInputLength() ; - if ((segmentSize += inflen) > segmentLimit) { - segmentSize -= inflen; - // Estimate number of remaining segments: - float filesDone = numDone+1; - float segsDone = segmentCount+1; - float filesToDo = inFiles.size() - filesDone; - float segsToDo = filesToDo * (segsDone/filesDone); - if (verbose > 1) - Utils.log.fine("Estimated segments to do: "+segsToDo); - flushPartial(out, (int) Math.ceil(segsToDo)); - } - InputStream strm = inFile.getInputStream(); - if (verbose > 1) - Utils.log.fine("Reading " + name); - Package.File file = null; - if (inFile.mustProcess()) { - file = readClass(name, strm); - if (file == null) { - strm.close(); - strm = inFile.getInputStream(); - } - } - if (file == null) { - file = readFile(name, strm); - pkg.addFile(file); - } - inFile.copyTo(file); - strm.close(); // tidy up - noteRead(inFile); - numDone += 1; - } - flushAll(out); - } - - Package.File readClass(String fname, InputStream in) throws IOException { - Package.Class cls = pkg.new Class(fname); - in = new BufferedInputStream(in); - ClassReader reader = new ClassReader(cls, in); - reader.setAttrDefs(attrDefs); - reader.setAttrCommands(attrCommands); - reader.unknownAttrCommand = unknownAttrCommand; - try { - reader.read(); - } catch (IOException ioe) { - String message = "Passing class file uncompressed due to"; - if (ioe instanceof Attribute.FormatException) { - Attribute.FormatException ee = (Attribute.FormatException) ioe; - // He passed up the category to us in layout. - if (ee.layout.equals(Pack200.Packer.PASS)) { - Utils.log.info(ee.toString()); - Utils.log.warning(message + " unrecognized attribute: " + - fname); - return null; - } - } else if (ioe instanceof ClassReader.ClassFormatException) { - ClassReader.ClassFormatException ce = (ClassReader.ClassFormatException) ioe; - if (classFormatCommand.equals(Pack200.Packer.PASS)) { - Utils.log.info(ce.toString()); - Utils.log.warning(message + " unknown class format: " + - fname); - return null; - } - } - // Otherwise, it must be an error. - throw ioe; - } - pkg.addClass(cls); - return cls.file; - } - - // Read raw data. - Package.File readFile(String fname, InputStream in) throws IOException { - - Package.File file = pkg.new File(fname); - file.readFrom(in); - if (file.isDirectory() && file.getFileLength() != 0) - throw new IllegalArgumentException("Non-empty directory: "+file.getFileName()); - return file; - } - - void flushPartial(OutputStream out, int nextCount) throws IOException { - if (pkg.files.isEmpty() && pkg.classes.isEmpty()) { - return; // do not flush an empty segment - } - flushPackage(out, Math.max(1, nextCount)); - props.setInteger(Pack200.Packer.PROGRESS, 25); - // In case there will be another segment: - makeNextPackage(); - segmentCount += 1; - segmentTotalSize += segmentSize; - segmentSize = 0; - } - - void flushAll(OutputStream out) throws IOException { - props.setInteger(Pack200.Packer.PROGRESS, 50); - flushPackage(out, 0); - out.flush(); - props.setInteger(Pack200.Packer.PROGRESS, 100); - segmentCount += 1; - segmentTotalSize += segmentSize; - segmentSize = 0; - if (verbose > 0 && segmentCount > 1) { - Utils.log.info("Transmitted " - +segmentTotalSize+" input bytes in " - +segmentCount+" segments totaling " - +totalOutputSize+" bytes"); - } - } - - - /** Write all information in the current package segment - * to the output stream. - */ - void flushPackage(OutputStream out, int nextCount) throws IOException { - int nfiles = pkg.files.size(); - if (!keepFileOrder) { - // Keeping the order of classes costs about 1% - // Keeping the order of all files costs something more. - if (verbose > 1) Utils.log.fine("Reordering files."); - boolean stripDirectories = true; - pkg.reorderFiles(keepClassOrder, stripDirectories); - } else { - // Package builder must have created a stub for each class. - assert(pkg.files.containsAll(pkg.getClassStubs())); - // Order of stubs in file list must agree with classes. - List res = pkg.files; - assert((res = new ArrayList<>(pkg.files)) - .retainAll(pkg.getClassStubs()) || true); - assert(res.equals(pkg.getClassStubs())); - } - pkg.trimStubs(); - - // Do some stripping, maybe. - if (props.getBoolean(Utils.COM_PREFIX+"strip.debug")) pkg.stripAttributeKind("Debug"); - if (props.getBoolean(Utils.COM_PREFIX+"strip.compile")) pkg.stripAttributeKind("Compile"); - if (props.getBoolean(Utils.COM_PREFIX+"strip.constants")) pkg.stripAttributeKind("Constant"); - if (props.getBoolean(Utils.COM_PREFIX+"strip.exceptions")) pkg.stripAttributeKind("Exceptions"); - if (props.getBoolean(Utils.COM_PREFIX+"strip.innerclasses")) pkg.stripAttributeKind("InnerClasses"); - - PackageWriter pw = new PackageWriter(pkg, out); - pw.archiveNextCount = nextCount; - pw.write(); - out.flush(); - if (verbose > 0) { - long outSize = pw.archiveSize0+pw.archiveSize1; - totalOutputSize += outSize; - long inSize = segmentSize; - Utils.log.info("Transmitted " - +nfiles+" files of " - +inSize+" input bytes in a segment of " - +outSize+" bytes"); - } - } - - List scanJar(JarFile jf) throws IOException { - // Collect jar entries, preserving order. - List inFiles = new ArrayList<>(); - try { - for (JarEntry je : Collections.list(jf.entries())) { - InFile inFile = new InFile(jf, je); - assert(je.isDirectory() == inFile.name.endsWith("/")); - inFiles.add(inFile); - } - } catch (IllegalStateException ise) { - throw new IOException(ise.getLocalizedMessage(), ise); - } - return inFiles; - } - } -} diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/PopulationCoding.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/PopulationCoding.java deleted file mode 100644 index e731aeb8e..000000000 --- a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/PopulationCoding.java +++ /dev/null @@ -1,499 +0,0 @@ -/* - * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package net.fabricmc.shade.com.sun.java.util.jar.pack; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - -/** - * Population-based coding. - * See the section "Encodings of Uncorrelated Values" in the Pack200 spec. - * @author John Rose - */ -// This tactic alone reduces the final zipped rt.jar by about a percent. -class PopulationCoding implements CodingMethod { - Histogram vHist; // histogram of all values - int[] fValues; // list of favored values - int fVlen; // inclusive max index - long[] symtab; // int map of favored value -> token [1..#fValues] - - CodingMethod favoredCoding; - CodingMethod tokenCoding; - CodingMethod unfavoredCoding; - - int L = -1; //preferred L value for tokenCoding - - public void setFavoredValues(int[] fValues, int fVlen) { - // Note: {f} is allFavoredValues[1..fvlen], not [0..fvlen-1]. - // This is because zero is an exceptional favored value index. - assert(fValues[0] == 0); // must be empty - assert(this.fValues == null); // do not do this twice - this.fValues = fValues; - this.fVlen = fVlen; - if (L >= 0) { - setL(L); // reassert - } - } - public void setFavoredValues(int[] fValues) { - int lfVlen = fValues.length-1; - setFavoredValues(fValues, lfVlen); - } - public void setHistogram(Histogram vHist) { - this.vHist = vHist; - } - public void setL(int L) { - this.L = L; - if (L >= 0 && fValues != null && tokenCoding == null) { - tokenCoding = fitTokenCoding(fVlen, L); - assert(tokenCoding != null); - } - } - - public static Coding fitTokenCoding(int fVlen, int L) { - // Find the smallest B s.t. (B,H,0) covers fVlen. - if (fVlen < 256) - // H/L do not matter when B==1 - return BandStructure.BYTE1; - Coding longest = BandStructure.UNSIGNED5.setL(L); - if (!longest.canRepresentUnsigned(fVlen)) - return null; // failure; L is too sharp and fVlen too large - Coding tc = longest; - for (Coding shorter = longest; ; ) { - shorter = shorter.setB(shorter.B()-1); - if (shorter.umax() < fVlen) - break; - tc = shorter; // shorten it by reducing B - } - return tc; - } - - public void setFavoredCoding(CodingMethod favoredCoding) { - this.favoredCoding = favoredCoding; - } - public void setTokenCoding(CodingMethod tokenCoding) { - this.tokenCoding = tokenCoding; - this.L = -1; - if (tokenCoding instanceof Coding && fValues != null) { - Coding tc = (Coding) tokenCoding; - if (tc == fitTokenCoding(fVlen, tc.L())) - this.L = tc.L(); - // Otherwise, it's a non-default coding. - } - } - public void setUnfavoredCoding(CodingMethod unfavoredCoding) { - this.unfavoredCoding = unfavoredCoding; - } - - public int favoredValueMaxLength() { - if (L == 0) - return Integer.MAX_VALUE; - else - return BandStructure.UNSIGNED5.setL(L).umax(); - } - - public void resortFavoredValues() { - Coding tc = (Coding) tokenCoding; - // Make a local copy before reordering. - fValues = BandStructure.realloc(fValues, 1+fVlen); - // Resort favoredValues within each byte-size cadre. - int fillp = 1; // skip initial zero - for (int n = 1; n <= tc.B(); n++) { - int nmax = tc.byteMax(n); - if (nmax > fVlen) - nmax = fVlen; - if (nmax < tc.byteMin(n)) - break; - int low = fillp; - int high = nmax+1; - if (high == low) continue; - assert(high > low) - : high+"!>"+low; - assert(tc.getLength(low) == n) - : n+" != len("+(low)+") == "+ - tc.getLength(low); - assert(tc.getLength(high-1) == n) - : n+" != len("+(high-1)+") == "+ - tc.getLength(high-1); - int midTarget = low + (high-low)/2; - int mid = low; - // Divide the values into cadres, and sort within each. - int prevCount = -1; - int prevLimit = low; - for (int i = low; i < high; i++) { - int val = fValues[i]; - int count = vHist.getFrequency(val); - if (prevCount != count) { - if (n == 1) { - // For the single-byte encoding, keep strict order - // among frequency groups. - Arrays.sort(fValues, prevLimit, i); - } else if (Math.abs(mid - midTarget) > - Math.abs(i - midTarget)) { - // Find a single inflection point - // close to the middle of the byte-size cadre. - mid = i; - } - prevCount = count; - prevLimit = i; - } - } - if (n == 1) { - Arrays.sort(fValues, prevLimit, high); - } else { - // Sort up to the midpoint, if any. - Arrays.sort(fValues, low, mid); - Arrays.sort(fValues, mid, high); - } - assert(tc.getLength(low) == tc.getLength(mid)); - assert(tc.getLength(low) == tc.getLength(high-1)); - fillp = nmax+1; - } - assert(fillp == fValues.length); - - // Reset symtab. - symtab = null; - } - - public int getToken(int value) { - if (symtab == null) - symtab = makeSymtab(); - int pos = Arrays.binarySearch(symtab, (long)value << 32); - if (pos < 0) pos = -pos-1; - if (pos < symtab.length && value == (int)(symtab[pos] >>> 32)) - return (int)symtab[pos]; - else - return 0; - } - - public int[][] encodeValues(int[] values, int start, int end) { - // Compute token sequence. - int[] tokens = new int[end-start]; - int nuv = 0; - for (int i = 0; i < tokens.length; i++) { - int val = values[start+i]; - int tok = getToken(val); - if (tok != 0) - tokens[i] = tok; - else - nuv += 1; - } - // Compute unfavored value sequence. - int[] unfavoredValues = new int[nuv]; - nuv = 0; // reset - for (int i = 0; i < tokens.length; i++) { - if (tokens[i] != 0) continue; // already covered - int val = values[start+i]; - unfavoredValues[nuv++] = val; - } - assert(nuv == unfavoredValues.length); - return new int[][]{ tokens, unfavoredValues }; - } - - private long[] makeSymtab() { - long[] lsymtab = new long[fVlen]; - for (int token = 1; token <= fVlen; token++) { - lsymtab[token-1] = ((long)fValues[token] << 32) | token; - } - // Index by value: - Arrays.sort(lsymtab); - return lsymtab; - } - - private Coding getTailCoding(CodingMethod c) { - while (c instanceof AdaptiveCoding) - c = ((AdaptiveCoding)c).tailCoding; - return (Coding) c; - } - - // CodingMethod methods. - public void writeArrayTo(OutputStream out, int[] a, int start, int end) throws IOException { - int[][] vals = encodeValues(a, start, end); - writeSequencesTo(out, vals[0], vals[1]); - } - void writeSequencesTo(OutputStream out, int[] tokens, int[] uValues) throws IOException { - favoredCoding.writeArrayTo(out, fValues, 1, 1+fVlen); - getTailCoding(favoredCoding).writeTo(out, computeSentinelValue()); - tokenCoding.writeArrayTo(out, tokens, 0, tokens.length); - if (uValues.length > 0) - unfavoredCoding.writeArrayTo(out, uValues, 0, uValues.length); - } - - int computeSentinelValue() { - Coding fc = getTailCoding(favoredCoding); - if (fc.isDelta()) { - // repeat the last favored value, using delta=0 - return 0; - } else { - // else repeat the shorter of the min or last value - int min = fValues[1]; - int last = min; - // (remember that fVlen is an inclusive limit in fValues) - for (int i = 2; i <= fVlen; i++) { - last = fValues[i]; - min = moreCentral(min, last); - } - int endVal; - if (fc.getLength(min) <= fc.getLength(last)) - return min; - else - return last; - } - } - - public void readArrayFrom(InputStream in, int[] a, int start, int end) throws IOException { - // Parameters are fCode, L, uCode. - setFavoredValues(readFavoredValuesFrom(in, end-start)); - // Read the tokens. Read them into the final array, for the moment. - tokenCoding.readArrayFrom(in, a, start, end); - // Decode the favored tokens. - int headp = 0, tailp = -1; - int uVlen = 0; - for (int i = start; i < end; i++) { - int tok = a[i]; - if (tok == 0) { - // Make a linked list, and decode in a second pass. - if (tailp < 0) { - headp = i; - } else { - a[tailp] = i; - } - tailp = i; - uVlen += 1; - } else { - a[i] = fValues[tok]; - } - } - // Walk the linked list of "zero" locations, decoding unfavored vals. - int[] uValues = new int[uVlen]; - if (uVlen > 0) - unfavoredCoding.readArrayFrom(in, uValues, 0, uVlen); - for (int i = 0; i < uVlen; i++) { - int nextp = a[headp]; - a[headp] = uValues[i]; - headp = nextp; - } - } - - int[] readFavoredValuesFrom(InputStream in, int maxForDebug) throws IOException { - int[] lfValues = new int[1000]; // realloc as needed - // The set uniqueValuesForDebug records all favored values. - // As each new value is added, we assert that the value - // was not already in the set. - Set uniqueValuesForDebug = null; - assert((uniqueValuesForDebug = new HashSet<>()) != null); - int fillp = 1; - maxForDebug += fillp; - int min = Integer.MIN_VALUE; // farthest from the center - //int min2 = Integer.MIN_VALUE; // emulate buggy 150.7 spec. - int last = 0; - CodingMethod fcm = favoredCoding; - while (fcm instanceof AdaptiveCoding) { - AdaptiveCoding ac = (AdaptiveCoding) fcm; - int len = ac.headLength; - while (fillp + len > lfValues.length) { - lfValues = BandStructure.realloc(lfValues); - } - int newFillp = fillp + len; - ac.headCoding.readArrayFrom(in, lfValues, fillp, newFillp); - while (fillp < newFillp) { - int val = lfValues[fillp++]; - assert(uniqueValuesForDebug.add(val)); - assert(fillp <= maxForDebug); - last = val; - min = moreCentral(min, val); - //min2 = moreCentral2(min2, val, min); - } - fcm = ac.tailCoding; - } - Coding fc = (Coding) fcm; - if (fc.isDelta()) { - for (long state = 0;;) { - // Read a new value: - state += fc.readFrom(in); - int val; - if (fc.isSubrange()) - val = fc.reduceToUnsignedRange(state); - else - val = (int)state; - state = val; - if (fillp > 1 && (val == last || val == min)) //|| val == min2 - break; - if (fillp == lfValues.length) - lfValues = BandStructure.realloc(lfValues); - lfValues[fillp++] = val; - assert(uniqueValuesForDebug.add(val)); - assert(fillp <= maxForDebug); - last = val; - min = moreCentral(min, val); - //min2 = moreCentral(min2, val); - } - } else { - for (;;) { - int val = fc.readFrom(in); - if (fillp > 1 && (val == last || val == min)) //|| val == min2 - break; - if (fillp == lfValues.length) - lfValues = BandStructure.realloc(lfValues); - lfValues[fillp++] = val; - assert(uniqueValuesForDebug.add(val)); - assert(fillp <= maxForDebug); - last = val; - min = moreCentral(min, val); - //min2 = moreCentral2(min2, val, min); - } - } - return BandStructure.realloc(lfValues, fillp); - } - - private static int moreCentral(int x, int y) { - int kx = (x >> 31) ^ (x << 1); - int ky = (y >> 31) ^ (y << 1); - // bias kx/ky to get an unsigned comparison: - kx -= Integer.MIN_VALUE; - ky -= Integer.MIN_VALUE; - int xy = (kx < ky? x: y); - // assert that this ALU-ish version is the same: - assert(xy == moreCentralSlow(x, y)); - return xy; - } -// private static int moreCentral2(int x, int y, int min) { -// // Strict implementation of buggy 150.7 specification. -// // The bug is that the spec. says absolute-value ties are broken -// // in favor of positive numbers, but the suggested implementation -// // (also mentioned in the spec.) breaks ties in favor of negatives. -// if (x + y == 0) return (x > y? x : y); -// return min; -// } - private static int moreCentralSlow(int x, int y) { - int ax = x; - if (ax < 0) ax = -ax; - if (ax < 0) return y; //x is MIN_VALUE - int ay = y; - if (ay < 0) ay = -ay; - if (ay < 0) return x; //y is MIN_VALUE - if (ax < ay) return x; - if (ax > ay) return y; - // At this point the absolute values agree, and the negative wins. - return x < y ? x : y; - } - - static final int[] LValuesCoded - = { -1, 4, 8, 16, 32, 64, 128, 192, 224, 240, 248, 252 }; - - public byte[] getMetaCoding(Coding dflt) { - int K = fVlen; - int LCoded = 0; - if (tokenCoding instanceof Coding) { - Coding tc = (Coding) tokenCoding; - if (tc.B() == 1) { - LCoded = 1; - } else if (L >= 0) { - assert(L == tc.L()); - for (int i = 1; i < LValuesCoded.length; i++) { - if (LValuesCoded[i] == L) { LCoded = i; break; } - } - } - } - CodingMethod tokenDflt = null; - if (LCoded != 0 && tokenCoding == fitTokenCoding(fVlen, L)) { - // A simple L value is enough to recover the tokenCoding. - tokenDflt = tokenCoding; - } - int FDef = (favoredCoding == dflt)?1:0; - int UDef = (unfavoredCoding == dflt || unfavoredCoding == null)?1:0; - int TDef = (tokenCoding == tokenDflt)?1:0; - int TDefL = (TDef == 1) ? LCoded : 0; - assert(TDef == ((TDefL>0)?1:0)); - ByteArrayOutputStream bytes = new ByteArrayOutputStream(10); - bytes.write(Constants._meta_pop + FDef + 2*UDef + 4*TDefL); - try { - if (FDef == 0) bytes.write(favoredCoding.getMetaCoding(dflt)); - if (TDef == 0) bytes.write(tokenCoding.getMetaCoding(dflt)); - if (UDef == 0) bytes.write(unfavoredCoding.getMetaCoding(dflt)); - } catch (IOException ee) { - throw new RuntimeException(ee); - } - return bytes.toByteArray(); - } - public static int parseMetaCoding(byte[] bytes, int pos, Coding dflt, CodingMethod res[]) { - int op = bytes[pos++] & 0xFF; - if (op < Constants._meta_pop || op >= Constants._meta_limit) return pos-1; // backup - op -= Constants._meta_pop; - int FDef = op % 2; - int UDef = (op / 2) % 2; - int TDefL = (op / 4); - int TDef = (TDefL > 0)?1:0; - int L = LValuesCoded[TDefL]; - CodingMethod[] FCode = {dflt}, TCode = {null}, UCode = {dflt}; - if (FDef == 0) - pos = BandStructure.parseMetaCoding(bytes, pos, dflt, FCode); - if (TDef == 0) - pos = BandStructure.parseMetaCoding(bytes, pos, dflt, TCode); - if (UDef == 0) - pos = BandStructure.parseMetaCoding(bytes, pos, dflt, UCode); - PopulationCoding pop = new PopulationCoding(); - pop.L = L; // might be -1 - pop.favoredCoding = FCode[0]; - pop.tokenCoding = TCode[0]; // might be null! - pop.unfavoredCoding = UCode[0]; - res[0] = pop; - return pos; - } - - private String keyString(CodingMethod m) { - if (m instanceof Coding) - return ((Coding)m).keyString(); - if (m == null) - return "none"; - return m.toString(); - } - public String toString() { - PropMap p200 = Utils.currentPropMap(); - boolean verbose - = (p200 != null && - p200.getBoolean(Utils.COM_PREFIX+"verbose.pop")); - StringBuilder res = new StringBuilder(100); - res.append("pop(").append("fVlen=").append(fVlen); - if (verbose && fValues != null) { - res.append(" fV=["); - for (int i = 1; i <= fVlen; i++) { - res.append(i==1?"":",").append(fValues[i]); - } - res.append(";").append(computeSentinelValue()); - res.append("]"); - } - res.append(" fc=").append(keyString(favoredCoding)); - res.append(" tc=").append(keyString(tokenCoding)); - res.append(" uc=").append(keyString(unfavoredCoding)); - res.append(")"); - return res.toString(); - } -} diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/PropMap.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/PropMap.java deleted file mode 100644 index 9a7e6b071..000000000 --- a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/PropMap.java +++ /dev/null @@ -1,315 +0,0 @@ -/* - * Copyright (c) 2003, 2015, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package net.fabricmc.shade.com.sun.java.util.jar.pack; - -import net.fabricmc.shade.java.util.jar.Pack200; - -import java.io.PrintStream; -import java.io.PrintWriter; -import java.security.AccessController; -import java.security.PrivilegedAction; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.Set; -import java.util.SortedMap; -import java.util.TreeMap; - -/** - * Control block for publishing Pack200 options to the other classes. - */ - -@SuppressWarnings({"removal"}) -final class PropMap implements SortedMap { - private final TreeMap theMap = new TreeMap<>();; - - // Override: - public String put(String key, String value) { - String oldValue = theMap.put(key, value); - return oldValue; - } - - // All this other stuff is private to the current package. - // Outide clients of Pack200 do not need to use it; they can - // get by with generic SortedMap functionality. - private static Map defaultProps; - static { - Properties props = new Properties(); - - // Allow implementation selected via -Dpack.disable.native=true - String propValue = getPropertyValue(Utils.DEBUG_DISABLE_NATIVE, "false"); - props.put(Utils.DEBUG_DISABLE_NATIVE, - String.valueOf(Boolean.parseBoolean(propValue))); - - // Set the DEBUG_VERBOSE from system - int verbose = 0; - try { - verbose = Integer.decode(getPropertyValue(Utils.DEBUG_VERBOSE, "0")); - } catch (NumberFormatException e) { - } - props.put(Utils.DEBUG_VERBOSE, String.valueOf(verbose)); - - // The segment size is unlimited - props.put(Pack200.Packer.SEGMENT_LIMIT, "-1"); - - // Preserve file ordering by default. - props.put(Pack200.Packer.KEEP_FILE_ORDER, Pack200.Packer.TRUE); - - // Preserve all modification times by default. - props.put(Pack200.Packer.MODIFICATION_TIME, Pack200.Packer.KEEP); - - // Preserve deflation hints by default. - props.put(Pack200.Packer.DEFLATE_HINT, Pack200.Packer.KEEP); - - // Pass through files with unrecognized attributes by default. - props.put(Pack200.Packer.UNKNOWN_ATTRIBUTE, Pack200.Packer.PASS); - - // Pass through files with unrecognized format by default, also - // allow system property to be set - props.put(Utils.CLASS_FORMAT_ERROR, - getPropertyValue(Utils.CLASS_FORMAT_ERROR, Pack200.Packer.PASS)); - - // Default effort is 5, midway between 1 and 9. - props.put(Pack200.Packer.EFFORT, "5"); - - for (Map.Entry e : props.entrySet()) { - String key = (String) e.getKey(); - String val = (String) e.getValue(); - if (key.startsWith("attribute.")) { - e.setValue(Attribute.normalizeLayoutString(val)); - } - } - - @SuppressWarnings({"unchecked", "rawtypes"}) - HashMap temp = new HashMap(props); // shrink to fit - defaultProps = temp; - } - - private static String getPropertyValue(String key, String defaultValue) { - PrivilegedAction pa = () -> System.getProperty(key); - String s = AccessController.doPrivileged(pa); - return s != null ? s : defaultValue; - } - - PropMap() { - theMap.putAll(defaultProps); - } - - // Return a view of this map which includes only properties - // that begin with the given prefix. This is easy because - // the map is sorted, and has a subMap accessor. - SortedMap prefixMap(String prefix) { - int len = prefix.length(); - if (len == 0) - return this; - char nextch = (char)(prefix.charAt(len-1) + 1); - String limit = prefix.substring(0, len-1)+nextch; - //System.out.println(prefix+" => "+subMap(prefix, limit)); - return subMap(prefix, limit); - } - - String getProperty(String s) { - return get(s); - } - String getProperty(String s, String defaultVal) { - String val = getProperty(s); - if (val == null) - return defaultVal; - return val; - } - String setProperty(String s, String val) { - return put(s, val); - } - - // Get sequence of props for "prefix", and "prefix.*". - List getProperties(String prefix) { - Collection values = prefixMap(prefix).values(); - List res = new ArrayList<>(values.size()); - res.addAll(values); - while (res.remove(null)); - return res; - } - - private boolean toBoolean(String val) { - return Boolean.valueOf(val).booleanValue(); - } - boolean getBoolean(String s) { - return toBoolean(getProperty(s)); - } - boolean setBoolean(String s, boolean val) { - return toBoolean(setProperty(s, String.valueOf(val))); - } - int toInteger(String val) { - return toInteger(val, 0); - } - int toInteger(String val, int def) { - if (val == null) return def; - if (Pack200.Packer.TRUE.equals(val)) return 1; - if (Pack200.Packer.FALSE.equals(val)) return 0; - return Integer.parseInt(val); - } - int getInteger(String s, int def) { - return toInteger(getProperty(s), def); - } - int getInteger(String s) { - return toInteger(getProperty(s)); - } - int setInteger(String s, int val) { - return toInteger(setProperty(s, String.valueOf(val))); - } - - long toLong(String val) { - try { - return val == null ? 0 : Long.parseLong(val); - } catch (java.lang.NumberFormatException nfe) { - throw new IllegalArgumentException("Invalid value"); - } - } - long getLong(String s) { - return toLong(getProperty(s)); - } - long setLong(String s, long val) { - return toLong(setProperty(s, String.valueOf(val))); - } - - int getTime(String s) { - String sval = getProperty(s, "0"); - if (Utils.NOW.equals(sval)) { - return (int)((System.currentTimeMillis()+500)/1000); - } - long lval = toLong(sval); - final long recentSecondCount = 1000000000; - - if (lval < recentSecondCount*10 && !"0".equals(sval)) - Utils.log.warning("Supplied modtime appears to be seconds rather than milliseconds: "+sval); - - return (int)((lval+500)/1000); - } - - void list(PrintStream out) { - PrintWriter outw = new PrintWriter(out); - list(outw); - outw.flush(); - } - void list(PrintWriter out) { - out.println("#"+Utils.PACK_ZIP_ARCHIVE_MARKER_COMMENT+"["); - Set> defaults = defaultProps.entrySet(); - for (Map.Entry e : theMap.entrySet()) { - if (defaults.contains(e)) continue; - out.println(" " + e.getKey() + " = " + e.getValue()); - } - out.println("#]"); - } - - @Override - public int size() { - return theMap.size(); - } - - @Override - public boolean isEmpty() { - return theMap.isEmpty(); - } - - @Override - public boolean containsKey(Object key) { - return theMap.containsKey(key); - } - - @Override - public boolean containsValue(Object value) { - return theMap.containsValue(value); - } - - @Override - public String get(Object key) { - return theMap.get(key); - } - - @Override - public String remove(Object key) { - return theMap.remove(key); - } - - @Override - public void putAll(Map m) { - theMap.putAll(m); - } - - @Override - public void clear() { - theMap.clear(); - } - - @Override - public Set keySet() { - return theMap.keySet(); - } - - @Override - public Collection values() { - return theMap.values(); - } - - @Override - public Set> entrySet() { - return theMap.entrySet(); - } - - @Override - public Comparator comparator() { - return theMap.comparator(); - } - - @Override - public SortedMap subMap(String fromKey, String toKey) { - return theMap.subMap(fromKey, toKey); - } - - @Override - public SortedMap headMap(String toKey) { - return theMap.headMap(toKey); - } - - @Override - public SortedMap tailMap(String fromKey) { - return theMap.tailMap(fromKey); - } - - @Override - public String firstKey() { - return theMap.firstKey(); - } - - @Override - public String lastKey() { - return theMap.lastKey(); - } -} diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/TLGlobals.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/TLGlobals.java deleted file mode 100644 index 78bef9049..000000000 --- a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/TLGlobals.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (c) 2010, 2012, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package net.fabricmc.shade.com.sun.java.util.jar.pack; - -import java.util.HashMap; -import java.util.Map; -import java.util.SortedMap; - -/* - * @author ksrini - */ - -/* - * This class provides a container to hold the global variables, for packer - * and unpacker instances. This is typically stashed away in a ThreadLocal, - * and the storage is destroyed upon completion. Therefore any local - * references to these members must be eliminated appropriately to prevent a - * memory leak. - */ -class TLGlobals { - // Global environment - final PropMap props; - - // Needed by ConstantPool.java - private final Map utf8Entries; - private final Map classEntries; - private final Map literalEntries; - private final Map signatureEntries; - private final Map descriptorEntries; - private final Map memberEntries; - private final Map methodHandleEntries; - private final Map methodTypeEntries; - private final Map invokeDynamicEntries; - private final Map bootstrapMethodEntries; - - TLGlobals() { - utf8Entries = new HashMap<>(); - classEntries = new HashMap<>(); - literalEntries = new HashMap<>(); - signatureEntries = new HashMap<>(); - descriptorEntries = new HashMap<>(); - memberEntries = new HashMap<>(); - methodHandleEntries = new HashMap<>(); - methodTypeEntries = new HashMap<>(); - invokeDynamicEntries = new HashMap<>(); - bootstrapMethodEntries = new HashMap<>(); - props = new PropMap(); - } - - SortedMap getPropMap() { - return props; - } - - Map getUtf8Entries() { - return utf8Entries; - } - - Map getClassEntries() { - return classEntries; - } - - Map getLiteralEntries() { - return literalEntries; - } - - Map getDescriptorEntries() { - return descriptorEntries; - } - - Map getSignatureEntries() { - return signatureEntries; - } - - Map getMemberEntries() { - return memberEntries; - } - - Map getMethodHandleEntries() { - return methodHandleEntries; - } - - Map getMethodTypeEntries() { - return methodTypeEntries; - } - - Map getInvokeDynamicEntries() { - return invokeDynamicEntries; - } - - Map getBootstrapMethodEntries() { - return bootstrapMethodEntries; - } -} diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/UnpackerImpl.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/UnpackerImpl.java deleted file mode 100644 index 3e2d18cf4..000000000 --- a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/UnpackerImpl.java +++ /dev/null @@ -1,264 +0,0 @@ -/* - * Copyright (c) 2003, 2015, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package net.fabricmc.shade.com.sun.java.util.jar.pack; - -import net.fabricmc.shade.java.util.jar.Pack200; - -import java.io.BufferedInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.time.LocalDateTime; -import java.time.ZoneOffset; -import java.util.HashSet; -import java.util.Set; -import java.util.SortedMap; -import java.util.jar.JarEntry; -import java.util.jar.JarInputStream; -import java.util.jar.JarOutputStream; -import java.util.zip.CRC32; -import java.util.zip.CheckedOutputStream; -import java.util.zip.ZipEntry; - -/* - * Implementation of the Pack provider. - * - * @author John Rose - * @author Kumar Srinivasan - */ - - -@SuppressWarnings({"removal"}) -public class UnpackerImpl extends TLGlobals implements Pack200.Unpacker { - - public UnpackerImpl() {} - - - - /** - * Get the set of options for the pack and unpack engines. - * @return A sorted association of option key strings to option values. - */ - public SortedMap properties() { - return props; - } - - // Back-pointer to NativeUnpacker, when active. - Object _nunp; - - - public String toString() { - return Utils.getVersionString(); - } - - //Driver routines - - // The unpack worker... - /** - * Takes a packed-stream InputStream, and writes to a JarOutputStream. Internally - * the entire buffer must be read, it may be more efficient to read the packed-stream - * to a file and pass the File object, in the alternate method described below. - *

- * Closes its input but not its output. (The output can accumulate more elements.) - * @param in an InputStream. - * @param out a JarOutputStream. - * @exception IOException if an error is encountered. - */ - public synchronized void unpack(InputStream in, JarOutputStream out) throws IOException { - if (in == null) { - throw new NullPointerException("null input"); - } - if (out == null) { - throw new NullPointerException("null output"); - } - assert(Utils.currentInstance.get() == null); - - try { - Utils.currentInstance.set(this); - final int verbose = props.getInteger(Utils.DEBUG_VERBOSE); - BufferedInputStream in0 = new BufferedInputStream(in); - if (Utils.isJarMagic(Utils.readMagic(in0))) { - if (verbose > 0) - Utils.log.info("Copying unpacked JAR file..."); - Utils.copyJarFile(new JarInputStream(in0), out); - } else if (props.getBoolean(Utils.DEBUG_DISABLE_NATIVE)) { - (new DoUnpack()).run(in0, out); - in0.close(); - Utils.markJarFile(out); - } else { - try { - (new NativeUnpack(this)).run(in0, out); - } catch (UnsatisfiedLinkError | NoClassDefFoundError ex) { - // failover to java implementation - (new DoUnpack()).run(in0, out); - } - in0.close(); - Utils.markJarFile(out); - } - } finally { - _nunp = null; - Utils.currentInstance.set(null); - } - } - - /** - * Takes an input File containing the pack file, and generates a JarOutputStream. - *

- * Does not close its output. (The output can accumulate more elements.) - * @param in a File. - * @param out a JarOutputStream. - * @exception IOException if an error is encountered. - */ - public synchronized void unpack(File in, JarOutputStream out) throws IOException { - if (in == null) { - throw new NullPointerException("null input"); - } - if (out == null) { - throw new NullPointerException("null output"); - } - // Use the stream-based implementation. - // %%% Reconsider if native unpacker learns to memory-map the file. - try (FileInputStream instr = new FileInputStream(in)) { - unpack(instr, out); - } - if (props.getBoolean(Utils.UNPACK_REMOVE_PACKFILE)) { - in.delete(); - } - } - - private class DoUnpack { - final int verbose = props.getInteger(Utils.DEBUG_VERBOSE); - - { - props.setInteger(Pack200.Unpacker.PROGRESS, 0); - } - - // Here's where the bits are read from disk: - final Package pkg = new Package(); - - final boolean keepModtime - = Pack200.Packer.KEEP.equals( - props.getProperty(Utils.UNPACK_MODIFICATION_TIME, Pack200.Packer.KEEP)); - final boolean keepDeflateHint - = Pack200.Packer.KEEP.equals( - props.getProperty(Pack200.Unpacker.DEFLATE_HINT, Pack200.Packer.KEEP)); - final int modtime; - final boolean deflateHint; - { - if (!keepModtime) { - modtime = props.getTime(Utils.UNPACK_MODIFICATION_TIME); - } else { - modtime = pkg.default_modtime; - } - - deflateHint = (keepDeflateHint) ? false : - props.getBoolean(Pack200.Unpacker.DEFLATE_HINT); - } - - // Checksum apparatus. - final CRC32 crc = new CRC32(); - final ByteArrayOutputStream bufOut = new ByteArrayOutputStream(); - final OutputStream crcOut = new CheckedOutputStream(bufOut, crc); - - public void run(BufferedInputStream in, JarOutputStream out) throws IOException { - if (verbose > 0) { - props.list(System.out); - } - for (int seg = 1; ; seg++) { - unpackSegment(in, out); - - // Try to get another segment. - if (!Utils.isPackMagic(Utils.readMagic(in))) break; - if (verbose > 0) - Utils.log.info("Finished segment #"+seg); - } - } - - private void unpackSegment(InputStream in, JarOutputStream out) throws IOException { - props.setProperty(Pack200.Unpacker.PROGRESS,"0"); - // Process the output directory or jar output. - new PackageReader(pkg, in).read(); - - if (props.getBoolean("unpack.strip.debug")) pkg.stripAttributeKind("Debug"); - if (props.getBoolean("unpack.strip.compile")) pkg.stripAttributeKind("Compile"); - props.setProperty(Pack200.Unpacker.PROGRESS,"50"); - pkg.ensureAllClassFiles(); - // Now write out the files. - Set classesToWrite = new HashSet<>(pkg.getClasses()); - for (Package.File file : pkg.getFiles()) { - String name = file.nameString; - JarEntry je = new JarEntry(Utils.getJarEntryName(name)); - boolean deflate; - - deflate = (keepDeflateHint) - ? (((file.options & Constants.FO_DEFLATE_HINT) != 0) || - ((pkg.default_options & Constants.AO_DEFLATE_HINT) != 0)) - : deflateHint; - - boolean needCRC = !deflate; // STORE mode requires CRC - - if (needCRC) crc.reset(); - bufOut.reset(); - if (file.isClassStub()) { - Package.Class cls = file.getStubClass(); - assert(cls != null); - new ClassWriter(cls, needCRC ? crcOut : bufOut).write(); - classesToWrite.remove(cls); // for an error check - } else { - // collect data & maybe CRC - file.writeTo(needCRC ? crcOut : bufOut); - } - je.setMethod(deflate ? JarEntry.DEFLATED : JarEntry.STORED); - if (needCRC) { - if (verbose > 0) - Utils.log.info("stored size="+bufOut.size()+" and crc="+crc.getValue()); - - je.setMethod(JarEntry.STORED); - je.setSize(bufOut.size()); - je.setCrc(crc.getValue()); - } - if (keepModtime) { - LocalDateTime ldt = LocalDateTime - .ofEpochSecond(file.modtime, 0, ZoneOffset.UTC); - je.setTimeLocal(ldt); - } else { - je.setTime((long)modtime * 1000); - } - out.putNextEntry(je); - bufOut.writeTo(out); - out.closeEntry(); - if (verbose > 0) - Utils.log.info("Writing "+Utils.zeString((ZipEntry)je)); - } - assert(classesToWrite.isEmpty()); - props.setProperty(Pack200.Unpacker.PROGRESS,"100"); - pkg.reset(); // reset for the next segment, if any - } - } -} diff --git a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Utils.java b/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Utils.java deleted file mode 100644 index c7381fa13..000000000 --- a/src/main/java/net/fabricmc/shade/com/sun/java/util/jar/pack/Utils.java +++ /dev/null @@ -1,321 +0,0 @@ -/* - * Copyright (c) 2003, 2015, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package net.fabricmc.shade.com.sun.java.util.jar.pack; - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FilterOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.Collections; -import java.util.Date; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; -import java.util.jar.JarInputStream; -import java.util.jar.JarOutputStream; -import java.util.logging.LogManager; -import java.util.logging.Logger; -import java.util.zip.ZipEntry; - -class Utils { - static final String COM_PREFIX = "com.sun.java.util.jar.pack."; - static final String METAINF = "META-INF"; - - /* - * Outputs various diagnostic support information. - * If >0, print summary comments (e.g., constant pool info). - * If >1, print unit comments (e.g., processing of classes). - * If >2, print many comments (e.g., processing of members). - * If >3, print tons of comments (e.g., processing of references). - * (installer only) - */ - static final String DEBUG_VERBOSE = COM_PREFIX+"verbose"; - - /* - * Disables use of native code, prefers the Java-coded implementation. - * (installer only) - */ - static final String DEBUG_DISABLE_NATIVE = COM_PREFIX+"disable.native"; - - /* - * Property indicating that the unpacker should - * ignore the transmitted PACK_MODIFICATION_TIME, - * replacing it by the given value. The value can - * be a numeric string, representing the number of - * mSecs since the epoch (UTC), or the special string - * {@link #NOW}, meaning the current time (UTC). - * The default value is the special string {@link #KEEP}, - * which asks the unpacker to preserve all transmitted - * modification time information. - * (installer only) - */ - static final String UNPACK_MODIFICATION_TIME = COM_PREFIX+"unpack.modification.time"; - - /* - * Property indicating that the unpacker strip the - * Debug Attributes, if they are present, in the pack stream. - * The default value is false. - * (installer only) - */ - static final String UNPACK_STRIP_DEBUG = COM_PREFIX+"unpack.strip.debug"; - - /* - * Remove the input file after unpacking. - * (installer only) - */ - static final String UNPACK_REMOVE_PACKFILE = COM_PREFIX+"unpack.remove.packfile"; - - /* - * A possible value for MODIFICATION_TIME - */ - static final String NOW = "now"; - // Other debug options: - // com...debug.bands=false add band IDs to pack file, to verify sync - // com...dump.bands=false dump band contents to local disk - // com...no.vary.codings=false turn off coding variation heuristics - // com...no.big.strings=false turn off "big string" feature - - /* - * If this property is set to {@link #TRUE}, the packer will preserve - * the ordering of class files of the original jar in the output archive. - * The ordering is preserved only for class-files; resource files - * may be reordered. - *

- * If the packer is allowed to reorder class files, it can marginally - * decrease the transmitted size of the archive. - */ - static final String PACK_KEEP_CLASS_ORDER = COM_PREFIX+"keep.class.order"; - /* - * This string PACK200 is given as a zip comment on all JAR files - * produced by this utility. - */ - static final String PACK_ZIP_ARCHIVE_MARKER_COMMENT = "PACK200"; - - /* - * behaviour when we hit a class format error, but not necessarily - * an unknown attribute, by default it is allowed to PASS. - */ - static final String CLASS_FORMAT_ERROR = COM_PREFIX+"class.format.error"; - - // Keep a TLS point to the global data and environment. - // This makes it simpler to supply environmental options - // to the engine code, especially the native code. - static final ThreadLocal currentInstance = new ThreadLocal<>(); - - // convenience method to access the TL globals - static TLGlobals getTLGlobals() { - return currentInstance.get(); - } - - static PropMap currentPropMap() { - Object obj = currentInstance.get(); - if (obj instanceof PackerImpl) - return ((PackerImpl)obj).props; - if (obj instanceof UnpackerImpl) - return ((UnpackerImpl)obj).props; - return null; - } - - static final boolean nolog - = Boolean.getBoolean(COM_PREFIX+"nolog"); - - static final boolean SORT_MEMBERS_DESCR_MAJOR - = Boolean.getBoolean(COM_PREFIX+"sort.members.descr.major"); - - static final boolean SORT_HANDLES_KIND_MAJOR - = Boolean.getBoolean(COM_PREFIX+"sort.handles.kind.major"); - - static final boolean SORT_INDY_BSS_MAJOR - = Boolean.getBoolean(COM_PREFIX+"sort.indy.bss.major"); - - static final boolean SORT_BSS_BSM_MAJOR - = Boolean.getBoolean(COM_PREFIX+"sort.bss.bsm.major"); - - static class Pack200Logger { - private final String name; - private Logger log; - Pack200Logger(String name) { - this.name = name; - } - - private synchronized Logger getLogger() { - if (log == null) { - log = LogManager.getLogManager().getLogger(name); - } - return log; - } - - public void warning(String msg, Object param) { - getLogger().warning(msg + param); - } - - public void warning(String msg) { - warning(msg, null); - } - - public void info(String msg) { - int verbose = currentPropMap().getInteger(DEBUG_VERBOSE); - if (verbose > 0) { - if (nolog) { - System.out.println(msg); - } else { - getLogger().info(msg); - } - } - } - - public void fine(String msg) { - int verbose = currentPropMap().getInteger(DEBUG_VERBOSE); - if (verbose > 0) { - System.out.println(msg); - } - } - } - - static final Pack200Logger log - = new Pack200Logger("net.minecraftforge.java.util.jar.Pack200"); - - // Returns the Max Version String of this implementation - static String getVersionString() { - return "Pack200, Vendor: " + - System.getProperty("java.vendor") + - ", Version: " + Constants.MAX_PACKAGE_VERSION; - } - - static void markJarFile(JarOutputStream out) throws IOException { - out.setComment(PACK_ZIP_ARCHIVE_MARKER_COMMENT); - } - - // -0 mode helper - static void copyJarFile(JarInputStream in, JarOutputStream out) throws IOException { - if (in.getManifest() != null) { - ZipEntry me = new ZipEntry(JarFile.MANIFEST_NAME); - out.putNextEntry(me); - in.getManifest().write(out); - out.closeEntry(); - } - byte[] buffer = new byte[1 << 14]; - for (JarEntry je; (je = in.getNextJarEntry()) != null; ) { - out.putNextEntry(je); - for (int nr; 0 < (nr = in.read(buffer)); ) { - out.write(buffer, 0, nr); - } - } - in.close(); - markJarFile(out); // add PACK200 comment - } - static void copyJarFile(JarFile in, JarOutputStream out) throws IOException { - byte[] buffer = new byte[1 << 14]; - for (JarEntry je : Collections.list(in.entries())) { - out.putNextEntry(je); - InputStream ein = in.getInputStream(je); - for (int nr; 0 < (nr = ein.read(buffer)); ) { - out.write(buffer, 0, nr); - } - } - in.close(); - markJarFile(out); // add PACK200 comment - } - static void copyJarFile(JarInputStream in, OutputStream out) throws IOException { - // 4947205 : Peformance is slow when using pack-effort=0 - out = new BufferedOutputStream(out); - out = new NonCloser(out); // protect from JarOutputStream.close() - try (JarOutputStream jout = new JarOutputStream(out)) { - copyJarFile(in, jout); - } - } - static void copyJarFile(JarFile in, OutputStream out) throws IOException { - - // 4947205 : Peformance is slow when using pack-effort=0 - out = new BufferedOutputStream(out); - out = new NonCloser(out); // protect from JarOutputStream.close() - try (JarOutputStream jout = new JarOutputStream(out)) { - copyJarFile(in, jout); - } - } - // Wrapper to prevent closing of client-supplied stream. - private static - class NonCloser extends FilterOutputStream { - NonCloser(OutputStream out) { super(out); } - public void close() throws IOException { flush(); } - } - static String getJarEntryName(String name) { - if (name == null) return null; - return name.replace(File.separatorChar, '/'); - } - - static String zeString(ZipEntry ze) { - int store = (ze.getCompressedSize() > 0) ? - (int)( (1.0 - ((double)ze.getCompressedSize()/(double)ze.getSize()))*100 ) - : 0 ; - // Follow unzip -lv output - return ze.getSize() + "\t" + ze.getMethod() - + "\t" + ze.getCompressedSize() + "\t" - + store + "%\t" - + new Date(ze.getTime()) + "\t" - + Long.toHexString(ze.getCrc()) + "\t" - + ze.getName() ; - } - - - - static byte[] readMagic(BufferedInputStream in) throws IOException { - in.mark(4); - byte[] magic = new byte[4]; - for (int i = 0; i < magic.length; i++) { - // read 1 byte at a time, so we always get 4 - if (1 != in.read(magic, i, 1)) - break; - } - in.reset(); - return magic; - } - - // magic number recognizers - static boolean isJarMagic(byte[] magic) { - return (magic[0] == (byte)'P' && - magic[1] == (byte)'K' && - magic[2] >= 1 && - magic[2] < 8 && - magic[3] == magic[2] + 1); - } - static boolean isPackMagic(byte[] magic) { - return (magic[0] == (byte)0xCA && - magic[1] == (byte)0xFE && - magic[2] == (byte)0xD0 && - magic[3] == (byte)0x0D); - } - static boolean isGZIPMagic(byte[] magic) { - return (magic[0] == (byte)0x1F && - magic[1] == (byte)0x8B && - magic[2] == (byte)0x08); - // fourth byte is variable "flg" field - } - - private Utils() { } // do not instantiate -} diff --git a/src/main/java/net/fabricmc/shade/java/util/jar/Pack200.java b/src/main/java/net/fabricmc/shade/java/util/jar/Pack200.java deleted file mode 100644 index 6cfcb5ce8..000000000 --- a/src/main/java/net/fabricmc/shade/java/util/jar/Pack200.java +++ /dev/null @@ -1,738 +0,0 @@ -/* - * Copyright (c) 2003, 2017, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package net.fabricmc.shade.java.util.jar; - -import java.util.SortedMap; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.File; -import java.io.IOException; -import java.util.jar.JarFile; -import java.util.jar.JarInputStream; -import java.util.jar.JarOutputStream; - -import net.fabricmc.shade.com.sun.java.util.jar.pack.PackerImpl; -import net.fabricmc.shade.com.sun.java.util.jar.pack.UnpackerImpl; - - -/** - * Transforms a JAR file to or from a packed stream in Pack200 format. - * Please refer to Network Transfer Format JSR 200 Specification - *

- * Typically the packer engine is used by application developers - * to deploy or host JAR files on a website. - * The unpacker engine is used by deployment applications to - * transform the byte-stream back to JAR format. - *

- * Here is an example using packer and unpacker: - *

{@code
- *    import net.minecraftforge.java.util.jar.Pack200;
- *    import net.minecraftforge.java.util.jar.Pack200.*;
- *    ...
- *    // Create the Packer object
- *    Packer packer = Pack200.newPacker();
- *
- *    // Initialize the state by setting the desired properties
- *    Map p = packer.properties();
- *    // take more time choosing codings for better compression
- *    p.put(Packer.EFFORT, "7");  // default is "5"
- *    // use largest-possible archive segments (>10% better compression).
- *    p.put(Packer.SEGMENT_LIMIT, "-1");
- *    // reorder files for better compression.
- *    p.put(Packer.KEEP_FILE_ORDER, Packer.FALSE);
- *    // smear modification times to a single value.
- *    p.put(Packer.MODIFICATION_TIME, Packer.LATEST);
- *    // ignore all JAR deflation requests,
- *    // transmitting a single request to use "store" mode.
- *    p.put(Packer.DEFLATE_HINT, Packer.FALSE);
- *    // discard debug attributes
- *    p.put(Packer.CODE_ATTRIBUTE_PFX+"LineNumberTable", Packer.STRIP);
- *    // throw an error if an attribute is unrecognized
- *    p.put(Packer.UNKNOWN_ATTRIBUTE, Packer.ERROR);
- *    // pass one class file uncompressed:
- *    p.put(Packer.PASS_FILE_PFX+0, "mutants/Rogue.class");
- *    try {
- *        JarFile jarFile = new JarFile("/tmp/testref.jar");
- *        FileOutputStream fos = new FileOutputStream("/tmp/test.pack");
- *        // Call the packer
- *        packer.pack(jarFile, fos);
- *        jarFile.close();
- *        fos.close();
- *
- *        File f = new File("/tmp/test.pack");
- *        FileOutputStream fostream = new FileOutputStream("/tmp/test.jar");
- *        JarOutputStream jostream = new JarOutputStream(fostream);
- *        Unpacker unpacker = Pack200.newUnpacker();
- *        // Call the unpacker
- *        unpacker.unpack(f, jostream);
- *        // Must explicitly close the output.
- *        jostream.close();
- *    } catch (IOException ioe) {
- *        ioe.printStackTrace();
- *    }
- * }
- *

- * A Pack200 file compressed with gzip can be hosted on HTTP/1.1 web servers. - * The deployment applications can use "Accept-Encoding=pack200-gzip". This - * indicates to the server that the client application desires a version of - * the file encoded with Pack200 and further compressed with gzip. Please - * refer to the Java Deployment Guide for techniques and details. - *

- * Unless otherwise noted, passing a {@code null} argument to a constructor or - * method in this class will cause a {@link NullPointerException} to be thrown. - * - * @author John Rose - * @author Kumar Srinivasan - * @since 1.5 - * @deprecated This class is deprecated, and is planned for removal in a future - * release. - */ -@Deprecated -public abstract class Pack200 { - private Pack200() {} //prevent instantiation - - // Static methods of the Pack200 class. - /** - * Obtain new instance of a class that implements Packer. - *

    - *
  • If the system property {@code net.minecraftforge.java.util.jar.Pack200.Packer} - * is defined, then the value is taken to be the fully-qualified name - * of a concrete implementation class, which must implement Packer. - * This class is loaded and instantiated. If this process fails - * then an unspecified error is thrown.

  • - * - *
  • If an implementation has not been specified with the system - * property, then the system-default implementation class is instantiated, - * and the result is returned.

  • - *
- * - *

Note: The returned object is not guaranteed to operate - * correctly if multiple threads use it at the same time. - * A multi-threaded application should either allocate multiple - * packer engines, or else serialize use of one engine with a lock. - * - * @return A newly allocated Packer engine. - */ - public static synchronized Packer newPacker() { - return (Packer) newInstance(PACK_PROVIDER); - } - - - /** - * Obtain new instance of a class that implements Unpacker. - *

    - *
  • If the system property {@code net.minecraftforge.java.util.jar.Pack200.Unpacker} - * is defined, then the value is taken to be the fully-qualified - * name of a concrete implementation class, which must implement Unpacker. - * The class is loaded and instantiated. If this process fails - * then an unspecified error is thrown.

  • - * - *
  • If an implementation has not been specified with the - * system property, then the system-default implementation class - * is instantiated, and the result is returned.

  • - *
- * - *

Note: The returned object is not guaranteed to operate - * correctly if multiple threads use it at the same time. - * A multi-threaded application should either allocate multiple - * unpacker engines, or else serialize use of one engine with a lock. - * - * @return A newly allocated Unpacker engine. - */ - - public static Unpacker newUnpacker() { - return (Unpacker) newInstance(UNPACK_PROVIDER); - } - - // Interfaces - /** - * The packer engine applies various transformations to the input JAR file, - * making the pack stream highly compressible by a compressor such as - * gzip or zip. An instance of the engine can be obtained - * using {@link #newPacker}. - - * The high degree of compression is achieved - * by using a number of techniques described in the JSR 200 specification. - * Some of the techniques are sorting, re-ordering and co-location of the - * constant pool. - *

- * The pack engine is initialized to an initial state as described - * by their properties below. - * The initial state can be manipulated by getting the - * engine properties (using {@link #properties}) and storing - * the modified properties on the map. - * The resource files will be passed through with no changes at all. - * The class files will not contain identical bytes, since the unpacker - * is free to change minor class file features such as constant pool order. - * However, the class files will be semantically identical, - * as specified in - * The Java™ Virtual Machine Specification. - *

- * By default, the packer does not change the order of JAR elements. - * Also, the modification time and deflation hint of each - * JAR element is passed unchanged. - * (Any other ZIP-archive information, such as extra attributes - * giving Unix file permissions, are lost.) - *

- * Note that packing and unpacking a JAR will in general alter the - * bytewise contents of classfiles in the JAR. This means that packing - * and unpacking will in general invalidate any digital signatures - * which rely on bytewise images of JAR elements. In order both to sign - * and to pack a JAR, you must first pack and unpack the JAR to - * "normalize" it, then compute signatures on the unpacked JAR elements, - * and finally repack the signed JAR. - * Both packing steps should - * use precisely the same options, and the segment limit may also - * need to be set to "-1", to prevent accidental variation of segment - * boundaries as class file sizes change slightly. - *

- * (Here's why this works: Any reordering the packer does - * of any classfile structures is idempotent, so the second packing - * does not change the orderings produced by the first packing. - * Also, the unpacker is guaranteed by the JSR 200 specification - * to produce a specific bytewise image for any given transmission - * ordering of archive elements.) - *

- * In order to maintain backward compatibility, the pack file's version is - * set to accommodate the class files present in the input JAR file. In - * other words, the pack file version will be the latest, if the class files - * are the latest and conversely the pack file version will be the oldest - * if the class file versions are also the oldest. For intermediate class - * file versions the corresponding pack file version will be used. - * For example: - * If the input JAR-files are solely comprised of 1.5 (or lesser) - * class files, a 1.5 compatible pack file is produced. This will also be - * the case for archives that have no class files. - * If the input JAR-files contains a 1.6 class file, then the pack file - * version will be set to 1.6. - *

- * Note: Unless otherwise noted, passing a {@code null} argument to a - * constructor or method in this class will cause a {@link NullPointerException} - * to be thrown. - * - * @since 1.5 - * @deprecated This interface is deprecated, and is planned for removal in a - * future release. - */ - @Deprecated - public interface Packer { - /** - * This property is a numeral giving the estimated target size N - * (in bytes) of each archive segment. - * If a single input file requires more than N bytes, - * it will be given its own archive segment. - *

- * As a special case, a value of -1 will produce a single large - * segment with all input files, while a value of 0 will - * produce one segment for each class. - * Larger archive segments result in less fragmentation and - * better compression, but processing them requires more memory. - *

- * The size of each segment is estimated by counting the size of each - * input file to be transmitted in the segment, along with the size - * of its name and other transmitted properties. - *

- * The default is -1, which means the packer will always create a single - * segment output file. In cases where extremely large output files are - * generated, users are strongly encouraged to use segmenting or break - * up the input file into smaller JARs. - *

- * A 10Mb JAR packed without this limit will - * typically pack about 10% smaller, but the packer may require - * a larger Java heap (about ten times the segment limit). - */ - String SEGMENT_LIMIT = "pack.segment.limit"; - - /** - * If this property is set to {@link #TRUE}, the packer will transmit - * all elements in their original order within the source archive. - *

- * If it is set to {@link #FALSE}, the packer may reorder elements, - * and also remove JAR directory entries, which carry no useful - * information for Java applications. - * (Typically this enables better compression.) - *

- * The default is {@link #TRUE}, which preserves the input information, - * but may cause the transmitted archive to be larger than necessary. - */ - String KEEP_FILE_ORDER = "pack.keep.file.order"; - - - /** - * If this property is set to a single decimal digit, the packer will - * use the indicated amount of effort in compressing the archive. - * Level 1 may produce somewhat larger size and faster compression speed, - * while level 9 will take much longer but may produce better compression. - *

- * The special value 0 instructs the packer to copy through the - * original JAR file directly, with no compression. The JSR 200 - * standard requires any unpacker to understand this special case - * as a pass-through of the entire archive. - *

- * The default is 5, investing a modest amount of time to - * produce reasonable compression. - */ - String EFFORT = "pack.effort"; - - /** - * If this property is set to {@link #TRUE} or {@link #FALSE}, the packer - * will set the deflation hint accordingly in the output archive, and - * will not transmit the individual deflation hints of archive elements. - *

- * If this property is set to the special string {@link #KEEP}, the packer - * will attempt to determine an independent deflation hint for each - * available element of the input archive, and transmit this hint separately. - *

- * The default is {@link #KEEP}, which preserves the input information, - * but may cause the transmitted archive to be larger than necessary. - *

- * It is up to the unpacker implementation - * to take action upon the hint to suitably compress the elements of - * the resulting unpacked jar. - *

- * The deflation hint of a ZIP or JAR element indicates - * whether the element was deflated or stored directly. - */ - String DEFLATE_HINT = "pack.deflate.hint"; - - /** - * If this property is set to the special string {@link #LATEST}, - * the packer will attempt to determine the latest modification time, - * among all the available entries in the original archive or the latest - * modification time of all the available entries in each segment. - * This single value will be transmitted as part of the segment and applied - * to all the entries in each segment, {@link #SEGMENT_LIMIT}. - *

- * This can marginally decrease the transmitted size of the - * archive, at the expense of setting all installed files to a single - * date. - *

- * If this property is set to the special string {@link #KEEP}, - * the packer transmits a separate modification time for each input - * element. - *

- * The default is {@link #KEEP}, which preserves the input information, - * but may cause the transmitted archive to be larger than necessary. - *

- * It is up to the unpacker implementation to take action to suitably - * set the modification time of each element of its output file. - * @see #SEGMENT_LIMIT - */ - String MODIFICATION_TIME = "pack.modification.time"; - - /** - * Indicates that a file should be passed through bytewise, with no - * compression. Multiple files may be specified by specifying - * additional properties with distinct strings appended, to - * make a family of properties with the common prefix. - *

- * There is no pathname transformation, except - * that the system file separator is replaced by the JAR file - * separator '/'. - *

- * The resulting file names must match exactly as strings with their - * occurrences in the JAR file. - *

- * If a property value is a directory name, all files under that - * directory will be passed also. - *

- * Examples: - *

{@code
-         *     Map p = packer.properties();
-         *     p.put(PASS_FILE_PFX+0, "mutants/Rogue.class");
-         *     p.put(PASS_FILE_PFX+1, "mutants/Wolverine.class");
-         *     p.put(PASS_FILE_PFX+2, "mutants/Storm.class");
-         *     # Pass all files in an entire directory hierarchy:
-         *     p.put(PASS_FILE_PFX+3, "police/");
-         * }
- */ - String PASS_FILE_PFX = "pack.pass.file."; - - /// Attribute control. - - /** - * Indicates the action to take when a class-file containing an unknown - * attribute is encountered. Possible values are the strings {@link #ERROR}, - * {@link #STRIP}, and {@link #PASS}. - *

- * The string {@link #ERROR} means that the pack operation - * as a whole will fail, with an exception of type {@code IOException}. - * The string - * {@link #STRIP} means that the attribute will be dropped. - * The string - * {@link #PASS} means that the whole class-file will be passed through - * (as if it were a resource file) without compression, with a suitable warning. - * This is the default value for this property. - *

- * Examples: - *

{@code
-         *     Map p = pack200.getProperties();
-         *     p.put(UNKNOWN_ATTRIBUTE, ERROR);
-         *     p.put(UNKNOWN_ATTRIBUTE, STRIP);
-         *     p.put(UNKNOWN_ATTRIBUTE, PASS);
-         * }
- */ - String UNKNOWN_ATTRIBUTE = "pack.unknown.attribute"; - - /** - * When concatenated with a class attribute name, - * indicates the format of that attribute, - * using the layout language specified in the JSR 200 specification. - *

- * For example, the effect of this option is built in: - * {@code pack.class.attribute.SourceFile=RUH}. - *

- * The special strings {@link #ERROR}, {@link #STRIP}, and {@link #PASS} are - * also allowed, with the same meaning as {@link #UNKNOWN_ATTRIBUTE}. - * This provides a way for users to request that specific attributes be - * refused, stripped, or passed bitwise (with no class compression). - *

- * Code like this might be used to support attributes for JCOV: - *

{@code
-         *     Map p = packer.properties();
-         *     p.put(CODE_ATTRIBUTE_PFX+"CoverageTable",       "NH[PHHII]");
-         *     p.put(CODE_ATTRIBUTE_PFX+"CharacterRangeTable", "NH[PHPOHIIH]");
-         *     p.put(CLASS_ATTRIBUTE_PFX+"SourceID",           "RUH");
-         *     p.put(CLASS_ATTRIBUTE_PFX+"CompilationID",      "RUH");
-         * }
- *

- * Code like this might be used to strip debugging attributes: - *

{@code
-         *     Map p = packer.properties();
-         *     p.put(CODE_ATTRIBUTE_PFX+"LineNumberTable",    STRIP);
-         *     p.put(CODE_ATTRIBUTE_PFX+"LocalVariableTable", STRIP);
-         *     p.put(CLASS_ATTRIBUTE_PFX+"SourceFile",        STRIP);
-         * }
- */ - String CLASS_ATTRIBUTE_PFX = "pack.class.attribute."; - - /** - * When concatenated with a field attribute name, - * indicates the format of that attribute. - * For example, the effect of this option is built in: - * {@code pack.field.attribute.Deprecated=}. - * The special strings {@link #ERROR}, {@link #STRIP}, and - * {@link #PASS} are also allowed. - * @see #CLASS_ATTRIBUTE_PFX - */ - String FIELD_ATTRIBUTE_PFX = "pack.field.attribute."; - - /** - * When concatenated with a method attribute name, - * indicates the format of that attribute. - * For example, the effect of this option is built in: - * {@code pack.method.attribute.Exceptions=NH[RCH]}. - * The special strings {@link #ERROR}, {@link #STRIP}, and {@link #PASS} - * are also allowed. - * @see #CLASS_ATTRIBUTE_PFX - */ - String METHOD_ATTRIBUTE_PFX = "pack.method.attribute."; - - /** - * When concatenated with a code attribute name, - * indicates the format of that attribute. - * For example, the effect of this option is built in: - * {@code pack.code.attribute.LocalVariableTable=NH[PHOHRUHRSHH]}. - * The special strings {@link #ERROR}, {@link #STRIP}, and {@link #PASS} - * are also allowed. - * @see #CLASS_ATTRIBUTE_PFX - */ - String CODE_ATTRIBUTE_PFX = "pack.code.attribute."; - - /** - * The packer's progress as a percentage, as periodically - * updated by the packer. - * Values of 0 - 100 are normal, and -1 indicates a stall. - * Progress can be monitored by polling the value of this - * property. - *

- * At a minimum, the packer must set progress to 0 - * at the beginning of a packing operation, and to 100 - * at the end. - */ - String PROGRESS = "pack.progress"; - - /** The string "keep", a possible value for certain properties. - * @see #DEFLATE_HINT - * @see #MODIFICATION_TIME - */ - String KEEP = "keep"; - - /** The string "pass", a possible value for certain properties. - * @see #UNKNOWN_ATTRIBUTE - * @see #CLASS_ATTRIBUTE_PFX - * @see #FIELD_ATTRIBUTE_PFX - * @see #METHOD_ATTRIBUTE_PFX - * @see #CODE_ATTRIBUTE_PFX - */ - String PASS = "pass"; - - /** The string "strip", a possible value for certain properties. - * @see #UNKNOWN_ATTRIBUTE - * @see #CLASS_ATTRIBUTE_PFX - * @see #FIELD_ATTRIBUTE_PFX - * @see #METHOD_ATTRIBUTE_PFX - * @see #CODE_ATTRIBUTE_PFX - */ - String STRIP = "strip"; - - /** The string "error", a possible value for certain properties. - * @see #UNKNOWN_ATTRIBUTE - * @see #CLASS_ATTRIBUTE_PFX - * @see #FIELD_ATTRIBUTE_PFX - * @see #METHOD_ATTRIBUTE_PFX - * @see #CODE_ATTRIBUTE_PFX - */ - String ERROR = "error"; - - /** The string "true", a possible value for certain properties. - * @see #KEEP_FILE_ORDER - * @see #DEFLATE_HINT - */ - String TRUE = "true"; - - /** The string "false", a possible value for certain properties. - * @see #KEEP_FILE_ORDER - * @see #DEFLATE_HINT - */ - String FALSE = "false"; - - /** The string "latest", a possible value for certain properties. - * @see #MODIFICATION_TIME - */ - String LATEST = "latest"; - - /** - * Get the set of this engine's properties. - * This set is a "live view", so that changing its - * contents immediately affects the Packer engine, and - * changes from the engine (such as progress indications) - * are immediately visible in the map. - * - *

The property map may contain pre-defined implementation - * specific and default properties. Users are encouraged to - * read the information and fully understand the implications, - * before modifying pre-existing properties. - *

- * Implementation specific properties are prefixed with a - * package name associated with the implementor, beginning - * with {@code com.} or a similar prefix. - * All property names beginning with {@code pack.} and - * {@code unpack.} are reserved for use by this API. - *

- * Unknown properties may be ignored or rejected with an - * unspecified error, and invalid entries may cause an - * unspecified error to be thrown. - * - *

- * The returned map implements all optional {@link SortedMap} operations - * @return A sorted association of property key strings to property - * values. - */ - SortedMap properties(); - - /** - * Takes a JarFile and converts it into a Pack200 archive. - *

- * Closes its input but not its output. (Pack200 archives are appendable.) - * @param in a JarFile - * @param out an OutputStream - * @exception IOException if an error is encountered. - */ - void pack(JarFile in, OutputStream out) throws IOException ; - - /** - * Takes a JarInputStream and converts it into a Pack200 archive. - *

- * Closes its input but not its output. (Pack200 archives are appendable.) - *

- * The modification time and deflation hint attributes are not available, - * for the JAR manifest file and its containing directory. - * - * @see #MODIFICATION_TIME - * @see #DEFLATE_HINT - * @param in a JarInputStream - * @param out an OutputStream - * @exception IOException if an error is encountered. - */ - void pack(JarInputStream in, OutputStream out) throws IOException ; - } - - /** - * The unpacker engine converts the packed stream to a JAR file. - * An instance of the engine can be obtained - * using {@link #newUnpacker}. - *

- * Every JAR file produced by this engine will include the string - * "{@code PACK200}" as a zip file comment. - * This allows a deployer to detect if a JAR archive was packed and unpacked. - *

- * Note: Unless otherwise noted, passing a {@code null} argument to a - * constructor or method in this class will cause a {@link NullPointerException} - * to be thrown. - *

- * This version of the unpacker is compatible with all previous versions. - * @since 1.5 - * @deprecated This interface is deprecated, and is planned for removal in a - * future release. - */ - @Deprecated - public interface Unpacker { - - /** The string "keep", a possible value for certain properties. - * @see #DEFLATE_HINT - */ - String KEEP = "keep"; - - /** The string "true", a possible value for certain properties. - * @see #DEFLATE_HINT - */ - String TRUE = "true"; - - /** The string "false", a possible value for certain properties. - * @see #DEFLATE_HINT - */ - String FALSE = "false"; - - /** - * Property indicating that the unpacker should - * ignore all transmitted values for DEFLATE_HINT, - * replacing them by the given value, {@link #TRUE} or {@link #FALSE}. - * The default value is the special string {@link #KEEP}, - * which asks the unpacker to preserve all transmitted - * deflation hints. - */ - String DEFLATE_HINT = "unpack.deflate.hint"; - - - - /** - * The unpacker's progress as a percentage, as periodically - * updated by the unpacker. - * Values of 0 - 100 are normal, and -1 indicates a stall. - * Progress can be monitored by polling the value of this - * property. - *

- * At a minimum, the unpacker must set progress to 0 - * at the beginning of an unpacking operation, and to 100 - * at the end. - */ - String PROGRESS = "unpack.progress"; - - /** - * Get the set of this engine's properties. This set is - * a "live view", so that changing its - * contents immediately affects the Unpacker engine, and - * changes from the engine (such as progress indications) - * are immediately visible in the map. - * - *

The property map may contain pre-defined implementation - * specific and default properties. Users are encouraged to - * read the information and fully understand the implications, - * before modifying pre-existing properties. - *

- * Implementation specific properties are prefixed with a - * package name associated with the implementor, beginning - * with {@code com.} or a similar prefix. - * All property names beginning with {@code pack.} and - * {@code unpack.} are reserved for use by this API. - *

- * Unknown properties may be ignored or rejected with an - * unspecified error, and invalid entries may cause an - * unspecified error to be thrown. - * - * @return A sorted association of option key strings to option values. - */ - SortedMap properties(); - - /** - * Read a Pack200 archive, and write the encoded JAR to - * a JarOutputStream. - * The entire contents of the input stream will be read. - * It may be more efficient to read the Pack200 archive - * to a file and pass the File object, using the alternate - * method described below. - *

- * Closes its input but not its output. (The output can accumulate more elements.) - * @param in an InputStream. - * @param out a JarOutputStream. - * @exception IOException if an error is encountered. - */ - void unpack(InputStream in, JarOutputStream out) throws IOException; - - /** - * Read a Pack200 archive, and write the encoded JAR to - * a JarOutputStream. - *

- * Does not close its output. (The output can accumulate more elements.) - * @param in a File. - * @param out a JarOutputStream. - * @exception IOException if an error is encountered. - */ - void unpack(File in, JarOutputStream out) throws IOException; - } - - // Private stuff.... - - private static final String PACK_PROVIDER = "net.minecraftforge.java.util.jar.Pack200.Packer"; - private static final String UNPACK_PROVIDER = "net.minecraftforge.java.util.jar.Pack200.Unpacker"; - - private static Class packerImpl; - private static Class unpackerImpl; - - private static synchronized Object newInstance(String prop) { - String implName = "(unknown)"; - try { - Class impl = (PACK_PROVIDER.equals(prop))? packerImpl: unpackerImpl; - if (impl == null) { - // The first time, we must decide which class to use. - implName = null; - if (implName != null && !implName.equals("")) - impl = Class.forName(implName); - else if (PACK_PROVIDER.equals(prop)) - impl = PackerImpl.class; - else - impl = UnpackerImpl.class; - } - // We have a class. Now instantiate it. - @SuppressWarnings("deprecation") - Object result = impl.newInstance(); - return result; - } catch (ClassNotFoundException e) { - throw new Error("Class not found: " + implName + - ":\ncheck property " + prop + - " in your properties file.", e); - } catch (InstantiationException e) { - throw new Error("Could not instantiate: " + implName + - ":\ncheck property " + prop + - " in your properties file.", e); - } catch (IllegalAccessException e) { - throw new Error("Cannot access class: " + implName + - ":\ncheck property " + prop + - " in your properties file.", e); - } - } - -} From 53db9b3c25a93e8aca594fcb725704d30cf746d0 Mon Sep 17 00:00:00 2001 From: shedaniel Date: Tue, 20 Jul 2021 17:30:02 +0800 Subject: [PATCH 26/28] Stub intermediaries Signed-off-by: shedaniel --- .../loom/api/LoomGradleExtensionAPI.java | 2 + .../mappings/MappingsProviderImpl.java | 73 ++++++ .../extension/LoomGradleExtensionApiImpl.java | 8 + .../extension/MinecraftGradleExtension.java | 6 + .../net/fabricmc/loom/util/srg/MCPReader.java | 216 +++++++++++++----- 5 files changed, 244 insertions(+), 61 deletions(-) diff --git a/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java b/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java index 1e939f3ce..18a7f2e11 100644 --- a/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java +++ b/src/main/java/net/fabricmc/loom/api/LoomGradleExtensionAPI.java @@ -270,4 +270,6 @@ default void addTaskBeforeRun(String task) { ForgeExtensionAPI getForge(); void forge(Action action); + + Property getStubIntermediaries(); } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java index 23bf71c83..d61a88439 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java @@ -37,6 +37,7 @@ import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.StandardCopyOption; +import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -63,6 +64,7 @@ import net.fabricmc.loom.configuration.accesswidener.TransitiveAccessWidenerJarProcessor; import net.fabricmc.loom.configuration.processors.JarProcessorManager; import net.fabricmc.loom.configuration.processors.MinecraftProcessedProvider; +import net.fabricmc.loom.configuration.providers.MinecraftProvider; import net.fabricmc.loom.configuration.providers.MinecraftProviderImpl; import net.fabricmc.loom.configuration.providers.forge.MinecraftPatchedProvider; import net.fabricmc.loom.configuration.providers.forge.SrgProvider; @@ -74,6 +76,7 @@ import net.fabricmc.loom.util.srg.MCPReader; import net.fabricmc.loom.util.srg.SrgMerger; import net.fabricmc.loom.util.srg.SrgNamedWriter; +import net.fabricmc.mappingio.MappedElementKind; import net.fabricmc.mappingio.MappingReader; import net.fabricmc.mappingio.adapter.MappingNsCompleter; import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch; @@ -86,6 +89,11 @@ import net.fabricmc.stitch.commands.CommandProposeFieldNames; import net.fabricmc.stitch.commands.tinyv2.TinyFile; import net.fabricmc.stitch.commands.tinyv2.TinyV2Writer; +import net.fabricmc.stitch.representation.JarClassEntry; +import net.fabricmc.stitch.representation.JarFieldEntry; +import net.fabricmc.stitch.representation.JarMethodEntry; +import net.fabricmc.stitch.representation.JarReader; +import net.fabricmc.stitch.representation.JarRootEntry; public class MappingsProviderImpl extends DependencyProvider implements MappingsProvider { public MinecraftMappedProvider mappedProvider; @@ -573,6 +581,22 @@ public Path getIntermediaryTiny() throws IOException { if (intermediaryTiny == null) { intermediaryTiny = getMinecraftProvider().file("intermediary-v2.tiny").toPath(); + if (getExtension().getStubIntermediaries().get()) { + intermediaryTiny = getMinecraftProvider().file("stub-intermediary-v2.tiny").toPath(); + + if (isRefreshDeps() && !hasRefreshed) { + Files.deleteIfExists(intermediaryTiny); + } + + if (Files.exists(intermediaryTiny)) { + return intermediaryTiny; + } + + hasRefreshed = true; + generateIntermediary(intermediaryTiny); + return intermediaryTiny; + } + if (!Files.exists(intermediaryTiny) || (isRefreshDeps() && !hasRefreshed)) { hasRefreshed = true; @@ -588,6 +612,55 @@ public Path getIntermediaryTiny() throws IOException { return intermediaryTiny; } + private void generateIntermediary(Path output) throws IOException { + MinecraftProviderImpl provider = getExtension().getMinecraftProvider(); + JarRootEntry entry = new JarRootEntry(provider.getMergedJar()); + MemoryMappingTree tree = new MemoryMappingTree(); + + MappingNsCompleter visitor = new MappingNsCompleter(tree, Collections.singletonMap(MappingsNamespace.INTERMEDIARY.toString(), MappingsNamespace.OFFICIAL.toString()), true); + + if (visitor.visitHeader()) { + visitor.visitNamespaces(MappingsNamespace.OFFICIAL.toString(), Collections.emptyList()); + } + + if (visitor.visitContent()) { + try { + JarReader reader = new JarReader(entry); + reader.apply(); + } catch (IOException e) { + e.printStackTrace(); + } + + for (JarClassEntry classEntry : entry.getAllClasses()) { + if (visitor.visitClass(classEntry.getFullyQualifiedName())) { + if (!visitor.visitElementContent(MappedElementKind.CLASS)) return; + + for (JarFieldEntry field : classEntry.getFields()) { + if (visitor.visitField(field.getName(), field.getDescriptor())) { + if (!visitor.visitElementContent(MappedElementKind.FIELD)) return; + } + } + + for (JarMethodEntry method : classEntry.getMethods()) { + if (method.getName().startsWith("<")) continue; + + if (visitor.visitMethod(method.getName(), method.getDescriptor())) { + if (!visitor.visitElementContent(MappedElementKind.METHOD)) return; + } + } + } + } + } + + visitor.visitEnd(); + + try (Tiny2Writer writer = new Tiny2Writer(Files.newBufferedWriter(output, StandardOpenOption.CREATE), false)) { + tree.accept(writer); + } catch (IOException e) { + e.printStackTrace(); + } + } + @Override public Path mappingsWorkingDir() { return mappingsWorkingDir; diff --git a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java index d3f6d6712..ce5b558f8 100644 --- a/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java +++ b/src/main/java/net/fabricmc/loom/extension/LoomGradleExtensionApiImpl.java @@ -94,6 +94,7 @@ public abstract class LoomGradleExtensionApiImpl implements LoomGradleExtensionA private final List tasksBeforeRun = Collections.synchronizedList(new ArrayList<>()); public final List> settingsPostEdit = new ArrayList<>(); private NamedDomainObjectContainer launchConfigs; + private final Property stubIntermediaries; protected LoomGradleExtensionApiImpl(Project project, LoomFiles directories) { this.runConfigs = project.container(RunConfigSettings.class, @@ -140,6 +141,8 @@ protected LoomGradleExtensionApiImpl(Project project, LoomFiles directories) { baseName -> new LaunchProviderSettings(project, baseName)); this.archDecompilers = project.getObjects().listProperty(ArchitecturyLoomDecompiler.class) .empty(); + this.stubIntermediaries = project.getObjects().property(Boolean.class) + .convention(false); } @Override @@ -295,6 +298,11 @@ public ListProperty getArchGameDecompilers() { return archDecompilers; } + @Override + public Property getStubIntermediaries() { + return stubIntermediaries; + } + // This is here to ensure that LoomGradleExtensionApiImpl compiles without any unimplemented methods private final class EnsureCompile extends LoomGradleExtensionApiImpl { private EnsureCompile() { diff --git a/src/main/java/net/fabricmc/loom/extension/MinecraftGradleExtension.java b/src/main/java/net/fabricmc/loom/extension/MinecraftGradleExtension.java index bd33fd622..fb377e51b 100644 --- a/src/main/java/net/fabricmc/loom/extension/MinecraftGradleExtension.java +++ b/src/main/java/net/fabricmc/loom/extension/MinecraftGradleExtension.java @@ -243,4 +243,10 @@ public void forge(Action action) { reportDeprecation(); parent.forge(action); } + + @Override + public Property getStubIntermediaries() { + reportDeprecation(); + return parent.getStubIntermediaries(); + } } diff --git a/src/main/java/net/fabricmc/loom/util/srg/MCPReader.java b/src/main/java/net/fabricmc/loom/util/srg/MCPReader.java index 96382bb5d..a1216547a 100644 --- a/src/main/java/net/fabricmc/loom/util/srg/MCPReader.java +++ b/src/main/java/net/fabricmc/loom/util/srg/MCPReader.java @@ -37,11 +37,14 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.function.BiConsumer; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.opencsv.CSVReader; import com.opencsv.exceptions.CsvValidationException; +import dev.architectury.refmapremapper.utils.DescriptorRemapper; import org.apache.commons.io.IOUtils; import org.cadixdev.lorenz.MappingSet; import org.cadixdev.lorenz.io.srg.tsrg.TSrgReader; @@ -72,11 +75,11 @@ public MCPReader(Path intermediaryTinyPath, Path srgTsrgPath) { } public TinyFile read(Path mcpJar) throws IOException { - Map srgTokens = readSrg(); + Map, String> srgTokens = readSrg(); TinyFile intermediaryTiny = TinyV2Reader.read(intermediaryTinyPath); - Map intermediaryToMCPMap = createIntermediaryToMCPMap(intermediaryTiny, srgTokens); - Map intermediaryToDocsMap = new HashMap<>(); - Map> intermediaryToParamsMap = new HashMap<>(); + Map, String> intermediaryToMCPMap = createIntermediaryToMCPMap(intermediaryTiny, srgTokens); + Map, String[]> intermediaryToDocsMap = new HashMap<>(); + Map, Map> intermediaryToParamsMap = new HashMap<>(); try { injectMcp(mcpJar, intermediaryToMCPMap, intermediaryToDocsMap, intermediaryToParamsMap); @@ -88,43 +91,54 @@ public TinyFile read(Path mcpJar) throws IOException { return intermediaryTiny; } - private Map createIntermediaryToMCPMap(TinyFile tiny, Map officialToMCP) { - Map map = new HashMap<>(); + private Map, String> createIntermediaryToMCPMap(TinyFile tiny, Map, String> officialToMCP) { + Map, String> map = new HashMap<>(); + BiConsumer, MemberToken> adder = (intermediary, obf) -> { + String mcp = officialToMCP.get(obf); + + if (mcp != null && !intermediary.name.equals(mcp)) { + map.put(intermediary, mcp); + } + }; for (TinyClass tinyClass : tiny.getClassEntries()) { String classObf = tinyClass.getMapping().get(0); String classIntermediary = tinyClass.getMapping().get(1); - MemberToken classTokenObf = MemberToken.ofClass(classObf); + MemberToken classTokenIntermediary = MemberToken.ofClass(classIntermediary); + MemberToken classTokenObf = MemberToken.ofClass(classObf); - if (officialToMCP.containsKey(classTokenObf)) { - map.put(classIntermediary, officialToMCP.get(classTokenObf)); - } + adder.accept(classTokenIntermediary, classTokenObf); for (TinyField tinyField : tinyClass.getFields()) { String fieldObf = tinyField.getMapping().get(0); String fieldIntermediary = tinyField.getMapping().get(1); - MemberToken fieldTokenObf = MemberToken.ofField(classTokenObf, fieldObf); + MemberToken fieldTokenObf = MemberToken.ofField(classTokenObf, fieldObf); - if (officialToMCP.containsKey(fieldTokenObf)) { - map.put(fieldIntermediary, officialToMCP.get(fieldTokenObf)); - } + adder.accept(MemberToken.ofField(classTokenIntermediary, fieldIntermediary), fieldTokenObf); } for (TinyMethod tinyMethod : tinyClass.getMethods()) { String methodObf = tinyMethod.getMapping().get(0); String methodIntermediary = tinyMethod.getMapping().get(1); - MemberToken methodTokenObf = MemberToken.ofMethod(classTokenObf, methodObf, tinyMethod.getMethodDescriptorInFirstNamespace()); + String methodDescIntermediary = remapDescriptor(tinyMethod.getMethodDescriptorInFirstNamespace(), tiny); + MemberToken methodTokenObf = MemberToken.ofMethod(classTokenObf, methodObf, tinyMethod.getMethodDescriptorInFirstNamespace()); - if (officialToMCP.containsKey(methodTokenObf)) { - map.put(methodIntermediary, officialToMCP.get(methodTokenObf)); - } + adder.accept(MemberToken.ofMethod(classTokenIntermediary, methodIntermediary, methodDescIntermediary), methodTokenObf); } } return map; } - private void mergeTokensIntoIntermediary(TinyFile tiny, Map intermediaryToMCPMap, Map intermediaryToDocsMap, Map> intermediaryToParamsMap) { + private String remapDescriptor(String descriptor, TinyFile file) { + return DescriptorRemapper.remapDescriptor(descriptor, s -> { + TinyClass tinyClass = file.mapClassesByFirstNamespace().get(s); + return tinyClass == null ? s : tinyClass.getMapping().get(1); + }); + } + + private void mergeTokensIntoIntermediary(TinyFile tiny, Map, String> intermediaryToMCPMap, Map, String[]> intermediaryToDocsMap, + Map, Map> intermediaryToParamsMap) { stripTinyWithParametersAndLocal(tiny); // We will be adding the "named" namespace with MCP @@ -132,12 +146,14 @@ private void mergeTokensIntoIntermediary(TinyFile tiny, Map inte for (TinyClass tinyClass : tiny.getClassEntries()) { String classIntermediary = tinyClass.getMapping().get(1); - tinyClass.getMapping().add(intermediaryToMCPMap.getOrDefault(classIntermediary, classIntermediary)); + MemberToken classMemberToken = MemberToken.ofClass(classIntermediary); + tinyClass.getMapping().add(intermediaryToMCPMap.getOrDefault(classMemberToken, classIntermediary)); for (TinyField tinyField : tinyClass.getFields()) { String fieldIntermediary = tinyField.getMapping().get(1); - String[] docs = intermediaryToDocsMap.get(fieldIntermediary); - tinyField.getMapping().add(intermediaryToMCPMap.getOrDefault(fieldIntermediary, fieldIntermediary)); + MemberToken fieldMemberToken = MemberToken.ofField(classMemberToken, fieldIntermediary); + String[] docs = intermediaryToDocsMap.get(fieldMemberToken); + tinyField.getMapping().add(intermediaryToMCPMap.getOrDefault(fieldMemberToken, fieldIntermediary)); if (docs != null) { tinyField.getComments().clear(); @@ -147,15 +163,17 @@ private void mergeTokensIntoIntermediary(TinyFile tiny, Map inte for (TinyMethod tinyMethod : tinyClass.getMethods()) { String methodIntermediary = tinyMethod.getMapping().get(1); - String[] docs = intermediaryToDocsMap.get(methodIntermediary); - tinyMethod.getMapping().add(intermediaryToMCPMap.getOrDefault(methodIntermediary, methodIntermediary)); + String methodDescIntermediary = remapDescriptor(tinyMethod.getMethodDescriptorInFirstNamespace(), tiny); + MemberToken methodMemberToken = MemberToken.ofMethod(classMemberToken, methodIntermediary, methodDescIntermediary); + String[] docs = intermediaryToDocsMap.get(methodMemberToken); + tinyMethod.getMapping().add(intermediaryToMCPMap.getOrDefault(methodMemberToken, methodIntermediary)); if (docs != null) { tinyMethod.getComments().clear(); tinyMethod.getComments().addAll(Arrays.asList(docs)); } - Map params = intermediaryToParamsMap.get(methodIntermediary); + Map params = intermediaryToParamsMap.get(methodMemberToken); if (params != null) { for (Map.Entry entry : params.entrySet()) { @@ -182,8 +200,8 @@ private void stripTinyWithParametersAndLocal(TinyFile tiny) { } } - private Map readSrg() throws IOException { - Map tokens = new HashMap<>(); + private Map, String> readSrg() throws IOException { + Map, String> tokens = new HashMap<>(); try (BufferedReader reader = Files.newBufferedReader(srgTsrgPath, StandardCharsets.UTF_8)) { String content = IOUtils.toString(reader); @@ -202,14 +220,14 @@ private Map readSrg() throws IOException { return tokens; } - private void readTsrg2(Map tokens, String content) throws IOException { + private void readTsrg2(Map, String> tokens, String content) throws IOException { MemoryMappingTree tree = new MemoryMappingTree(); MappingReader.read(new StringReader(content), tree); int obfIndex = tree.getNamespaceId("obf"); int srgIndex = tree.getNamespaceId("srg"); for (MappingTree.ClassMapping classDef : tree.getClasses()) { - MemberToken ofClass = MemberToken.ofClass(classDef.getName(obfIndex)); + MemberToken ofClass = MemberToken.ofClass(classDef.getName(obfIndex)); tokens.put(ofClass, classDef.getName(srgIndex)); for (MappingTree.FieldMapping fieldDef : classDef.getFields()) { @@ -223,17 +241,19 @@ private void readTsrg2(Map tokens, String content) throws I } } - private void injectMcp(Path mcpJar, Map intermediaryToSrgMap, Map intermediaryToDocsMap, Map> intermediaryToParamsMap) + private void injectMcp(Path mcpJar, Map, String> intermediaryToSrgMap, Map, String[]> intermediaryToDocsMap, + Map, Map> intermediaryToParamsMap) throws IOException, CsvValidationException { - Map> srgToIntermediary = inverseMap(intermediaryToSrgMap); - Map> simpleSrgToIntermediary = new HashMap<>(); + Map>> srgToIntermediary = inverseMap(intermediaryToSrgMap); + Map>> simpleSrgToIntermediary = new HashMap<>(); Pattern methodPattern = Pattern.compile("(func_\\d*)_.*"); - for (Map.Entry> entry : srgToIntermediary.entrySet()) { + for (Map.Entry>> entry : srgToIntermediary.entrySet()) { Matcher matcher = methodPattern.matcher(entry.getKey()); if (matcher.matches()) { - simpleSrgToIntermediary.put(matcher.group(1), entry.getValue()); + simpleSrgToIntermediary.put(matcher.group(1), + (List>) (List>) entry.getValue()); } } @@ -248,11 +268,11 @@ private void injectMcp(Path mcpJar, Map intermediaryToSrgMap, Ma String[] line; while ((line = reader.readNext()) != null) { - List intermediaryField = srgToIntermediary.get(line[0]); + List> intermediaryField = (List>) (List>) srgToIntermediary.get(line[0]); String[] docs = line[3].split("\n"); if (intermediaryField != null) { - for (String s : intermediaryField) { + for (MemberToken s : intermediaryField) { intermediaryToSrgMap.put(s, line[1]); if (!line[3].trim().isEmpty() && docs.length > 0) { @@ -268,11 +288,11 @@ private void injectMcp(Path mcpJar, Map intermediaryToSrgMap, Ma String[] line; while ((line = reader.readNext()) != null) { - List intermediaryMethod = srgToIntermediary.get(line[0]); + List> intermediaryMethod = (List>) (List>) srgToIntermediary.get(line[0]); String[] docs = line[3].split("\n"); if (intermediaryMethod != null) { - for (String s : intermediaryMethod) { + for (MemberToken s : intermediaryMethod) { intermediaryToSrgMap.put(s, line[1]); if (!line[3].trim().isEmpty() && docs.length > 0) { @@ -295,10 +315,10 @@ private void injectMcp(Path mcpJar, Map intermediaryToSrgMap, Ma String named = line[1]; String srgMethodStartWith = "func_" + param.group(1); int lvIndex = Integer.parseInt(param.group(2)); - List intermediaryMethod = simpleSrgToIntermediary.get(srgMethodStartWith); + List> intermediaryMethod = simpleSrgToIntermediary.get(srgMethodStartWith); if (intermediaryMethod != null) { - for (String s : intermediaryMethod) { + for (MemberToken s : intermediaryMethod) { intermediaryToParamsMap.computeIfAbsent(s, s1 -> new HashMap<>()).put(lvIndex, named); } } @@ -309,18 +329,18 @@ private void injectMcp(Path mcpJar, Map intermediaryToSrgMap, Ma } } - private Map> inverseMap(Map intermediaryToMCPMap) { - Map> map = new HashMap<>(); + private Map> inverseMap(Map intermediaryToMCPMap) { + Map> map = new HashMap<>(); - for (Map.Entry token : intermediaryToMCPMap.entrySet()) { + for (Map.Entry token : intermediaryToMCPMap.entrySet()) { map.computeIfAbsent(token.getValue(), s -> new ArrayList<>()).add(token.getKey()); } return map; } - private void appendClass(Map tokens, ClassMapping classMapping) { - MemberToken ofClass = MemberToken.ofClass(classMapping.getFullObfuscatedName()); + private void appendClass(Map, String> tokens, ClassMapping classMapping) { + MemberToken ofClass = MemberToken.ofClass(classMapping.getFullObfuscatedName()); tokens.put(ofClass, classMapping.getFullDeobfuscatedName()); for (FieldMapping fieldMapping : classMapping.getFieldMappings()) { @@ -336,28 +356,102 @@ private void appendClass(Map tokens, ClassMapping cla } } - private record MemberToken( - TokenType type, - @Nullable MCPReader.MemberToken owner, - String name, - @Nullable String descriptor - ) { - static MemberToken ofClass(String name) { - return new MemberToken(TokenType.CLASS, null, name, null); + private interface TokenType { + enum Class implements TokenType { } - static MemberToken ofField(MemberToken owner, String name) { - return new MemberToken(TokenType.FIELD, owner, name, null); + enum Method implements TokenType { } - static MemberToken ofMethod(MemberToken owner, String name, String descriptor) { - return new MemberToken(TokenType.METHOD, owner, name, descriptor); + enum Field implements TokenType { } } - private enum TokenType { - CLASS, - METHOD, - FIELD + private static class MemberToken { + @Nullable + private MemberToken owner; + private String name; + @Nullable private String descriptor; + + public MemberToken(@Nullable MemberToken owner, String name, @Nullable String descriptor) { + this.owner = owner; + this.name = name; + this.descriptor = descriptor; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof MemberToken that)) return false; + return Objects.equals(owner, that.owner) && Objects.equals(name, that.name) && Objects.equals(descriptor, that.descriptor); + } + + @Override + public int hashCode() { + return Objects.hash(owner, name, descriptor); + } + + static class ClassToken extends MemberToken { + ClassToken(String name) { + super(null, name, null); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof ClassToken)) return false; + return super.equals(o); + } + + @Override + public int hashCode() { + return (1 + super.hashCode()) * 31 + 1; + } + } + + static class FieldToken extends MemberToken { + FieldToken(@Nullable MemberToken owner, String name) { + super(owner, name, null); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof FieldToken)) return false; + return super.equals(o); + } + + @Override + public int hashCode() { + return (1 + super.hashCode()) * 31 + 2; + } + } + + static class MethodToken extends MemberToken { + MethodToken(@Nullable MemberToken owner, String name, @Nullable String descriptor) { + super(owner, name, descriptor); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof MethodToken)) return false; + return super.equals(o); + } + + @Override + public int hashCode() { + return (1 + super.hashCode()) * 31 + 3; + } + } + + static MemberToken ofClass(String name) { + return new MemberToken.ClassToken(name); + } + + static MemberToken ofField(MemberToken owner, String name) { + return new MemberToken.FieldToken(owner, name); + } + + static MemberToken ofMethod(MemberToken owner, String name, String descriptor) { + return new MemberToken.MethodToken(owner, name, descriptor); + } } } From f4d0a19b4f0fb720e3290f6f6dcd0c9f7d5f8dcc Mon Sep 17 00:00:00 2001 From: shedaniel Date: Tue, 14 Dec 2021 01:16:43 +0800 Subject: [PATCH 27/28] Remove ServiceLoader Signed-off-by: shedaniel --- .../net/fabricmc/loom/api/ForgeExtensionAPI.java | 4 ++++ .../providers/forge/fg2/FG2TaskApplyBinPatches.java | 13 +++++-------- .../fabricmc/loom/extension/ForgeExtensionImpl.java | 8 ++++++++ 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/main/java/net/fabricmc/loom/api/ForgeExtensionAPI.java b/src/main/java/net/fabricmc/loom/api/ForgeExtensionAPI.java index e455d742f..6b77f8420 100644 --- a/src/main/java/net/fabricmc/loom/api/ForgeExtensionAPI.java +++ b/src/main/java/net/fabricmc/loom/api/ForgeExtensionAPI.java @@ -33,6 +33,8 @@ import org.gradle.api.provider.SetProperty; import org.jetbrains.annotations.ApiStatus; +import net.fabricmc.loom.configuration.providers.forge.fg2.Pack200Provider; + /** * This is the forge extension api available exposed to build scripts. */ @@ -148,4 +150,6 @@ interface DataGenConsumer { * @see ForgeLocalMod */ NamedDomainObjectContainer getLocalMods(); + + Property getPack200Provider(); } diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/FG2TaskApplyBinPatches.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/FG2TaskApplyBinPatches.java index e615f48bc..cef631791 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/FG2TaskApplyBinPatches.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/FG2TaskApplyBinPatches.java @@ -30,8 +30,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; -import java.util.List; -import java.util.ServiceLoader; import java.util.jar.JarEntry; import java.util.jar.JarInputStream; import java.util.jar.JarOutputStream; @@ -51,6 +49,8 @@ import lzma.streams.LzmaInputStream; import org.gradle.api.Project; +import net.fabricmc.loom.LoomGradleExtension; + public class FG2TaskApplyBinPatches { private final HashMap patches = Maps.newHashMap(); private final GDiffPatcher patcher = new GDiffPatcher(); @@ -138,16 +138,13 @@ public void setup(File patches, String side) { LzmaInputStream binpatchesDecompressed = new LzmaInputStream(new FileInputStream(patches), new Decoder()); ByteArrayOutputStream jarBytes = new ByteArrayOutputStream(); JarOutputStream jos = new JarOutputStream(jarBytes); - List> loader = ServiceLoader.load(Pack200Provider.class) - .stream().toList(); + Pack200Provider provider = LoomGradleExtension.get(project).getForge().getPack200Provider().get(); - if (loader.isEmpty()) { + if (provider == null) { throw new IllegalStateException("No provider for Pack200 has been found. Did you declare a provider?"); - } else if (loader.size() > 1) { - throw new IllegalStateException("Multiple providers for Pack200 have been found, this is not supported. Did you properly declare a provider?"); } - loader.get(0).get().unpack(binpatchesDecompressed, jos); + provider.unpack(binpatchesDecompressed, jos); jis = new JarInputStream(new ByteArrayInputStream(jarBytes.toByteArray())); } catch (Exception e) { throw new RuntimeException(e); diff --git a/src/main/java/net/fabricmc/loom/extension/ForgeExtensionImpl.java b/src/main/java/net/fabricmc/loom/extension/ForgeExtensionImpl.java index 25da6717a..5a4956d95 100644 --- a/src/main/java/net/fabricmc/loom/extension/ForgeExtensionImpl.java +++ b/src/main/java/net/fabricmc/loom/extension/ForgeExtensionImpl.java @@ -42,6 +42,7 @@ import net.fabricmc.loom.api.ForgeExtensionAPI; import net.fabricmc.loom.api.ForgeLocalMod; import net.fabricmc.loom.configuration.ide.RunConfigSettings; +import net.fabricmc.loom.configuration.providers.forge.fg2.Pack200Provider; public class ForgeExtensionImpl implements ForgeExtensionAPI { private final LoomGradleExtension extension; @@ -52,6 +53,7 @@ public class ForgeExtensionImpl implements ForgeExtensionAPI { private final Property useCustomMixin; private final List dataGenMods = new ArrayList<>(); // not a property because it has custom adding logic private final NamedDomainObjectContainer localMods; + private final Property pack200Provider; @Inject public ForgeExtensionImpl(Project project, LoomGradleExtension extension) { @@ -63,6 +65,7 @@ public ForgeExtensionImpl(Project project, LoomGradleExtension extension) { useCustomMixin = project.getObjects().property(Boolean.class).convention(true); localMods = project.container(ForgeLocalMod.class, baseName -> new ForgeLocalMod(project, baseName, new ArrayList<>())); + pack200Provider = project.getObjects().property(Pack200Provider.class); // Create default mod from main source set localMods(mod -> mod.create("main").add("main")); @@ -133,4 +136,9 @@ public void localMods(Action> action) public NamedDomainObjectContainer getLocalMods() { return localMods; } + + @Override + public Property getPack200Provider() { + return pack200Provider; + } } From 718c0be4339752b04fd5c20b8505212c84b27b0c Mon Sep 17 00:00:00 2001 From: Wagyourtail Date: Tue, 14 Dec 2021 00:15:06 -0700 Subject: [PATCH 28/28] remove isForgeAndOfficial stuff from fg2 also remove extra fg2 flag --- .../providers/forge/McpConfigProvider.java | 7 ----- .../fg2/MinecraftPatchedProviderFG2.java | 26 +++++-------------- .../mappings/MappingsProviderImpl.java | 2 +- 3 files changed, 8 insertions(+), 27 deletions(-) diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java index f79149180..a1669c462 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/McpConfigProvider.java @@ -58,16 +58,10 @@ public class McpConfigProvider extends DependencyProvider { private String mappingsPath; private RemapAction remapAction; - private boolean isFG2 = false; - public McpConfigProvider(Project project) { super(project); } - public boolean isFG2() { - return isFG2; - } - @Override public void provide(DependencyInfo dependency, Consumer postPopulationScheduler) throws Exception { init(dependency.getDependency().getVersion()); @@ -77,7 +71,6 @@ public void provide(DependencyInfo dependency, Consumer postPopulation if (getExtension().getForgeProvider().isFG2()) { official = false; mappingsPath = ZipUtils.contains(mcpZip, "joined.srg") ? "joined.srg" : "config/joined.tsrg"; - isFG2 = mappingsPath.endsWith(".srg"); remapAction = null; if (!Files.exists(mcp) || isRefreshDeps()) { diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/MinecraftPatchedProviderFG2.java b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/MinecraftPatchedProviderFG2.java index a6ebfce9a..5ba25dfb1 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/MinecraftPatchedProviderFG2.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/forge/fg2/MinecraftPatchedProviderFG2.java @@ -92,11 +92,12 @@ public void initFiles() throws IOException { @Override protected File[] getGlobalCaches() { File[] files = { + minecraftClientJar, + minecraftServerJar, minecraftClientPatchedJar, minecraftServerPatchedJar, minecraftMergedPatchedJar, - minecraftMergedPatchedSrgJar, - minecraftClientExtra, + minecraftMergedPatchedSrgJar }; if (forgeMergedJar != null) { @@ -155,18 +156,10 @@ public void endTransform() throws Exception { // Step 5: Remap Patched AT & Forge to Official (global or project) if (dirty) { remapPatchedJar(getProject().getLogger()); - - if (getExtension().isForgeAndOfficial()) { - fillClientExtraJar(); - } } this.filesDirty = dirty; this.dirty = false; - - if (getExtension().isForgeAndOfficial()) { - addDependency(minecraftClientExtra, Constants.Configurations.FORGE_EXTRA); - } } public enum Environment { @@ -214,9 +207,7 @@ private void patchJars(Logger logger) throws IOException { copyMissingClasses(environment.srgJar.apply(this), environment.patchedSrgJar.apply(this)); deleteParameterNames(environment.patchedSrgJar.apply(this)); - if (getExtension().isForgeAndNotOfficial()) { - fixParameterAnnotation(environment.patchedSrgJar.apply(this)); - } + fixParameterAnnotation(environment.patchedSrgJar.apply(this)); }); logger.lifecycle(":patched jars in " + stopwatch.stop()); @@ -253,12 +244,9 @@ protected void mergeJars(Logger logger) throws Exception { logger.lifecycle(":copying resources"); // Copy resources - if (getExtension().isForgeAndNotOfficial()) { - // Copy resources - MinecraftProviderImpl minecraftProvider = getExtension().getMinecraftProvider(); - copyNonClassFiles(minecraftProvider.minecraftClientJar, minecraftMergedPatchedJar); - copyNonClassFiles(minecraftProvider.getMinecraftServerJar(), minecraftMergedPatchedJar); - } + MinecraftProviderImpl minecraftProvider = getExtension().getMinecraftProvider(); + copyNonClassFiles(minecraftProvider.minecraftClientJar, minecraftMergedPatchedJar); + copyNonClassFiles(minecraftProvider.getMinecraftServerJar(), minecraftMergedPatchedJar); } protected void remapPatchedJarToSrg(Logger logger) throws Exception { diff --git a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java index a73f3ee74..2aebab2a2 100644 --- a/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java +++ b/src/main/java/net/fabricmc/loom/configuration/providers/mappings/MappingsProviderImpl.java @@ -157,7 +157,7 @@ public void provide(DependencyInfo dependency, Consumer postPopulation } if (getExtension().isForge()) { - if (getExtension().getMcpConfigProvider().isFG2()) { + if (getExtension().getForgeProvider().isFG2()) { patchedProvider = new MinecraftPatchedProviderFG2(getProject()); patchedProvider.provide(dependency, postPopulationScheduler); } else {