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             System.out.println("Module name " + this.moduleName);
 106             this.dirPath = dirPath;
 107         }
 108 
 109         @Override
 110         public String moduleName() {
 111             return moduleName;
 112         }
 113 
 114         @Override
 115         public Stream<Entry> entries() {
 116             try {
 117                 return Files.walk(dirPath).map(this::toEntry).filter(n -> n != null);
 118             } catch(IOException ex) {
 119                 throw new RuntimeException(ex);
 120             }
 121         }
 122 
 123         private Archive.Entry toEntry(Path p) {
 124             if (Files.isDirectory(p)) {
 125                 return null;
 126             }
 127             String name = getPathName(p).substring(chop);
 128             if (name.startsWith("_")) {
 129                 return null;
 130             }
 131             if (verbose) {
 132                 String verboseName = moduleName + "/" + name;
 133                 log.println(verboseName);
 134             }
 135 
 136             return new FileEntry(p, name);
 137         }
 138 
 139         @Override
 140         public void close() throws IOException {
 141             IOException e = null;
 142             for (InputStream stream : open) {
 143                 try {
 144                     stream.close();
 145                 } catch (IOException ex) {
 146                     if (e == null) {
 147                         e = ex;
 148                     } else {
 149                         e.addSuppressed(ex);
 150                     }
 151                 }
 152             }
 153             if (e != null) {
 154                 throw e;
 155             }
 156         }
 157 
 158         @Override
 159         public void open() throws IOException {
 160             // NOOP
 161         }
 162     }
 163     private Map<String, Set<String>> modulePackages = new LinkedHashMap<>();
 164     private Set<Archive> archives = new HashSet<>();
 165     private final PrintWriter log;
 166     private final boolean verbose;
 167     private final String jdataName;
 168     ExtractedImage(Path dirPath, PrintWriter log,
 169             boolean verbose) throws IOException {
 170         if (!Files.isDirectory(dirPath)) {
 171             throw new IOException("Not a directory");
 172         }
 173         List<String> jdataNameHolder = new ArrayList<>();
 174         Files.walk(dirPath, 1).forEach((p) -> {
 175             try {
 176                 if (!dirPath.equals(p)) {
 177                     String name = getPathName(p);
 178                     if (name.endsWith(ImageModuleData.META_DATA_EXTENSION)) {
 179                         jdataNameHolder.add(p.getFileName().toString());
 180                         List<String> lines = Files.readAllLines(p);
 181                         for (Entry<String, List<String>> entry
 182                                 : ImageModuleDataWriter.toModulePackages(lines).entrySet()) {
 183                             Set<String> pkgs = new HashSet<>();
 184                             pkgs.addAll(entry.getValue());
 185                             modulePackages.put(entry.getKey(), pkgs);
 186                         }
 187                         modulePackages = Collections.unmodifiableMap(modulePackages);
 188                     } else {
 189                         if (Files.isDirectory(p)) {
 190                             Archive a = new DirArchive(p);
 191                             archives.add(a);
 192                         }
 193                     }
 194                 }
 195             } catch (IOException ex) {
 196                 throw new RuntimeException(ex);
 197             }
 198         });
 199         archives = Collections.unmodifiableSet(archives);
 200         this.log = log;
 201         this.verbose = verbose;
 202         if (jdataNameHolder.size() != 1) {
 203             throw new IOException("Wrong module information");
 204         }
 205         // The name of the metadata resource must be reused in the recreated jimage
 206         String name = jdataNameHolder.get(0);
 207         // Extension will be added when recreating the jimage
 208         if (name.endsWith(ImageModuleData.META_DATA_EXTENSION)) {
 209             name = name.substring(0, name.length()
 210                     - ImageModuleData.META_DATA_EXTENSION.length());
 211         }
 212         jdataName = name;
 213     }
 214 
 215     void recreateJImage(Path path) throws IOException {
 216 
 217         ImageFileCreator.recreateJimage(path, jdataName, archives, modulePackages);
 218     }
 219 
 220     private static String getPathName(Path path) {
 221         return path.toString().replace(File.separatorChar, '/');
 222     }
 223 }