/* * Copyright 2015 Cel Skeggs, 2016 Alexander Mackworth. * * This file is part of the CCRE, the Common Chicken Runtime Engine. * * The CCRE 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 3 of the License, or (at your option) any * later version. * * The CCRE 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 the CCRE. If not, see <http://www.gnu.org/licenses/>. */ package ccre.deployment; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.jar.JarOutputStream; import java.util.jar.Manifest; import java.util.zip.ZipEntry; /** * A helper class for putting together a Jar from its individual components. * * @author skeggsc */ public class JarBuilder { /** * A constant that means that a Jar should be preserved after JVM exit. * * @see Jar */ public static final boolean PRESERVE = true; /** * A constant that means that a Jar should be deleted after JVM exit. * * @see Jar */ public static final boolean DELETE = false; /** * A constant that means that the manifest should be taken from an Artifact * when added to the builder. * * @see JarBuilder#addAll(Artifact, boolean) */ public static final boolean KEEP_MANIFEST = true; /** * A constant that means that the manifest should not be taken from an * Artifact when added to the builder. * * @see JarBuilder#addAll(Artifact, boolean) */ public static final boolean DISCARD_MANIFEST = false; private static final String MANIFEST = "META-INF/MANIFEST.MF"; private final File tempOut; private final JarOutputStream jout; private final boolean preserved; /** * Creates a new JarBuilder that optionally preserves the result. No * manifest is specified. * * @param preserve if the generated Jar should be preserved. * @throws IOException if the new temporary Jar cannot be created. * @see Jar for an explanation of preservation. */ public JarBuilder(boolean preserve) throws IOException { this(null, preserve); } /** * Creates a new JarBuilder that optionally preserves the result. A manifest * is specified. * * @param mf the manifest to include in the Jar. * @param preserve if the generated Jar should be preserved. * @throws IOException if the new temporary Jar cannot be created. * @see Jar for an explanation of preservation. */ public JarBuilder(Manifest mf, boolean preserve) throws IOException { tempOut = File.createTempFile("jb-", ".jar"); if (!preserve) { tempOut.deleteOnExit(); } this.preserved = preserve; if (mf == null) { jout = new JarOutputStream(new FileOutputStream(tempOut)); } else { jout = new JarOutputStream(new FileOutputStream(tempOut), mf); } } /** * Adds a class with the given dot-format name and with data specified by an * input stream. * * @param elem the dot-format name of the class, such as * <code>java.lang.Object</code>. * @param is the InputStream that carries the class data for this class. * @throws IOException if the InputStream fails, or if the Jar output fails. */ public void addClass(String elem, InputStream is) throws IOException { addResource(elem.replace('.', '/') + ".class", is); } /** * Adds a resource with the given path and with data specified by an input * stream. * * @param name the path of the resource. * @param is the InputStream that carries the resource data for this * resource. * @throws IOException if the InputStream fails, or if the Jar output fails. */ public void addResource(String name, InputStream is) throws IOException { if (is == null) { throw new NullPointerException(); } try { jout.putNextEntry(new ZipEntry(name)); byte[] buffer = new byte[4096]; int n; while ((n = is.read(buffer)) > 0) { jout.write(buffer, 0, n); } jout.closeEntry(); } finally { is.close(); } } /** * Adds a resource with the given path and with data from a File. * * @param name the path of the resource. * @param file the File that carries the resource data for this resource. * @throws IOException if the file reading fails, or if the Jar output * fails. */ public void addResource(String name, File file) throws IOException { try (FileInputStream is = new FileInputStream(file)) { this.addResource(name, is); } } /** * Adds all of the classes and resources from <code>artifact</code>, and * optionally the manifest. * * @param artifact the artifact to read data from. * @param andManifest if the manifest should be taken from this artifact. * @throws IOException if the reading fails, or if the Jar output fails. */ public void addAll(Artifact artifact, boolean andManifest) throws IOException { for (String cn : artifact.listClassNames()) { addClass(cn, artifact.loadClassFile(cn)); } for (String cn : artifact.listResources()) { if (!andManifest && (MANIFEST.equals(cn) || (cn.startsWith("/") && MANIFEST.equals(cn.substring(1))))) { continue;// we don't want the manifest! } addResource(cn, artifact.loadResource(cn)); } } /** * Finalizes this Jar and converts it to a {@link Jar}. * * @return the built Jar. * @throws IOException if the Jar cannot be converted properly. */ public Jar build() throws IOException { jout.close(); return new Jar(tempOut, preserved); } }