/*
 * Copyright (c) 2016, 2020, 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 jdk.tools.jlink.internal.plugins;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.util.EnumSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;

import jdk.internal.access.JavaLangInvokeAccess;
import jdk.internal.access.SharedSecrets;
import jdk.tools.jlink.plugin.PluginException;
import jdk.tools.jlink.plugin.ResourcePool;
import jdk.tools.jlink.plugin.ResourcePoolBuilder;
import jdk.tools.jlink.plugin.ResourcePoolEntry;

Plugin to generate java.lang.invoke classes. The plugin reads in a file generated by running any application with -Djava.lang.invoke.MethodHandle.TRACE_RESOLVE=true. This is done automatically during build, see make/GenerateLinkOptData.gmk. See build/tools/classlist/HelloClasslist.java for the training application. HelloClasslist tries to reflect common use of java.lang.invoke during early startup and warmup in various applications. To ensure a good default trade-off between static footprint and startup the application should be relatively conservative. When using jlink to build a custom application runtime, generating a trace file using -Djava.lang.invoke.MethodHandle.TRACE_RESOLVE=true and feeding that into jlink using --generate-jli-classes=@trace_file can help improve startup time.
/** * Plugin to generate java.lang.invoke classes. * * The plugin reads in a file generated by running any application with * {@code -Djava.lang.invoke.MethodHandle.TRACE_RESOLVE=true}. This is done * automatically during build, see make/GenerateLinkOptData.gmk. See * build/tools/classlist/HelloClasslist.java for the training application. * * HelloClasslist tries to reflect common use of java.lang.invoke during early * startup and warmup in various applications. To ensure a good default * trade-off between static footprint and startup the application should be * relatively conservative. * * When using jlink to build a custom application runtime, generating a trace * file using {@code -Djava.lang.invoke.MethodHandle.TRACE_RESOLVE=true} and * feeding that into jlink using {@code --generate-jli-classes=@trace_file} can * help improve startup time. */
public final class GenerateJLIClassesPlugin extends AbstractPlugin { private static final String DEFAULT_TRACE_FILE = "default_jli_trace.txt"; private static final JavaLangInvokeAccess JLIA = SharedSecrets.getJavaLangInvokeAccess(); private String mainArgument; private Stream<String> traceFileStream; public GenerateJLIClassesPlugin() { super("generate-jli-classes"); } @Override public Set<State> getState() { return EnumSet.of(State.AUTO_ENABLED, State.FUNCTIONAL); } @Override public boolean hasArguments() { return true; } @Override public void configure(Map<String, String> config) { mainArgument = config.get(getName()); } public void initialize(ResourcePool in) { // Load configuration from the contents in the supplied input file // - if none was supplied we look for the default file if (mainArgument == null || !mainArgument.startsWith("@")) { try (InputStream traceFile = this.getClass().getResourceAsStream(DEFAULT_TRACE_FILE)) { if (traceFile != null) { traceFileStream = new BufferedReader(new InputStreamReader(traceFile)).lines(); } } catch (Exception e) { throw new PluginException("Couldn't read " + DEFAULT_TRACE_FILE, e); } } else { File file = new File(mainArgument.substring(1)); if (file.exists()) { traceFileStream = fileLines(file); } } } private Stream<String> fileLines(File file) { try { return Files.lines(file.toPath()); } catch (IOException io) { throw new PluginException("Couldn't read file"); } } @Override public ResourcePool transform(ResourcePool in, ResourcePoolBuilder out) { initialize(in); // Copy all but DMH_ENTRY to out in.transformAndCopy(entry -> { // No trace file given. Copy all entries. if (traceFileStream == null) return entry; // filter out placeholder entries String path = entry.path(); if (path.equals(DIRECT_METHOD_HOLDER_ENTRY) || path.equals(DELEGATING_METHOD_HOLDER_ENTRY) || path.equals(INVOKERS_HOLDER_ENTRY) || path.equals(BASIC_FORMS_HOLDER_ENTRY)) { return null; } else { return entry; } }, out); // Generate Holder classes if (traceFileStream != null) { try { JLIA.generateHolderClasses(traceFileStream) .forEach((cn, bytes) -> { String entryName = "/java.base/" + cn + ".class"; ResourcePoolEntry ndata = ResourcePoolEntry.create(entryName, bytes); out.add(ndata); }); } catch (Exception ex) { throw new PluginException(ex); } } return out.build(); } private static final String DIRECT_METHOD_HOLDER_ENTRY = "/java.base/java/lang/invoke/DirectMethodHandle$Holder.class"; private static final String DELEGATING_METHOD_HOLDER_ENTRY = "/java.base/java/lang/invoke/DelegatingMethodHandle$Holder.class"; private static final String BASIC_FORMS_HOLDER_ENTRY = "/java.base/java/lang/invoke/LambdaForm$Holder.class"; private static final String INVOKERS_HOLDER_ENTRY = "/java.base/java/lang/invoke/Invokers$Holder.class"; }