1 /*
   2  * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 package jdk.test.lib.util;
  25 
  26 import java.io.FileInputStream;
  27 import java.io.FileNotFoundException;
  28 import java.io.FileOutputStream;
  29 import java.io.IOException;
  30 import java.nio.file.Files;
  31 import java.nio.file.InvalidPathException;
  32 import java.nio.file.Path;
  33 import java.nio.file.Paths;
  34 import java.util.Enumeration;
  35 import java.util.HashMap;
  36 import java.util.Map;
  37 import java.util.jar.JarEntry;
  38 import java.util.jar.JarFile;
  39 import java.util.jar.JarOutputStream;
  40 import java.util.jar.Manifest;
  41 
  42 /**
  43  * Common library for various test jar file utility functions.
  44  */
  45 public final class JarUtils {
  46 
  47     /**
  48      * Create jar file with specified files. If a specified file does not exist,
  49      * a new jar entry will be created with the file name itself as the content.
  50      */
  51     public static void createJar(String dest, String... files)
  52             throws IOException {
  53         try (JarOutputStream jos = new JarOutputStream(
  54                 new FileOutputStream(dest), new Manifest())) {
  55             for (String file : files) {
  56                 System.out.println(String.format("Adding %s to %s",
  57                         file, dest));
  58 
  59                 // add an archive entry, and write a file
  60                 jos.putNextEntry(new JarEntry(file));
  61                 try (FileInputStream fis = new FileInputStream(file)) {
  62                     fis.transferTo(jos);
  63                 } catch (FileNotFoundException e) {
  64                     jos.write(file.getBytes());
  65                 }
  66             }
  67         }
  68         System.out.println();
  69     }
  70 
  71     /**
  72      * Add or remove specified files to existing jar file. If a specified file
  73      * to be updated or added does not exist, the jar entry will be created
  74      * with the file name itself as the content.
  75      *
  76      * @param src the original jar file name
  77      * @param dest the new jar file name
  78      * @param files the files to update. The list is broken into 2 groups
  79      *              by a "-" string. The files before in the 1st group will
  80      *              be either updated or added. The files in the 2nd group
  81      *              will be removed. If no "-" exists, all files belong to
  82      *              the 1st group.
  83      */
  84     public static void updateJar(String src, String dest, String... files)
  85             throws IOException {
  86         Map<String,Object> changes = new HashMap<>();
  87         boolean update = true;
  88         for (String file : files) {
  89             if (file.equals("-")) {
  90                 update = false;
  91             } else if (update) {
  92                 try {
  93                     Path p = Paths.get(file);
  94                     if (Files.exists(p)) {
  95                         changes.put(file, p);
  96                     } else {
  97                         changes.put(file, file);
  98                     }
  99                 } catch (InvalidPathException e) {
 100                     // Fallback if file not a valid Path.
 101                     changes.put(file, file);
 102                 }
 103             } else {
 104                 changes.put(file, Boolean.FALSE);
 105             }
 106         }
 107         updateJar(src, dest, changes);
 108     }
 109 
 110     /**
 111      * Update content of a jar file.
 112      *
 113      * @param src the original jar file name
 114      * @param dest the new jar file name
 115      * @param changes a map of changes, key is jar entry name, value is content.
 116      *                Value can be Path, byte[] or String. If key exists in
 117      *                src but value is Boolean FALSE. The entry is removed.
 118      *                Existing entries in src not a key is unmodified.
 119      * @throws IOException
 120      */
 121     public static void updateJar(String src, String dest,
 122                                  Map<String,Object> changes)
 123             throws IOException {
 124 
 125         // What if input changes is immutable?
 126         changes = new HashMap<>(changes);
 127 
 128         System.out.printf("Creating %s from %s...\n", dest, src);
 129         try (JarOutputStream jos = new JarOutputStream(
 130                 new FileOutputStream(dest))) {
 131 
 132             try (JarFile srcJarFile = new JarFile(src)) {
 133                 Enumeration<JarEntry> entries = srcJarFile.entries();
 134                 while (entries.hasMoreElements()) {
 135                     JarEntry entry = entries.nextElement();
 136                     String name = entry.getName();
 137                     if (changes.containsKey(name)) {
 138                         System.out.println(String.format("- Update %s", name));
 139                         updateEntry(jos, name, changes.get(name));
 140                         changes.remove(name);
 141                     } else {
 142                         System.out.println(String.format("- Copy %s", name));
 143                         jos.putNextEntry(entry);
 144                         srcJarFile.getInputStream(entry).transferTo(jos);
 145                     }
 146                 }
 147             }
 148             for (Map.Entry<String, Object> e : changes.entrySet()) {
 149                 System.out.println(String.format("- Add %s", e.getKey()));
 150                 updateEntry(jos, e.getKey(), e.getValue());
 151             }
 152         }
 153         System.out.println();
 154     }
 155 
 156     private static void updateEntry(JarOutputStream jos, String name, Object content)
 157            throws IOException {
 158         if (content instanceof Boolean) {
 159             if (((Boolean) content).booleanValue()) {
 160                 throw new RuntimeException("Boolean value must be FALSE");
 161             }
 162         } else {
 163             jos.putNextEntry(new JarEntry(name));
 164             if (content instanceof Path) {
 165                 Files.newInputStream((Path) content).transferTo(jos);
 166             } else if (content instanceof byte[]) {
 167                 jos.write((byte[]) content);
 168             } else if (content instanceof String) {
 169                 jos.write(((String) content).getBytes());
 170             } else {
 171                 throw new RuntimeException("Unknown type " + content.getClass());
 172             }
 173         }
 174     }
 175 }