1 /*
   2  * Copyright (c) 2014, 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 
  26 package build.tools.module;
  27 
  28 import jdk.internal.jimage.Archive;
  29 
  30 import java.io.IOException;
  31 import java.io.InputStream;
  32 import java.io.UncheckedIOException;
  33 import java.nio.file.Files;
  34 import java.nio.file.Path;
  35 import java.util.ArrayList;
  36 import java.util.List;
  37 import java.util.stream.Collectors;
  38 import java.util.stream.Stream;
  39 import jdk.internal.jimage.Archive.Entry.EntryType;
  40 
  41 /**
  42  * An Archive backed by an exploded representation on disk.
  43  */
  44 public class ModuleArchive implements Archive {
  45     private final Path classes;
  46     private final Path cmds;
  47     private final Path libs;
  48     private final Path configs;
  49     private final String moduleName;
  50 
  51     private final List<InputStream> opened = new ArrayList<>();
  52 
  53     public ModuleArchive(String moduleName, Path classes, Path cmds,
  54                          Path libs, Path configs) {
  55         this.moduleName = moduleName;
  56         this.classes = classes;
  57         this.cmds = cmds;
  58         this.libs = libs;
  59         this.configs = configs;
  60     }
  61 
  62     @Override
  63     public String moduleName() {
  64         return moduleName;
  65     }
  66 
  67     @Override
  68     public void open() throws IOException {
  69         // NOOP
  70     }
  71 
  72     @Override
  73     public void close() throws IOException {
  74         IOException e = null;
  75         for (InputStream stream : opened) {
  76             try {
  77                 stream.close();
  78             } catch (IOException ex) {
  79                 if (e == null) {
  80                     e = ex;
  81                 } else {
  82                     e.addSuppressed(ex);
  83                 }
  84             }
  85         }
  86         if (e != null) {
  87             throw e;
  88         }
  89     }
  90 
  91     @Override
  92     public Stream<Entry> entries() {
  93         List<Entry> entries = new ArrayList<>();
  94         try {
  95             /*
  96              * This code should be revisited to avoid buffering of the entries.
  97              * 1) Do we really need sorting classes? This force buffering of entries. 
  98              *    libs, cmds and configs are not sorted.
  99              * 2) I/O streams should be concatenated instead of buffering into 
 100              *    entries list.
 101              * 3) Close I/O streams in a close handler.
 102              */
 103             if (classes != null) {
 104                 try (Stream<Path> stream = Files.walk(classes)) {
 105                     entries.addAll(stream
 106                             .filter(p -> !Files.isDirectory(p)
 107                                     && !classes.relativize(p).toString().startsWith("_the.")
 108                                     && !classes.relativize(p).toString().equals("javac_state"))
 109                             .sorted()
 110                             .map(p -> toEntry(p, classes, EntryType.CLASS_OR_RESOURCE))
 111                             .collect(Collectors.toList()));
 112                 }
 113             }
 114             if (cmds != null) {
 115                 try (Stream<Path> stream = Files.walk(cmds)) {
 116                     entries.addAll(stream
 117                             .filter(p -> !Files.isDirectory(p))
 118                             .map(p -> toEntry(p, cmds, EntryType.NATIVE_CMD))
 119                             .collect(Collectors.toList()));
 120                 }
 121             }
 122             if (libs != null) {
 123                 try (Stream<Path> stream = Files.walk(libs)) {
 124                     entries.addAll(stream
 125                             .filter(p -> !Files.isDirectory(p))
 126                             .map(p -> toEntry(p, libs, EntryType.NATIVE_LIB))
 127                             .collect(Collectors.toList()));
 128                 }
 129             }
 130             if (configs != null) {
 131                 try (Stream<Path> stream = Files.walk(configs)) {
 132                 entries.addAll(stream
 133                         .filter(p -> !Files.isDirectory(p))
 134                         .map(p -> toEntry(p, configs, EntryType.CONFIG))
 135                         .collect(Collectors.toList()));
 136                 }
 137             }
 138         } catch (IOException ioe) {
 139             throw new UncheckedIOException(ioe);
 140         }
 141         return entries.stream();
 142     }
 143 
 144     private class FileEntry extends Entry {
 145         private final boolean isDirectory;
 146         private final long size;
 147         private final Path entryPath;
 148         FileEntry(Path entryPath, String path, EntryType type,
 149                   boolean isDirectory, long size) {
 150             super(ModuleArchive.this, path, path, type);
 151             this.entryPath = entryPath;
 152             this.isDirectory = isDirectory;
 153             this.size = size;
 154         }
 155 
 156         public boolean isDirectory() {
 157             return isDirectory;
 158         }
 159 
 160         @Override
 161         public long size() {
 162             return size;
 163         }
 164 
 165         @Override
 166         public InputStream stream() throws IOException {
 167             InputStream stream = Files.newInputStream(entryPath);
 168             opened.add(stream);
 169             return stream;
 170         }
 171     }
 172 
 173     private Entry toEntry(Path entryPath, Path basePath, EntryType section) {
 174         try {
 175             String path = basePath.relativize(entryPath).toString().replace('\\', '/');
 176             return new FileEntry(entryPath, path, section,
 177                     false, Files.size(entryPath));
 178         } catch (IOException e) {
 179             throw new UncheckedIOException(e);
 180         }
 181     }
 182 }
 183