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 import jdk.internal.jimage.Resource;
  30 
  31 import java.io.IOException;
  32 import java.io.InputStream;
  33 import java.io.OutputStream;
  34 import java.io.UncheckedIOException;
  35 import java.nio.file.Files;
  36 import java.nio.file.Path;
  37 import java.util.function.Consumer;
  38 
  39 /**
  40  * An Archive backed by an exploded representation on disk.
  41  */
  42 public class ModuleArchive implements Archive {
  43     private final Path classes;
  44     private final Path cmds;
  45     private final Path libs;
  46     private final Path configs;
  47     private final String moduleName;
  48 
  49     public ModuleArchive(String moduleName, Path classes, Path cmds,
  50                          Path libs, Path configs) {
  51         this.moduleName = moduleName;
  52         this.classes = classes;
  53         this.cmds = cmds;
  54         this.libs = libs;
  55         this.configs = configs;
  56     }
  57 
  58     @Override
  59     public String moduleName() {
  60         return moduleName;
  61     }
  62 
  63     @Override
  64     public void visitResources(Consumer<Resource> consumer) {
  65         if (classes == null)
  66             return;
  67         try{
  68             Files.walk(classes)
  69                     .sorted()
  70                     .filter(p -> !Files.isDirectory(p)
  71                             && !classes.relativize(p).toString().startsWith("_the.")
  72                             && !classes.relativize(p).toString().equals("javac_state"))
  73                     .map(this::toResource)
  74                     .forEach(consumer::accept);
  75         } catch (IOException ioe) {
  76             throw new UncheckedIOException(ioe);
  77         }
  78     }
  79 
  80     private Resource toResource(Path path) {
  81         try {
  82             return new Resource(classes.relativize(path).toString().replace('\\','/'),
  83                                 Files.size(path),
  84                                 0 /* no compression support yet */);
  85         } catch (IOException ioe) {
  86             throw new UncheckedIOException(ioe);
  87         }
  88     }
  89 
  90     private enum Section {
  91         CLASSES,
  92         CMDS,
  93         LIBS,
  94         CONFIGS
  95     }
  96 
  97     @Override
  98     public void visitEntries(Consumer<Entry> consumer) {
  99         try{
 100             if (classes != null)
 101                 Files.walk(classes)
 102                         .sorted()
 103                         .filter(p -> !Files.isDirectory(p)
 104                                 && !classes.relativize(p).toString().startsWith("_the.")
 105                                 && !classes.relativize(p).toString().equals("javac_state"))
 106                         .map(p -> toEntry(p, classes, Section.CLASSES))
 107                         .forEach(consumer::accept);
 108             if (cmds != null)
 109                 Files.walk(cmds)
 110                         .filter(p -> !Files.isDirectory(p))
 111                         .map(p -> toEntry(p, cmds, Section.CMDS))
 112                         .forEach(consumer::accept);
 113             if (libs != null)
 114                 Files.walk(libs)
 115                         .filter(p -> !Files.isDirectory(p))
 116                         .map(p -> toEntry(p, libs, Section.LIBS))
 117                         .forEach(consumer::accept);
 118             if (configs != null)
 119                 Files.walk(configs)
 120                         .filter(p -> !Files.isDirectory(p))
 121                         .map(p -> toEntry(p, configs, Section.CONFIGS))
 122                         .forEach(consumer::accept);
 123         } catch (IOException ioe) {
 124             throw new UncheckedIOException(ioe);
 125         }
 126     }
 127 
 128     private static class FileEntry implements Entry {
 129         private final String name;
 130         private final InputStream is;
 131         private final boolean isDirectory;
 132         private final Section section;
 133         FileEntry(String name, InputStream is,
 134                   boolean isDirectory, Section section) {
 135             this.name = name;
 136             this.is = is;
 137             this.isDirectory = isDirectory;
 138             this.section = section;
 139         }
 140         public String getName() {
 141             return name;
 142         }
 143         public Section getSection() {
 144             return section;
 145         }
 146         public InputStream getInputStream() {
 147             return is;
 148         }
 149         public boolean isDirectory() {
 150             return isDirectory;
 151         }
 152     }
 153 
 154     private Entry toEntry(Path entryPath, Path basePath, Section section) {
 155         try {
 156             return new FileEntry(basePath.relativize(entryPath).toString().replace('\\', '/'),
 157                                  Files.newInputStream(entryPath), false,
 158                                  section);
 159         } catch (IOException e) {
 160             throw new UncheckedIOException(e);
 161         }
 162     }
 163 
 164     @Override
 165     public Consumer<Entry> defaultImageWriter(Path path, OutputStream out) {
 166         return new DefaultEntryWriter(path, out);
 167     }
 168 
 169     private static class DefaultEntryWriter implements Consumer<Archive.Entry> {
 170         private final Path root;
 171         private final OutputStream out;
 172 
 173         DefaultEntryWriter(Path root, OutputStream out) {
 174             this.root = root;
 175             this.out = out;
 176         }
 177 
 178         @Override
 179         public void accept(Archive.Entry entry) {
 180             try {
 181                 FileEntry e = (FileEntry)entry;
 182                 Section section = e.getSection();
 183                 String filename = e.getName();
 184 
 185                 try (InputStream in = entry.getInputStream()) {
 186                     switch (section) {
 187                         case CLASSES:
 188                             if (!filename.startsWith("_the.") && !filename.equals("javac_state"))
 189                                 writeEntry(in);
 190                             break;
 191                         case LIBS:
 192                             writeEntry(in, destFile(nativeDir(filename), filename));
 193                             break;
 194                         case CMDS:
 195                             Path path = destFile("bin", filename);
 196                             writeEntry(in, path);
 197                             path.toFile().setExecutable(true, false);
 198                             break;
 199                         case CONFIGS:
 200                             writeEntry(in, destFile("conf", filename));
 201                             break;
 202                         default:
 203                             throw new InternalError("unexpected entry: " + filename);
 204                     }
 205                 }
 206             } catch (IOException x) {
 207                 throw new UncheckedIOException(x);
 208             }
 209         }
 210 
 211         private Path destFile(String dir, String filename) {
 212             return root.resolve(dir).resolve(filename);
 213         }
 214 
 215         private static void writeEntry(InputStream in, Path dstFile) throws IOException {
 216             if (Files.notExists(dstFile.getParent()))
 217                 Files.createDirectories(dstFile.getParent());
 218             Files.copy(in, dstFile);
 219         }
 220 
 221         private void writeEntry(InputStream in) throws IOException {
 222             byte[] buf = new byte[8192];
 223             int n;
 224             while ((n = in.read(buf)) > 0)
 225                 out.write(buf, 0, n);
 226         }
 227 
 228         private static String nativeDir(String filename) {
 229             if (System.getProperty("os.name").startsWith("Windows")) {
 230                 if (filename.endsWith(".dll"))
 231                     return "bin";
 232                  else
 233                     return "lib";
 234             } else {
 235                 return "lib";
 236             }
 237         }
 238     }
 239 }
 240