1 /*
   2  * Copyright (c) 2015, 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.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package jdk.tools.jimage;
  26 
  27 import java.io.File;
  28 import java.io.IOException;
  29 import java.io.InputStream;
  30 import java.io.PrintWriter;
  31 import java.nio.file.Files;
  32 import java.nio.file.Path;
  33 import java.util.ArrayList;
  34 import java.util.Collections;
  35 import java.util.HashSet;
  36 import java.util.LinkedHashMap;
  37 import java.util.List;
  38 import java.util.Map;
  39 import java.util.Map.Entry;
  40 import java.util.Set;
  41 import java.util.function.Consumer;
  42 import java.util.stream.Stream;
  43 import jdk.internal.jimage.Archive;
  44 import jdk.internal.jimage.ImageFileCreator;
  45 import jdk.internal.jimage.ImageModuleData;
  46 import jdk.internal.jimage.ImageModuleDataWriter;
  47 
  48 /**
  49  *
  50  * Support for extracted image.
  51  */
  52 public final class ExtractedImage {
  53 
  54     /**
  55      * An Archive backed by a directory.
  56      */
  57     public class DirArchive implements Archive {
  58 
  59         /**
  60          * A File located in a Directory.
  61          */
  62         private class FileEntry extends Archive.Entry {
  63 
  64             private final long size;
  65             private final Path path;
  66 
  67             FileEntry(Path path, String name) {
  68                 super(DirArchive.this, getPathName(path), name,
  69                         Archive.Entry.EntryType.CLASS_OR_RESOURCE);
  70                 this.path = path;
  71                 try {
  72                     size = Files.size(path);
  73                 } catch (IOException ex) {
  74                     throw new RuntimeException(ex);
  75                 }
  76             }
  77 
  78             /**
  79              * Returns the number of bytes of this file.
  80              */
  81             @Override
  82             public long size() {
  83                 return size;
  84             }
  85 
  86             @Override
  87             public InputStream stream() throws IOException {
  88                 InputStream stream = Files.newInputStream(path);
  89                 open.add(stream);
  90                 return stream;
  91             }
  92         }
  93 
  94         private final Path dirPath;
  95         private final String moduleName;
  96         private final List<InputStream> open = new ArrayList<>();
  97         private final int chop;
  98 
  99         protected DirArchive(Path dirPath) throws IOException {
 100             if (!Files.isDirectory(dirPath)) {
 101                 throw new IOException("Not a directory");
 102             }
 103             chop = dirPath.toString().length() + 1;
 104             this.moduleName = dirPath.getFileName().toString();
 105             this.dirPath = dirPath;
 106         }
 107 
 108         @Override
 109         public String moduleName() {
 110             return moduleName;
 111         }
 112 
 113         @Override
 114         public Stream<Entry> entries() {
 115             try {
 116                 return Files.walk(dirPath).map(this::toEntry).filter(n -> n != null);
 117             } catch(IOException ex) {
 118                 throw new RuntimeException(ex);
 119             }
 120         }
 121 
 122         private Archive.Entry toEntry(Path p) {
 123             if (Files.isDirectory(p)) {
 124                 return null;
 125             }
 126             String name = getPathName(p).substring(chop);
 127             if (name.startsWith("_")) {
 128                 return null;
 129             }
 130             if (verbose) {
 131                 String verboseName = moduleName + "/" + name;
 132                 log.println(verboseName);
 133             }
 134 
 135             return new FileEntry(p, name);
 136         }
 137 
 138         @Override
 139         public void close() throws IOException {
 140             IOException e = null;
 141             for (InputStream stream : open) {
 142                 try {
 143                     stream.close();
 144                 } catch (IOException ex) {
 145                     if (e == null) {
 146                         e = ex;
 147                     } else {
 148                         e.addSuppressed(ex);
 149                     }
 150                 }
 151             }
 152             if (e != null) {
 153                 throw e;
 154             }
 155         }
 156 
 157         @Override
 158         public void open() throws IOException {
 159             // NOOP
 160         }
 161     }
 162     private Map<String, Set<String>> modulePackages = new LinkedHashMap<>();
 163     private Set<Archive> archives = new HashSet<>();
 164     private final PrintWriter log;
 165     private final boolean verbose;
 166     private final String jdataName;
 167     ExtractedImage(Path dirPath, PrintWriter log,
 168             boolean verbose) throws IOException {
 169         if (!Files.isDirectory(dirPath)) {
 170             throw new IOException("Not a directory");
 171         }
 172         List<String> jdataNameHolder = new ArrayList<>();
 173         Files.walk(dirPath, 1).forEach((p) -> {
 174             try {
 175                 if (!dirPath.equals(p)) {
 176                     String name = getPathName(p);
 177                     if (name.endsWith(ImageModuleData.META_DATA_EXTENSION)) {
 178                         jdataNameHolder.add(p.getFileName().toString());
 179                         List<String> lines = Files.readAllLines(p);
 180                         for (Entry<String, List<String>> entry
 181                                 : ImageModuleDataWriter.toModulePackages(lines).entrySet()) {
 182                             Set<String> pkgs = new HashSet<>();
 183                             pkgs.addAll(entry.getValue());
 184                             modulePackages.put(entry.getKey(), pkgs);
 185                         }
 186                         modulePackages = Collections.unmodifiableMap(modulePackages);
 187                     } else {
 188                         if (Files.isDirectory(p)) {
 189                             Archive a = new DirArchive(p);
 190                             archives.add(a);
 191                         }
 192                     }
 193                 }
 194             } catch (IOException ex) {
 195                 throw new RuntimeException(ex);
 196             }
 197         });
 198         archives = Collections.unmodifiableSet(archives);
 199         this.log = log;
 200         this.verbose = verbose;
 201         if (jdataNameHolder.size() != 1) {
 202             throw new IOException("Wrong module information");
 203         }
 204         // The name of the metadata resource must be reused in the recreated jimage
 205         String name = jdataNameHolder.get(0);
 206         // Extension will be added when recreating the jimage
 207         if (name.endsWith(ImageModuleData.META_DATA_EXTENSION)) {
 208             name = name.substring(0, name.length()
 209                     - ImageModuleData.META_DATA_EXTENSION.length());
 210         }
 211         jdataName = name;
 212     }
 213 
 214     void recreateJImage(Path path) throws IOException {
 215 
 216         ImageFileCreator.recreateJimage(path, jdataName, archives, modulePackages);
 217     }
 218 
 219     private static String getPathName(Path path) {
 220         return path.toString().replace(File.separatorChar, '/');
 221     }
 222 }