1 /*
   2  * Copyright (c) 2009, 2011, 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 org.openjdk.jigsaw;
  27 
  28 import java.lang.module.*;
  29 import java.io.*;
  30 import java.net.URI;
  31 import java.nio.channels.FileChannel;
  32 import java.nio.file.*;
  33 import java.nio.file.attribute.BasicFileAttributes;
  34 import java.security.*;
  35 import java.security.cert.*;
  36 import java.util.*;
  37 import java.util.jar.*;
  38 import java.util.zip.*;
  39 
  40 import static java.nio.file.StandardCopyOption.*;
  41 import static java.nio.file.StandardOpenOption.*;
  42 
  43 /**
  44  * A simple module library which stores data directly in the filesystem
  45  *
  46  * @see Library
  47  */
  48 
  49 // ## TODO: Move remaining parent-searching logic upward into Library class
  50 
  51 // On-disk library layout
  52 //
  53 //   $LIB/%jigsaw-library
  54 //        com.foo.bar/1.2.3/info (= module-info.class)
  55 //                          index (list of defined classes)
  56 //                          config (resolved configuration, if a root)
  57 //                          classes/com/foo/bar/...
  58 //                          resources/com/foo/bar/...
  59 //                          lib/libbar.so
  60 //                          bin/bar
  61 //                          signer (signer's certchain & timestamp)
  62 //
  63 // ## Issue: Concurrent access to the module library
  64 // ## e.g. a module is being removed while a running application
  65 // ## is depending on it
  66 
  67 public final class SimpleLibrary
  68     extends Library
  69 {
  70 
  71     private static abstract class MetaData {
  72 
  73         protected final int maxMajorVersion;
  74         protected final int maxMinorVersion;
  75         protected int majorVersion;
  76         protected int minorVersion;
  77         private final FileConstants.Type type;
  78         private final File file;
  79 
  80         protected MetaData(int maxMajor, int maxMinor,
  81                            FileConstants.Type t, File f)
  82         {
  83             maxMajorVersion = majorVersion = maxMajor;
  84             maxMinorVersion = minorVersion = maxMinor;
  85             type = t;
  86             file = f;
  87         }
  88 
  89         protected abstract void storeRest(DataOutputStream out)
  90             throws IOException;
  91 
  92         void store() throws IOException {
  93             try (OutputStream fos = new FileOutputStream(file);
  94                  BufferedOutputStream bos = new BufferedOutputStream(fos);
  95                  DataOutputStream out = new DataOutputStream(bos)) {
  96                 out.writeInt(FileConstants.MAGIC);
  97                 out.writeShort(type.value());
  98                 out.writeShort(majorVersion);
  99                 out.writeShort(minorVersion);
 100                 storeRest(out);
 101             }
 102         }
 103 
 104         protected abstract void loadRest(DataInputStream in)
 105             throws IOException;
 106 
 107         protected void load() throws IOException {
 108             try (InputStream fis = new FileInputStream(file);
 109                  BufferedInputStream bis = new BufferedInputStream(fis);
 110                  DataInputStream in = new DataInputStream(bis)) {
 111                 if (in.readInt() != FileConstants.MAGIC)
 112                     throw new IOException(file + ": Invalid magic number");
 113                 if (in.readShort() != type.value())
 114                     throw new IOException(file + ": Invalid file type");
 115                 int maj = in.readShort();
 116                 int min = in.readShort();
 117                 if (   maj > maxMajorVersion
 118                     || (maj == maxMajorVersion && min > maxMinorVersion)) {
 119                     throw new IOException(file
 120                                           + ": Futuristic version number");
 121                 }
 122                 majorVersion = maj;
 123                 minorVersion = min;
 124                 loadRest(in);
 125             } catch (EOFException x) {
 126                 throw new IOException(file + ": Invalid library metadata", x);
 127             }
 128         }
 129     }
 130 
 131     /**
 132      * Defines the storage options that SimpleLibrary supports.
 133      */
 134     public static enum StorageOption {
 135         DEFLATED,
 136     }
 137 
 138     private static final class Header
 139         extends MetaData
 140     {
 141         private static final String FILE
 142             = FileConstants.META_PREFIX + "jigsaw-library";
 143 
 144         private static final int MAJOR_VERSION = 0;
 145         private static final int MINOR_VERSION = 1;
 146 
 147         private static final int DEFLATED = 1 << 0;
 148 
 149         private File parent;
 150         // location of native libs for this library (may be outside the library)
 151         // null:default, to use a per-module 'lib' directory
 152         private File natlibs;
 153         // location of native cmds for this library (may be outside the library)
 154         // null:default, to use a per-module 'bin' directory
 155         private File natcmds;
 156         // location of config files for this library (may be outside the library)
 157         // null:default, to use a per-module 'etc' directory
 158         private File configs;
 159         private Set<StorageOption> opts;
 160 
 161         public File parent()  { return parent;  }
 162         public File natlibs() { return natlibs; }
 163         public File natcmds() { return natcmds; }
 164         public File configs() { return configs; }
 165         public boolean isDeflated() {
 166             return opts.contains(StorageOption.DEFLATED);
 167         }
 168 
 169         private Header(File root) {
 170             super(MAJOR_VERSION, MINOR_VERSION,
 171                   FileConstants.Type.LIBRARY_HEADER,
 172                   new File(root, FILE));
 173         }
 174 
 175         private Header(File root, File parent, File natlibs, File natcmds,
 176                        File configs, Set<StorageOption> opts) {
 177             this(root);
 178             this.parent = parent;
 179             this.natlibs = natlibs;
 180             this.natcmds = natcmds;
 181             this.configs = configs;
 182             this.opts = new HashSet<>(opts);
 183         }
 184 
 185         private void storePath(File p, DataOutputStream out) throws IOException {
 186             if (p != null) {
 187                 out.writeByte(1);
 188                 out.writeUTF(Files.convertSeparator(p.toString()));
 189             } else {
 190                 out.write(0);
 191             }
 192         }
 193 
 194         protected void storeRest(DataOutputStream out) throws IOException {
 195             int flags = 0;
 196             if (isDeflated())
 197                 flags |= DEFLATED;
 198             out.writeShort(flags);
 199 
 200             storePath(parent, out);
 201             storePath(natlibs, out);
 202             storePath(natcmds, out);
 203             storePath(configs, out);
 204         }
 205 
 206         private File loadPath(DataInputStream in) throws IOException {
 207             if (in.readByte() != 0)
 208                 return new File(Files.platformSeparator(in.readUTF()));
 209             return null;
 210         }
 211 
 212         protected void loadRest(DataInputStream in) throws IOException {
 213             opts = new HashSet<StorageOption>();
 214             int flags = in.readShort();
 215             if ((flags & DEFLATED) == DEFLATED)
 216                 opts.add(StorageOption.DEFLATED);
 217             parent = loadPath(in);
 218             natlibs = loadPath(in);
 219             natcmds = loadPath(in);
 220             configs = loadPath(in);
 221         }
 222 
 223         private static Header load(File f) throws IOException {
 224             Header h = new Header(f);
 225             h.load();
 226             return h;
 227         }
 228     }
 229 
 230     private final File root;
 231     private final File canonicalRoot;
 232     private final File parentPath;
 233     private final File natlibs;
 234     private final File natcmds;
 235     private final File configs;
 236     private final SimpleLibrary parent;
 237     private final Header hd;
 238     private final ModuleDictionary moduleDictionary;
 239     private final File lockf;

 240 
 241     public String name() { return root.toString(); }
 242     public File root() { return canonicalRoot; }
 243     public int majorVersion() { return hd.majorVersion; }
 244     public int minorVersion() { return hd.minorVersion; }
 245     public SimpleLibrary parent() { return parent; }
 246     public File natlibs() { return natlibs; }
 247     public File natcmds() { return natcmds; }
 248     public File configs() { return configs; }
 249     public boolean isDeflated() { return hd.isDeflated(); }
 250 
 251     private URI location = null;
 252     public URI location() {
 253         if (location == null)
 254             location = root().toURI();
 255         return location;
 256     }
 257 
 258     @Override
 259     public String toString() {
 260         return (this.getClass().getName()
 261                 + "[" + canonicalRoot
 262                 + ", v" + hd.majorVersion + "." + hd.minorVersion + "]");
 263     }
 264 
 265     private static File resolveAndEnsurePath(File path) throws IOException {
 266         if (path == null) { return null; }
 267 
 268         File p = path.getCanonicalFile();
 269         if (!p.exists()) {
 270             Files.mkdirs(p, p.toString());
 271         } else {
 272             Files.ensureIsDirectory(p);
 273             Files.ensureWriteable(p);
 274         }
 275         return p;
 276     }
 277 
 278     private File relativize(File path) throws IOException {
 279         if (path == null) { return null; }
 280         // Return the path relative to the canonical root
 281         return (canonicalRoot.toPath().relativize(path.toPath().toRealPath())).toFile();
 282     }
 283 
 284     // Opens an existing library
 285     private SimpleLibrary(File path) throws IOException {
 286         root = path;
 287         canonicalRoot = root.getCanonicalFile();
 288         Files.ensureIsDirectory(root);
 289         hd = Header.load(root);
 290 
 291         parentPath = hd.parent();
 292         parent = parentPath != null ? open(parentPath) : null;
 293 
 294         natlibs = hd.natlibs() == null ? null :
 295             new File(canonicalRoot, hd.natlibs().toString()).getCanonicalFile();
 296         natcmds = hd.natcmds() == null ? null :
 297             new File(canonicalRoot, hd.natcmds().toString()).getCanonicalFile();
 298         configs = hd.configs() == null ? null :
 299             new File(canonicalRoot, hd.configs().toString()).getCanonicalFile();
 300 
 301         lockf = new File(root, FileConstants.META_PREFIX + "lock");

 302         moduleDictionary = new ModuleDictionary(root);
 303     }
 304 
 305     // Creates a new library
 306     private SimpleLibrary(File path, File parentPath, File natlibs, File natcmds,
 307                           File configs, Set<StorageOption> opts)
 308         throws IOException
 309     {
 310         root = path;
 311         canonicalRoot = root.getCanonicalFile();
 312         if (root.exists()) {
 313             Files.ensureIsDirectory(root);
 314             if (root.list().length != 0)
 315                 throw new IOException(root + ": Already Exists");
 316             Files.ensureWriteable(root);
 317         } else
 318             Files.mkdirs(root, root.toString());
 319 
 320         this.parent = parentPath != null ? open(parentPath) : null;
 321         this.parentPath = parentPath != null ? this.parent.root() : null;
 322 
 323         this.natlibs = resolveAndEnsurePath(natlibs);
 324         this.natcmds = resolveAndEnsurePath(natcmds);
 325         this.configs = resolveAndEnsurePath(configs);
 326 
 327         hd = new Header(canonicalRoot, this.parentPath, relativize(this.natlibs),
 328                         relativize(this.natcmds), relativize(this.configs), opts);
 329         hd.store();
 330 
 331         lockf = new File(root, FileConstants.META_PREFIX + "lock");
 332         lockf.createNewFile();


 333         moduleDictionary = new ModuleDictionary(canonicalRoot);
 334         moduleDictionary.store();
 335     }
 336 
 337     public static SimpleLibrary create(File path, File parent, File natlibs,
 338                                        File natcmds, File configs,
 339                                        Set<StorageOption> opts)
 340         throws IOException
 341     {
 342         return new SimpleLibrary(path, parent, natlibs, natcmds, configs, opts);
 343     }
 344 
 345     public static SimpleLibrary create(File path, File parent, Set<StorageOption> opts)
 346         throws IOException
 347     {
 348         return new SimpleLibrary(path, parent, null, null, null, opts);
 349     }
 350 
 351     public static SimpleLibrary create(File path, File parent)
 352         throws IOException
 353     {
 354         return SimpleLibrary.create(path, parent, Collections.<StorageOption>emptySet());
 355     }
 356 
 357     public static SimpleLibrary create(File path, Set<StorageOption> opts)
 358         throws IOException
 359     {
 360         // ## Should default parent to $JAVA_HOME/lib/modules
 361         return SimpleLibrary.create(path, null, opts);
 362     }
 363 
 364     public static SimpleLibrary open(File path)
 365         throws IOException
 366     {
 367         return new SimpleLibrary(path);
 368     }
 369 
 370     private static final JigsawModuleSystem jms
 371         = JigsawModuleSystem.instance();
 372 
 373     private static final class Index
 374         extends MetaData
 375     {
 376 
 377         private static String FILE = "index";
 378 
 379         private static int MAJOR_VERSION = 0;
 380         private static int MINOR_VERSION = 1;
 381 
 382         private Set<String> publicClasses;
 383         public Set<String> publicClasses() { return publicClasses; }
 384 
 385         private Set<String> otherClasses;
 386         public Set<String> otherClasses() { return otherClasses; }
 387 
 388         private Index(File root) {
 389             super(MAJOR_VERSION, MINOR_VERSION,
 390                   FileConstants.Type.LIBRARY_MODULE_INDEX,
 391                   new File(root, FILE));
 392             // Unsorted on input, because we don't need it sorted
 393             publicClasses = new HashSet<String>();
 394             otherClasses = new HashSet<String>();
 395         }
 396 
 397         private void storeSet(Set<String> cnset, DataOutputStream out)
 398             throws IOException
 399         {
 400             // Sorted on output, because we can afford it
 401             List<String> cns = new ArrayList<String>(cnset);
 402             Collections.sort(cns);
 403             out.writeInt(cns.size());
 404             for (String cn : cns)
 405                 out.writeUTF(cn);
 406         }
 407 
 408         protected void storeRest(DataOutputStream out)
 409             throws IOException
 410         {
 411             storeSet(publicClasses, out);
 412             storeSet(otherClasses, out);
 413         }
 414 
 415         private void loadSet(DataInputStream in, Set<String> cnset)
 416             throws IOException
 417         {
 418             int n = in.readInt();
 419             for (int i = 0; i < n; i++)
 420                 cnset.add(in.readUTF());
 421         }
 422 
 423         protected void loadRest(DataInputStream in)
 424             throws IOException
 425         {
 426             loadSet(in, publicClasses);
 427             loadSet(in, otherClasses);
 428         }
 429 
 430         private static Index load(File f)
 431             throws IOException
 432         {
 433             Index ix = new Index(f);
 434             ix.load();
 435             return ix;
 436         }
 437 
 438     }
 439 
 440     private static final class StoredConfiguration
 441         extends MetaData
 442     {
 443 
 444         private static String FILE = "config";
 445 
 446         private static int MAJOR_VERSION = 0;
 447         private static int MINOR_VERSION = 1;
 448 
 449         private Configuration<Context> cf;
 450 
 451         private static void delete(File root) {
 452             new File(root, FILE).delete();
 453         }
 454 
 455         private StoredConfiguration(File root, Configuration<Context> conf)
 456         {
 457             super(MAJOR_VERSION, MINOR_VERSION,
 458                   FileConstants.Type.LIBRARY_MODULE_CONFIG,
 459                   new File(root, FILE));
 460             cf = conf;
 461         }
 462 
 463         protected void storeRest(DataOutputStream out)
 464             throws IOException
 465         {
 466             // Roots
 467             out.writeInt(cf.roots().size());
 468             for (ModuleId mid : cf.roots()) {
 469                 out.writeUTF(mid.toString());
 470             }
 471             // Contexts
 472             out.writeInt(cf.contexts().size());
 473             for (Context cx : cf.contexts()) {
 474                 out.writeUTF(cx.name());
 475                 // Module ids, and their libraries
 476                 out.writeInt(cx.modules().size());
 477                 for (ModuleId mid : cx.modules()) {
 478                     out.writeUTF(mid.toString());
 479                     File lp = cx.findLibraryPathForModule(mid);
 480                     if (lp == null)
 481                         out.writeUTF("");
 482                     else
 483                         out.writeUTF(lp.toString());
 484 
 485                     // Module views
 486                     out.writeInt(cx.views(mid).size());
 487                     for (ModuleId id : cx.views(mid)) {
 488                         out.writeUTF(id.toString());
 489                     }
 490                 }
 491 
 492                 // Local class map
 493                 out.writeInt(cx.localClasses().size());
 494                 for (Map.Entry<String,ModuleId> me
 495                          : cx.moduleForLocalClassMap().entrySet()) {
 496                     out.writeUTF(me.getKey());
 497                     out.writeUTF(me.getValue().toString());
 498                 }
 499 
 500                 // Remote package map
 501                 out.writeInt(cx.contextForRemotePackageMap().size());
 502                 for (Map.Entry<String,String> me
 503                          : cx.contextForRemotePackageMap().entrySet()) {
 504                     out.writeUTF(me.getKey());
 505                     out.writeUTF(me.getValue());
 506                 }
 507 
 508                 // Suppliers
 509                 out.writeInt(cx.remoteContexts().size());
 510                 for (String cxn : cx.remoteContexts()) {
 511                     out.writeUTF(cxn);
 512                 }
 513 
 514                 // Local service implementations
 515                 Map<String,Set<String>> services = cx.services();
 516                 out.writeInt(services.size());
 517                 for (Map.Entry<String,Set<String>> me: services.entrySet()) {
 518                     out.writeUTF(me.getKey());
 519                     Set<String> values = me.getValue();
 520                     out.writeInt(values.size());
 521                     for (String value: values) {
 522                         out.writeUTF(value);
 523                     }
 524                 }
 525 
 526                 // Remote service suppliers
 527                 Map<String,Set<String>> serviceSuppliers = cx.serviceSuppliers();
 528                 out.writeInt(serviceSuppliers.size());
 529                 for (Map.Entry<String,Set<String>> entry: serviceSuppliers.entrySet()) {
 530                     out.writeUTF(entry.getKey());
 531                     Set<String> remotes = entry.getValue();
 532                     out.writeInt(remotes.size());
 533                     for (String rcxn: remotes) {
 534                         out.writeUTF(rcxn);
 535                     }
 536                 }
 537 
 538             }
 539         }
 540 
 541         protected void loadRest(DataInputStream in)
 542             throws IOException
 543         {
 544             // Roots
 545             int nRoots = in.readInt();
 546             List<ModuleId> roots = new ArrayList<>();
 547             for (int i = 0; i < nRoots; i++) {
 548                 String root = in.readUTF();
 549                 ModuleId rmid = jms.parseModuleId(root);
 550                 roots.add(rmid);
 551             }
 552             cf = new Configuration<Context>(roots);
 553             // Contexts
 554             int nContexts = in.readInt();
 555             for (int i = 0; i < nContexts; i++) {
 556                 Context cx = new Context();
 557                 String cxn = in.readUTF();
 558                 // Module ids
 559                 int nModules = in.readInt();
 560                 for (int j = 0; j < nModules; j++) {
 561                     ModuleId mid = jms.parseModuleId(in.readUTF());
 562                     String lps = in.readUTF();
 563                     if (lps.length() > 0)
 564                         cx.putLibraryPathForModule(mid, new File(lps));
 565                     // Module Views
 566                     int nViews = in.readInt();
 567                     Set<ModuleId> views = new HashSet<>();
 568                     for (int k = 0; k < nViews; k++) {
 569                         ModuleId id = jms.parseModuleId(in.readUTF());
 570                         views.add(id);
 571                         cf.put(id.name(), cx);
 572                     }
 573                     cx.add(mid, views);
 574                 }
 575                 cx.freeze();
 576                 assert cx.name().equals(cxn);
 577                 cf.add(cx);
 578                 // Local class map
 579                 int nClasses = in.readInt();
 580                 for (int j = 0; j < nClasses; j++)
 581                     cx.putModuleForLocalClass(in.readUTF(),
 582                                               jms.parseModuleId(in.readUTF()));
 583                 // Remote package map
 584                 int nPackages = in.readInt();
 585                 for (int j = 0; j < nPackages; j++)
 586                     cx.putContextForRemotePackage(in.readUTF(), in.readUTF());
 587 
 588                 // Suppliers
 589                 int nSuppliers = in.readInt();
 590                 for (int j = 0; j < nSuppliers; j++)
 591                     cx.addSupplier(in.readUTF());
 592 
 593                 // Local service implementations
 594                 int nServices = in.readInt();
 595                 for (int j = 0; j < nServices; j++) {
 596                     String sn = in.readUTF();
 597                     int nImpl = in.readInt();
 598                     for (int k = 0; k < nImpl; k++) {
 599                         String cn = in.readUTF();
 600                         cx.putService(sn, cn);
 601                     }
 602                 }
 603 
 604                 // Remote service suppliers
 605                 int nRemoteServices = in.readInt();
 606                 for (int j = 0; j < nRemoteServices; j++) {
 607                     String sn = in.readUTF();
 608                     int nRemotes = in.readInt();
 609                     for (int k = 0; k < nRemotes; k++) {
 610                         String rcxn = in.readUTF();
 611                         cx.addServiceSupplier(sn, rcxn);
 612                     }
 613                 }
 614             }
 615 
 616         }
 617 
 618         private static StoredConfiguration load(File f)
 619             throws IOException
 620         {
 621             StoredConfiguration sp = new StoredConfiguration(f, null);
 622             sp.load();
 623             return sp;
 624         }
 625 
 626     }
 627 
 628     private static final class Signers
 629         extends MetaData {
 630 
 631         private static String FILE = "signer";
 632         private static int MAJOR_VERSION = 0;
 633         private static int MINOR_VERSION = 1;
 634 
 635         private CertificateFactory cf = null;
 636         private Set<CodeSigner> signers;
 637         private Set<CodeSigner> signers() { return signers; }
 638 
 639         private Signers(File root, Set<CodeSigner> signers) {
 640             super(MAJOR_VERSION, MINOR_VERSION,
 641                   FileConstants.Type.LIBRARY_MODULE_SIGNER,
 642                   new File(root, FILE));
 643             this.signers = signers;
 644         }
 645 
 646         protected void storeRest(DataOutputStream out)
 647             throws IOException
 648         {
 649             out.writeInt(signers.size());
 650             for (CodeSigner signer : signers) {
 651                 try {
 652                     CertPath signerCertPath = signer.getSignerCertPath();
 653                     out.write(signerCertPath.getEncoded("PkiPath"));
 654                     Timestamp ts = signer.getTimestamp();
 655                     out.writeByte((ts != null) ? 1 : 0);
 656                     if (ts != null) {
 657                         out.writeLong(ts.getTimestamp().getTime());
 658                         out.write(ts.getSignerCertPath().getEncoded("PkiPath"));
 659                     }
 660                 } catch (CertificateEncodingException cee) {
 661                     throw new IOException(cee);
 662                 }
 663             }
 664         }
 665 
 666         protected void loadRest(DataInputStream in)
 667             throws IOException
 668         {
 669             int size = in.readInt();
 670             for (int i = 0; i < size; i++) {
 671                 try {
 672                     if (cf == null)
 673                         cf = CertificateFactory.getInstance("X.509");
 674                     CertPath signerCertPath = cf.generateCertPath(in, "PkiPath");
 675                     int b = in.readByte();
 676                     if (b != 0) {
 677                         Date timestamp = new Date(in.readLong());
 678                         CertPath tsaCertPath = cf.generateCertPath(in, "PkiPath");
 679                         Timestamp ts = new Timestamp(timestamp, tsaCertPath);
 680                         signers.add(new CodeSigner(signerCertPath, ts));
 681                     } else {
 682                         signers.add(new CodeSigner(signerCertPath, null));
 683                     }
 684                 } catch (CertificateException ce) {
 685                     throw new IOException(ce);
 686                 }
 687             }
 688         }
 689 
 690         private static Signers load(File f)
 691             throws IOException
 692         {
 693             Signers signers = new Signers(f, new HashSet<CodeSigner>());
 694             signers.load();
 695             return signers;
 696         }
 697     }
 698 
 699     protected void gatherLocalModuleIds(String moduleName,
 700                                         Set<ModuleId> mids)
 701         throws IOException
 702     {
 703         moduleDictionary.gatherLocalModuleIds(moduleName, mids);
 704     }
 705 
 706     protected void gatherLocalDeclaringModuleIds(Set<ModuleId> mids)
 707         throws IOException
 708     {
 709         mids.addAll(moduleDictionary.modules());
 710     }
 711 
 712     private void checkModuleId(ModuleId mid) {
 713         Version v = mid.version();
 714         if (v == null)
 715             return;
 716         if (!(v instanceof JigsawVersion))
 717             throw new IllegalArgumentException(mid + ": Not a Jigsaw module id");
 718     }
 719 
 720     private static File moduleDir(File root, ModuleId mid) {
 721         Version v = mid.version();
 722         String vs = (v != null) ? v.toString() : "default";
 723         return new File(new File(root, mid.name()), vs);
 724     }
 725 
 726     private static void checkModuleDir(File md)
 727         throws IOException
 728     {
 729         if (!md.isDirectory())
 730             throw new IOException(md + ": Not a directory");
 731         if (!md.canRead())
 732             throw new IOException(md + ": Not readable");
 733     }
 734 
 735     private File preinstallModuleDir(File dst, ModuleInfo mi) throws IOException {
 736         File md = moduleDir(dst, mi.id());
 737         if (md.exists()) {
 738             Files.deleteTree(md);
 739         }
 740         if (!md.mkdirs()) {
 741             throw new IOException(md + ": Cannot create");
 742         }
 743         return md;
 744     }
 745 
 746     public byte[] readLocalModuleInfoBytes(ModuleId mid)
 747         throws IOException
 748     {
 749         File md = moduleDictionary.findDeclaringModuleDir(mid);
 750         if (md == null)
 751             return null;
 752         return Files.load(new File(md, "info"));
 753     }
 754 
 755     public CodeSigner[] readLocalCodeSigners(ModuleId mid)
 756         throws IOException
 757     {
 758         File md = moduleDictionary.findDeclaringModuleDir(mid);
 759         if (md == null)
 760             return null;
 761 
 762         // Only one signer is currently supported
 763         File f = new File(md, "signer");
 764         // ## concurrency issues : what is the expected behavior if file is
 765         // ## removed by another thread/process here?
 766         if (!f.exists())
 767             return null;
 768         return Signers.load(md).signers().toArray(new CodeSigner[0]);
 769     }
 770 
 771     // ## Close all zip files when we close this library
 772     private Map<ModuleId, Object> contentForModule = new HashMap<>();
 773     private Object NONE = new Object();
 774 
 775     private Object findContent(ModuleId mid)
 776         throws IOException
 777     {
 778         ModuleId dmid = moduleDictionary.getDeclaringModule(mid);
 779         Object o = contentForModule.get(dmid);
 780         if (o == NONE)
 781             return null;
 782         if (o != null)
 783             return o;
 784         File md = moduleDictionary.findDeclaringModuleDir(dmid);
 785         if (md == null) {
 786             contentForModule.put(mid, NONE);
 787             return null;
 788         }
 789         File cf = new File(md, "classes");
 790         if (cf.isFile()) {
 791             ZipFile zf = new ZipFile(cf);
 792             contentForModule.put(mid, zf);
 793             return zf;
 794         }
 795         if (cf.isDirectory()) {
 796             contentForModule.put(mid, cf);
 797             return cf;
 798         }
 799         contentForModule.put(mid, NONE);
 800         return null;
 801     }
 802 
 803     private byte[] loadContent(ZipFile zf, String path)
 804         throws IOException
 805     {
 806         ZipEntry ze = zf.getEntry(path);
 807         if (ze == null)
 808             return null;
 809         return Files.load(zf.getInputStream(ze), (int)ze.getSize());
 810     }
 811 
 812     private byte[] loadContent(ModuleId mid, String path)
 813         throws IOException
 814     {
 815         Object o = findContent(mid);
 816         if (o == null)
 817             return null;
 818         if (o instanceof ZipFile) {
 819             ZipFile zf = (ZipFile)o;
 820             ZipEntry ze = zf.getEntry(path);
 821             if (ze == null)
 822                 return null;
 823             return Files.load(zf.getInputStream(ze), (int)ze.getSize());
 824         }
 825         if (o instanceof File) {
 826             File f = new File((File)o, path);
 827             if (!f.exists())
 828                 return null;
 829             return Files.load(f);
 830         }
 831         assert false;
 832         return null;
 833     }
 834 
 835     private URI locateContent(ModuleId mid, String path)
 836         throws IOException
 837     {
 838         Object o = findContent(mid);
 839         if (o == null)
 840             return null;
 841         if (o instanceof ZipFile) {
 842             ZipFile zf = (ZipFile)o;
 843             ZipEntry ze = zf.getEntry(path);
 844             if (ze == null)
 845                 return null;
 846             return URI.create("jar:"
 847                               + new File(zf.getName()).toURI().toString()
 848                               + "!/" + path);
 849         }
 850         if (o instanceof File) {
 851             File f = new File((File)o, path);
 852             if (!f.exists())
 853                 return null;
 854             return f.toURI();
 855         }
 856         return null;
 857     }
 858 
 859     public byte[] readLocalClass(ModuleId mid, String className)
 860         throws IOException
 861     {
 862         return loadContent(mid, className.replace('.', '/') + ".class");
 863     }
 864 
 865     public List<String> listLocalClasses(ModuleId mid, boolean all)
 866         throws IOException
 867     {
 868         File md = moduleDictionary.findDeclaringModuleDir(mid);
 869         if (md == null)
 870             return null;
 871         Index ix = Index.load(md);
 872         int os = all ? ix.otherClasses().size() : 0;
 873         ArrayList<String> cns
 874             = new ArrayList<String>(ix.publicClasses().size() + os);
 875         cns.addAll(ix.publicClasses());
 876         if (all)
 877             cns.addAll(ix.otherClasses());
 878         return cns;
 879     }
 880 
 881     public Configuration<Context> readConfiguration(ModuleId mid)
 882         throws IOException
 883     {
 884         File md = moduleDictionary.findDeclaringModuleDir(mid);
 885         if (md == null) {
 886             if (parent != null) {
 887                 return parent.readConfiguration(mid);
 888             }
 889             return null;
 890         }
 891         StoredConfiguration scf = StoredConfiguration.load(md);
 892         return scf.cf;
 893     }
 894 
 895     private boolean addToIndex(ClassInfo ci, Index ix)
 896         throws IOException
 897     {
 898         if (ci.isModuleInfo())
 899             return false;
 900         if (ci.moduleName() != null) {
 901             // ## From early Jigsaw development; can probably delete now
 902             throw new IOException("Old-style class file with"
 903                                   + " module attribute");
 904         }
 905         if (ci.isPublic())
 906             ix.publicClasses().add(ci.name());
 907         else
 908             ix.otherClasses().add(ci.name());
 909         return true;
 910     }
 911 
 912     private void reIndex(ModuleId mid)
 913         throws IOException
 914     {
 915 
 916         File md = moduleDictionary.findDeclaringModuleDir(mid);
 917         if (md == null)
 918             throw new IllegalArgumentException(mid + ": No such module");
 919         File cd = new File(md, "classes");
 920         final Index ix = new Index(md);
 921 
 922         if (cd.isDirectory()) {
 923             Files.walkTree(cd, new Files.Visitor<File>() {
 924                 public void accept(File f) throws IOException {
 925                     if (f.getPath().endsWith(".class"))
 926                         addToIndex(ClassInfo.read(f), ix);
 927                 }
 928             });
 929         } else if (cd.isFile()) {
 930             try (FileInputStream fis = new FileInputStream(cd);
 931                  ZipInputStream zis = new ZipInputStream(fis))
 932             {
 933                 ZipEntry ze;
 934                 while ((ze = zis.getNextEntry()) != null) {
 935                     if (!ze.getName().endsWith(".class"))
 936                         continue;
 937                     addToIndex(ClassInfo.read(Files.nonClosingStream(zis),
 938                                               ze.getSize(),
 939                                               mid + ":" + ze.getName()),
 940                                ix);
 941                 }
 942             }
 943         }
 944 
 945         ix.store();
 946     }
 947 
 948     /**
 949      * Strip the debug attributes from the classes in a given module
 950      * directory.
 951      */
 952     private void strip(File md) throws IOException {
 953         File classes = new File(md, "classes");
 954         if (classes.isFile()) {
 955             File pf = new File(md, "classes.pack");
 956             try (JarFile jf = new JarFile(classes);
 957                 FileOutputStream out = new FileOutputStream(pf))
 958             {
 959                 Pack200.Packer packer = Pack200.newPacker();
 960                 Map<String,String> p = packer.properties();
 961                 p.put("com.sun.java.util.jar.pack.strip.debug", Pack200.Packer.TRUE);
 962                 packer.pack(jf, out);
 963             }
 964 
 965             try (OutputStream out = new FileOutputStream(classes);
 966                  JarOutputStream jos = new JarOutputStream(out))
 967             {
 968                 Pack200.Unpacker unpacker = Pack200.newUnpacker();
 969                 unpacker.unpack(pf, jos);
 970             } finally {
 971                 pf.delete();
 972            }
 973         }
 974     }
 975 
 976     private List<Path> listFiles(Path dir) throws IOException {
 977         final List<Path> files = new ArrayList<>();
 978         java.nio.file.Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
 979             @Override
 980             public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
 981                 throws IOException
 982             {
 983                 if (!file.endsWith("module-info.class"))
 984                     files.add(file);
 985 
 986                 return FileVisitResult.CONTINUE;
 987             }
 988         });
 989         return files;
 990     }
 991 
 992     private ModuleId installWhileLocked(Manifest mf, File dst, boolean strip)
 993         throws IOException
 994     {
 995         if (mf.classes().size() > 1)
 996             throw new IllegalArgumentException("Multiple module-class"
 997                                                + " directories"
 998                                                + " not yet supported");
 999         if (mf.classes().size() < 1)
1000             throw new IllegalArgumentException("At least one module-class"
1001                                                + " directory required");
1002         File classes = mf.classes().get(0);
1003         final String mn = mf.module();
1004 
1005         File mif = new File(classes, "module-info.class");
1006         File src = null;
1007         if (mif.exists()) {
1008             src = classes;
1009         } else {
1010             src = new File(classes, mn);
1011             mif = new File(src, "module-info.class");
1012         }
1013         byte[] bs =  Files.load(mif);
1014         ModuleInfo mi = jms.parseModuleInfo(bs);
1015         if (!mi.id().name().equals(mn)) {
1016             // ## Need a more appropriate throwable here
1017             throw new Error(mif + " is for module " + mi.id().name()
1018                             + ", not " + mn);
1019         }
1020         String m = mi.id().name();
1021         JigsawVersion v = (JigsawVersion)mi.id().version();
1022         String vs = (v == null) ? "default" : v.toString();
1023 
1024         try {
1025             File mdst;
1026             if (dst.equals(root)) {
1027                 mdst = moduleDictionary.add(mi);
1028             } else {
1029                 mdst = preinstallModuleDir(dst, mi);
1030             }
1031             Files.store(bs, new File(mdst, "info"));
1032             File cldst = new File(mdst, "classes");
1033 
1034             // Delete the config file, if one exists
1035             StoredConfiguration.delete(mdst);
1036 
1037             if (false) {
1038 
1039                 // ## Retained for now in case we later want to add an option
1040                 // ## to install into a tree rather than a zip file
1041 
1042                 // Copy class files and build index
1043                 final Index ix = new Index(mdst);
1044                 Files.copyTree(src, cldst, new Files.Filter<File>() {
1045 
1046                     public boolean accept(File f) throws IOException {
1047                         if (f.isDirectory())
1048                             return true;
1049                         if (f.getName().endsWith(".class")) {
1050                             return addToIndex(ClassInfo.read(f), ix);
1051                         } else {
1052                             return true;
1053                         }
1054                     }
1055                 });
1056                 ix.store();
1057             } else {
1058                 // Copy class/resource files and build index
1059                 Index ix = new Index(mdst);
1060                 Path srcPath = src.toPath();
1061                 List<Path> files = listFiles(srcPath);
1062 
1063                 if (!files.isEmpty()) {
1064                     try (FileOutputStream fos = new FileOutputStream(new File(mdst, "classes"));
1065                          JarOutputStream jos = new JarOutputStream(new BufferedOutputStream(fos)))
1066                     {
1067                         boolean deflate = isDeflated();
1068                         for (Path path : files) {
1069                             File file = path.toFile();
1070                             String jp = Files.convertSeparator(srcPath.relativize(path).toString());
1071                             try (OutputStream out = Files.newOutputStream(jos, deflate, jp)) {
1072                                 java.nio.file.Files.copy(path, out);
1073                             }
1074                             if (file.getName().endsWith(".class"))
1075                                 addToIndex(ClassInfo.read(file), ix);
1076                         }
1077                     }
1078                 }
1079                 ix.store();
1080                 if (strip) {
1081                     strip(mdst);
1082                 }
1083             }
1084         } catch (ConfigurationException x) {
1085             // module already exists
1086             throw new IOException(x);
1087         } catch (IOException x) {
1088             try {
1089                 moduleDictionary.remove(mi);
1090             } catch (IOException y) {
1091                 x.addSuppressed(y);
1092             }
1093             throw x;
1094         }
1095         return mi.id();
1096     }
1097 
1098     public void installFromManifests(Collection<Manifest> mfs, boolean strip)
1099         throws ConfigurationException, IOException
1100     {
1101         boolean complete = false;
1102         List<ModuleId> mids = new ArrayList<>();
1103         FileChannel fc = FileChannel.open(lockf.toPath(), WRITE);
1104         try {
1105             fc.lock();
1106             moduleDictionary.load();
1107             for (Manifest mf : mfs) {
1108                 mids.add(installWhileLocked(mf, root, strip));
1109             }
1110             configureWhileModuleDirectoryLocked(null);
1111             complete = true;
1112         } catch (ConfigurationException | IOException x) {
1113             try {
1114                 for (ModuleId mid : mids) {
1115                     ModuleInfo mi = readLocalModuleInfo(mid);
1116                     if (mi != null) {
1117                         moduleDictionary.remove(mi);
1118                     }
1119                 }
1120             } catch (IOException y) {
1121                 x.addSuppressed(y);
1122             }
1123             throw x;
1124         } finally {
1125             if (complete) {
1126                 moduleDictionary.store();
1127             }
1128             fc.close();
1129         }
1130     }
1131 
1132     @Override
1133     public void installFromManifests(Collection<Manifest> mfs)
1134         throws ConfigurationException, IOException
1135     {
1136         installFromManifests(mfs, false);
1137     }
1138 
1139     private ModuleFileVerifier.Parameters mfvParams;
1140     private ModuleId installWhileLocked(InputStream is, boolean verifySignature, boolean strip)
1141         throws ConfigurationException, IOException, SignatureException
1142     {
1143         BufferedInputStream bin = new BufferedInputStream(is);
1144         DataInputStream in = new DataInputStream(bin);
1145         ModuleInfo mi = null;
1146         try (ModuleFile.Reader mr = new ModuleFile.Reader(in)) {
1147             byte[] mib = mr.readStart();
1148             mi = jms.parseModuleInfo(mib);
1149             File md = moduleDictionary.add(mi);
1150             if (verifySignature && mr.hasSignature()) {
1151                 ModuleFileVerifier mfv = new SignedModule.PKCS7Verifier(mr);
1152                 if (mfvParams == null) {
1153                     mfvParams = new SignedModule.VerifierParameters();
1154                 }
1155                 // Verify the module signature and validate the signer's
1156                 // certificate chain
1157                 Set<CodeSigner> signers = mfv.verifySignature(mfvParams);
1158 
1159                 // Verify the module header hash and the module info hash
1160                 mfv.verifyHashesStart(mfvParams);
1161 
1162                 // ## Check policy - is signer trusted and what permissions
1163                 // ## should be granted?
1164 
1165                 // Store signer info
1166                 new Signers(md, signers).store();
1167 
1168                 // Read and verify the rest of the hashes
1169                 mr.readRest(md, isDeflated(), natlibs(), natcmds(), configs());
1170                 mfv.verifyHashesRest(mfvParams);
1171             } else {
1172                 mr.readRest(md, isDeflated(), natlibs(), natcmds(), configs());
1173             }
1174 
1175             if (strip)
1176                 strip(md);
1177             reIndex(mi.id());         // ## Could do this while reading module file
1178 
1179             return mi.id();
1180 
1181         } catch (ConfigurationException | IOException | SignatureException x) {
1182             if (mi != null) {
1183                 try {
1184                     moduleDictionary.remove(mi);
1185                 } catch (IOException y) {
1186                     x.addSuppressed(y);
1187                 }
1188             }
1189             throw x;
1190         }
1191     }
1192 
1193     private ModuleId installFromJarFile(File mf, boolean verifySignature, boolean strip)
1194         throws ConfigurationException, IOException, SignatureException
1195     {
1196         ModuleInfo mi = null;
1197         try (JarFile jf = new JarFile(mf, verifySignature)) {
1198             mi = jf.getModuleInfo();
1199             if (mi == null)
1200                 throw new ConfigurationException(mf + ": not a modular JAR file");
1201 
1202             File md = moduleDictionary.add(mi);
1203             ModuleId mid = mi.id();
1204 
1205             boolean signed = false;
1206 
1207             // copy the jar file to the module library
1208             File classesDir = new File(md, "classes");
1209             try (FileOutputStream fos = new FileOutputStream(classesDir);
1210                  BufferedOutputStream bos = new BufferedOutputStream(fos);
1211                  JarOutputStream jos = new JarOutputStream(bos)) {
1212                 jos.setLevel(0);
1213 
1214                 Enumeration<JarEntry> entries = jf.entries();
1215                 while (entries.hasMoreElements()) {
1216                     JarEntry je = entries.nextElement();
1217                     try (InputStream is = jf.getInputStream(je)) {
1218                         if (je.getName().equals(JarFile.MODULEINFO_NAME)) {
1219                             java.nio.file.Files.copy(is, md.toPath().resolve("info"));
1220                         } else {
1221                             writeJarEntry(is, je, jos);
1222                         }
1223                     }
1224                     if (!signed) {
1225                         String name = je.getName().toUpperCase(Locale.ENGLISH);
1226                         signed = name.startsWith("META-INF/")
1227                                  && name.endsWith(".SF");
1228                     }
1229                 }
1230             }
1231 
1232             try {
1233                 if (verifySignature && signed) {
1234                     // validate the code signers
1235                     Set<CodeSigner> signers = getSigners(jf);
1236                     SignedModule.validateSigners(signers);
1237                     // store the signers
1238                     new Signers(md, signers).store();
1239                 }
1240             } catch (CertificateException ce) {
1241                 throw new SignatureException(ce);
1242             }
1243 
1244             if (strip)
1245                 strip(md);
1246             reIndex(mid);
1247 
1248             return mid;
1249         } catch (ConfigurationException | IOException | SignatureException x) {
1250             if (mi != null) {
1251                 try {
1252                     moduleDictionary.remove(mi);
1253                 } catch (IOException y) {
1254                     x.addSuppressed(y);
1255                 }
1256             }
1257             throw x;
1258         }
1259     }
1260 
1261     /**
1262      * Returns the set of signers of the specified jar file. Each signer
1263      * must have signed all relevant entries.
1264      */
1265     private static Set<CodeSigner> getSigners(JarFile jf)
1266         throws SignatureException
1267     {
1268         Set<CodeSigner> signers = new HashSet<>();
1269         Enumeration<JarEntry> entries = jf.entries();
1270         while (entries.hasMoreElements()) {
1271             JarEntry je = entries.nextElement();
1272             String name = je.getName().toUpperCase(Locale.ENGLISH);
1273             if (name.endsWith("/") || isSigningRelated(name))
1274                 continue;
1275 
1276             // A signed modular jar can be signed by multiple signers.
1277             // However, all entries must be signed by each of these signers.
1278             // Signers that only sign a subset of entries are ignored.
1279             CodeSigner[] jeSigners = je.getCodeSigners();
1280             if (jeSigners == null || jeSigners.length == 0)
1281                 throw new SignatureException("Found unsigned entry in "
1282                                              + "signed modular JAR");
1283 
1284             Set<CodeSigner> jeSignerSet =
1285                 new HashSet<>(Arrays.asList(jeSigners));
1286             if (signers.isEmpty())
1287                 signers.addAll(jeSignerSet);
1288             else {
1289                 if (signers.retainAll(jeSignerSet) && signers.isEmpty())
1290                     throw new SignatureException("No signers in common in "
1291                                                  + "signed modular JAR");
1292             }
1293         }
1294         return signers;
1295     }
1296 
1297     // true if file is part of the signature mechanism itself
1298     private static boolean isSigningRelated(String name) {
1299         if (!name.startsWith("META-INF/")) {
1300             return false;
1301         }
1302         name = name.substring(9);
1303         if (name.indexOf('/') != -1) {
1304             return false;
1305         }
1306         if (name.endsWith(".DSA") ||
1307             name.endsWith(".RSA") ||
1308             name.endsWith(".SF")  ||
1309             name.endsWith(".EC")  ||
1310             name.startsWith("SIG-") ||
1311             name.equals("MANIFEST.MF")) {
1312             return true;
1313         }
1314         return false;
1315     }
1316 
1317     private void writeJarEntry(InputStream is, JarEntry je, JarOutputStream jos)
1318         throws IOException, SignatureException
1319     {
1320         JarEntry entry = new JarEntry(je.getName());
1321         entry.setMethod(isDeflated() ? ZipEntry.DEFLATED : ZipEntry.STORED);
1322         entry.setTime(je.getTime());
1323         try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
1324             int size = 0;
1325             byte[] bs = new byte[1024];
1326             int cc = 0;
1327             // This will throw a SecurityException if a signature is invalid.
1328             while ((cc = is.read(bs)) > 0) {
1329                 baos.write(bs, 0, cc);
1330                 size += cc;
1331             }
1332             if (!isDeflated()) {
1333                 entry.setSize(size);
1334                 entry.setCrc(je.getCrc());
1335                 entry.setCompressedSize(size);
1336             }
1337             jos.putNextEntry(entry);
1338             if (baos.size() > 0)
1339                 baos.writeTo(jos);
1340             jos.closeEntry();
1341         } catch (SecurityException se) {
1342             throw new SignatureException(se);
1343         }
1344     }
1345 
1346     private ModuleId installWhileLocked(File mf, boolean verifySignature, boolean strip)
1347         throws ConfigurationException, IOException, SignatureException
1348     {
1349         if (mf.getName().endsWith(".jar"))
1350             return installFromJarFile(mf, verifySignature, strip);
1351         else {
1352             // Assume jmod file
1353             try (FileInputStream in = new FileInputStream(mf)) {
1354                 return installWhileLocked(in, verifySignature, strip);
1355             }
1356         }
1357     }
1358 
1359     public void install(Collection<File> mfs, boolean verifySignature, boolean strip)
1360         throws ConfigurationException, IOException, SignatureException
1361     {
1362         List<ModuleId> mids = new ArrayList<>();
1363         boolean complete = false;
1364         FileChannel fc = FileChannel.open(lockf.toPath(), WRITE);
1365         try {
1366             fc.lock();
1367             moduleDictionary.load();
1368             for (File mf : mfs)
1369                 mids.add(installWhileLocked(mf, verifySignature, strip));
1370             configureWhileModuleDirectoryLocked(mids);
1371             complete = true;
1372         } catch (ConfigurationException | IOException | SignatureException x) {
1373             try {
1374                 for (ModuleId mid : mids) {
1375                     ModuleInfo mi = readLocalModuleInfo(mid);
1376                     if (mi != null) {
1377                         moduleDictionary.remove(mi);
1378                     }
1379                 }
1380             } catch (IOException y) {
1381                 x.addSuppressed(y);
1382             }
1383             throw x;
1384         } finally {
1385             if (complete) {
1386                 moduleDictionary.store();
1387             }
1388             fc.close();
1389         }
1390     }
1391 
1392     @Override
1393     public void install(Collection<File> mfs, boolean verifySignature)
1394         throws ConfigurationException, IOException, SignatureException
1395     {
1396         install(mfs, verifySignature, false);
1397     }
1398 
1399     // Public entry point, since the Resolver itself is package-private
1400     //
1401     public Resolution resolve(Collection<ModuleIdQuery> midqs)
1402         throws ConfigurationException, IOException
1403     {
1404         try (FileChannel fc = FileChannel.open(lockf.toPath(), WRITE)) {
1405             fc.lock();
1406             return Resolver.run(this, midqs);
1407         }
1408     }
1409 
1410     public void install(Resolution res, boolean verifySignature, boolean strip)
1411         throws ConfigurationException, IOException, SignatureException
1412     {
1413         boolean complete = false;
1414         FileChannel fc = FileChannel.open(lockf.toPath(), WRITE);
1415         try {
1416             fc.lock();
1417             moduleDictionary.load();
1418 
1419             // ## Handle case of installing multiple root modules
1420             assert res.rootQueries.size() == 1;
1421             ModuleIdQuery midq = res.rootQueries.iterator().next();
1422             ModuleInfo root = null;
1423             for (String mn : res.moduleViewForName.keySet()) {
1424                 ModuleView mv = res.moduleViewForName.get(mn);
1425                 if (midq.matches(mv.id())) {
1426                     root = mv.moduleInfo();
1427                     break;
1428                 }
1429             }
1430             assert root != null;
1431 
1432             // Download
1433             //
1434             for (ModuleId mid : res.modulesNeeded()) {
1435                 URI u = res.locationForName.get(mid.name());
1436                 assert u != null;
1437                 RemoteRepository rr = repositoryList().firstRepository();
1438                 assert rr != null;
1439                 installWhileLocked(rr.fetch(mid), verifySignature, strip);
1440                 res.locationForName.put(mid.name(), location());
1441                 // ## If something goes wrong, delete all our modules
1442             }
1443 
1444             // Configure
1445             //
1446             configureWhileModuleDirectoryLocked(res.modulesNeeded());
1447             complete = true;
1448         } catch (ConfigurationException | IOException | SignatureException x) {
1449             try {
1450                 for (ModuleId mid : res.modulesNeeded()) {
1451                     ModuleInfo mi = readLocalModuleInfo(mid);
1452                     if (mi != null) {
1453                         moduleDictionary.remove(mi);
1454                     }
1455                 }
1456             } catch (IOException y) {
1457                 x.addSuppressed(y);
1458             }
1459             throw x;
1460         } finally {
1461             if (complete) {
1462                 moduleDictionary.store();
1463             }
1464             fc.close();
1465         }
1466     }
1467 
1468     @Override
1469     public void install(Resolution res, boolean verifySignature)
1470         throws ConfigurationException, IOException, SignatureException
1471     {
1472         install(res, verifySignature, false);
1473     }
1474 





























































































































1475     /**
1476      * <p> Pre-install one or more modules to an arbitrary destination
1477      * directory. </p>
1478      *
1479      * <p> A pre-installed module has the same format as within the library
1480      * itself, except that there is never a configuration file. </p>
1481      *
1482      * <p> This method is provided for use by the module-packaging tool. </p>
1483      *
1484      * @param   mfs
1485      *          The manifest describing the contents of the modules to be
1486      *          pre-installed
1487      *
1488      * @param   dst
1489      *          The destination directory, with one subdirectory per module
1490      *          name, each of which contains one subdirectory per version
1491      */
1492     public void preInstall(Collection<Manifest> mfs, File dst)
1493         throws IOException
1494     {
1495         Files.mkdirs(dst, "module destination");
1496         try (FileChannel fc = FileChannel.open(lockf.toPath(), WRITE)) {
1497             fc.lock();
1498             for (Manifest mf : mfs) {
1499                 installWhileLocked(mf, dst, false);
1500             }
1501             // no update to the module directory
1502         }
1503     }
1504 
1505     public void preInstall(Manifest mf, File dst)
1506         throws IOException
1507     {
1508         preInstall(Collections.singleton(mf), dst);
1509     }
1510 
1511     /**
1512      * Refresh the module library.
1513      */
1514     public void refresh() throws IOException {
1515         try (FileChannel fc = FileChannel.open(lockf.toPath(), WRITE)) {
1516             fc.lock();
1517             moduleDictionary.refresh();
1518             moduleDictionary.store();
1519         }
1520     }
1521 
1522     /**
1523      * <p> Update the configurations of any root modules affected by the
1524      * copying of the named modules, in pre-installed format, into this
1525      * library. </p>
1526      *
1527      * @param   mids
1528      *          The module ids of the new or updated modules, or
1529      *          {@code null} if the configuration of every root module
1530      *          should be (re)computed
1531      */
1532     public void configure(Collection<ModuleId> mids)
1533         throws ConfigurationException, IOException
1534     {
1535         try (FileChannel fc = FileChannel.open(lockf.toPath(), WRITE)) {
1536             fc.lock();
1537             configureWhileModuleDirectoryLocked(mids);
1538         }
1539     }
1540 
1541     private void configureWhileModuleDirectoryLocked(Collection<ModuleId> mids)
1542         throws ConfigurationException, IOException
1543     {
1544         // ## mids not used yet












1545         List<ModuleId> roots = new ArrayList<>();
1546         for (ModuleId mid : listLocalDeclaringModuleIds()) {
1547             // each module can have multiple entry points
1548             // only configure once for each module.
1549             ModuleInfo mi = readModuleInfo(mid);
1550             for (ModuleView mv : mi.views()) {
1551                 if (mv.mainClass() != null) {
1552                     roots.add(mid);
1553                     break;
1554                 }
1555             }
1556         }
1557 
1558         for (ModuleId mid : roots) {
1559             // ## We could be a lot more clever about this!
1560             Configuration<Context> cf
1561                 = Configurator.configure(this, mid.toQuery());
1562             File md = moduleDictionary.findDeclaringModuleDir(mid);
1563             new StoredConfiguration(md, cf).store();
1564         }
1565     }
1566 
1567     public URI findLocalResource(ModuleId mid, String name)
1568         throws IOException
1569     {
1570         return locateContent(mid, name);
1571     }
1572 
1573     public File findLocalNativeLibrary(ModuleId mid, String name)
1574         throws IOException
1575     {
1576         File f = natlibs();
1577         if (f == null) {
1578             f = moduleDictionary.findDeclaringModuleDir(mid);
1579             if (f == null)
1580                 return null;
1581             f = new File(f, "lib");
1582         }
1583         f = new File(f, name);
1584         if (!f.exists())
1585             return null;
1586         return f;
1587     }
1588 
1589     public File classPath(ModuleId mid)
1590         throws IOException
1591     {
1592         File md = moduleDictionary.findDeclaringModuleDir(mid);
1593         if (md == null) {
1594             if (parent != null)
1595                 return parent.classPath(mid);
1596             return null;
1597         }
1598         // ## Check for other formats here
1599         return new File(md, "classes");
1600     }
1601 
1602     /**
1603      * <p> Re-index the classes of the named previously-installed modules, and
1604      * then update the configurations of any affected root modules. </p>
1605      *
1606      * <p> This method is intended for use during development, when a build
1607      * process may update a previously-installed module in place, adding or
1608      * removing classes. </p>
1609      *
1610      * @param   mids
1611      *          The module ids of the new or updated modules, or
1612      *          {@code null} if the configuration of every root module
1613      *          should be (re)computed
1614      */
1615     public void reIndex(List<ModuleId> mids)
1616         throws ConfigurationException, IOException
1617     {
1618         for (ModuleId mid : mids)
1619             reIndex(mid);
1620         configure(mids);
1621     }
1622 
1623     private static final class ModuleDictionary
1624     {
1625         private static final String FILE
1626             = FileConstants.META_PREFIX + "mids";
1627 
1628         private static final int MAJOR_VERSION = 0;
1629         private static final int MINOR_VERSION = 0;
1630 
1631         private final File root;
1632         private final File file;
1633         private Map<String,Set<ModuleId>> moduleIdsForName;
1634         private Map<ModuleId,ModuleId> providingModuleIds;
1635         private Set<ModuleId> modules;
1636         private long lastUpdated;
1637 
1638         ModuleDictionary(File root) {
1639             this.root = root;
1640             this.file = new File(root, FileConstants.META_PREFIX + "mids");
1641             this.providingModuleIds = new LinkedHashMap<>();
1642             this.moduleIdsForName = new LinkedHashMap<>();
1643             this.modules = new HashSet<>();
1644             this.lastUpdated = -1;
1645         }
1646 
1647         private static FileHeader fileHeader() {
1648             return (new FileHeader()
1649                     .type(FileConstants.Type.LIBRARY_MODULE_IDS)
1650                     .majorVersion(MAJOR_VERSION)
1651                     .minorVersion(MINOR_VERSION));
1652         }
1653 
1654         void load() throws IOException {
1655             if (lastUpdated == file.lastModified())
1656                 return;
1657 
1658             providingModuleIds = new LinkedHashMap<>();
1659             moduleIdsForName = new LinkedHashMap<>();
1660             modules = new HashSet<>();
1661             lastUpdated = file.lastModified();
1662 
1663             try (FileInputStream fin = new FileInputStream(file);
1664                  DataInputStream in = new DataInputStream(new BufferedInputStream(fin)))
1665             {
1666                 FileHeader fh = fileHeader();
1667                 fh.read(in);
1668                 int nMids = in.readInt();
1669                 for (int j = 0; j < nMids; j++) {
1670                     ModuleId mid = jms.parseModuleId(in.readUTF());
1671                     ModuleId pmid = jms.parseModuleId(in.readUTF());
1672                     providingModuleIds.put(mid, pmid);
1673                     addModuleId(mid);
1674                     addModuleId(pmid);
1675                     if (mid.equals(pmid))
1676                         modules.add(mid);
1677                 }
1678             }
1679         }
1680 
1681         void store() throws IOException {
1682             File newfn = new File(root, "mids.new");
1683             FileOutputStream fout = new FileOutputStream(newfn);
1684             DataOutputStream out = new DataOutputStream(new BufferedOutputStream(fout));
1685             try {
1686                 try {
1687                     fileHeader().write(out);
1688                     out.writeInt(providingModuleIds.size());
1689                     for (Map.Entry<ModuleId, ModuleId> e : providingModuleIds.entrySet()) {
1690                         out.writeUTF(e.getKey().toString());
1691                         out.writeUTF(e.getValue().toString());
1692                     }
1693                 } finally {
1694                     out.close();
1695                 }
1696             } catch (IOException x) {
1697                 newfn.delete();
1698                 throw x;
1699             }
1700             java.nio.file.Files.move(newfn.toPath(), file.toPath(), ATOMIC_MOVE);
1701         }
1702 
1703         void gatherLocalModuleIds(String moduleName, Set<ModuleId> mids)
1704                 throws IOException
1705         {
1706             if (lastUpdated != file.lastModified())
1707                 load();
1708 
1709             if (moduleName == null) {
1710                 mids.addAll(providingModuleIds.keySet());
1711             } else {
1712                 Set<ModuleId> res = moduleIdsForName.get(moduleName);
1713                 if (res != null)
1714                     mids.addAll(res);
1715             }
1716         }
1717 
1718         ModuleId getDeclaringModule(ModuleId mid) throws IOException {
1719             if (lastUpdated != file.lastModified())
1720                 load();
1721 
1722             ModuleId pmid = providingModuleIds.get(mid);
1723             if (pmid != null && !pmid.equals(providingModuleIds.get(pmid))) {
1724                 // mid is an alias
1725                 pmid = providingModuleIds.get(pmid);
1726             }
1727             return pmid;
1728         }
1729 
1730         File findDeclaringModuleDir(ModuleId mid)
1731                 throws IOException
1732         {
1733             ModuleId dmid = getDeclaringModule(mid);
1734             if (dmid == null)
1735                 return null;
1736 
1737             File md = moduleDir(root, dmid);
1738             assert md.exists();
1739             checkModuleDir(md);
1740             return md;
1741         }
1742 
1743         Set<ModuleId> modules() throws IOException {
1744             if (lastUpdated != file.lastModified())
1745                 load();
1746             return modules;
1747         }
1748 
1749         void addModuleId(ModuleId mid) {
1750             Set<ModuleId> mids = moduleIdsForName.get(mid.name());
1751             if (mids == null) {
1752                 mids = new HashSet<>();
1753                 moduleIdsForName.put(mid.name(), mids);
1754             }
1755             mids.add(mid);
1756         }
1757 
1758         File add(ModuleInfo mi)
1759                 throws ConfigurationException, IOException
1760         {
1761             File md = ensureNewModule(mi);
1762             addToDirectory(mi);
1763             return md;
1764         }
1765 
1766         private void addToDirectory(ModuleInfo mi) {
1767             modules.add(mi.id());
1768             for (ModuleView view : mi.views()) {
1769                 providingModuleIds.put(view.id(), mi.id());
1770                 addModuleId(view.id());
1771                 for (ModuleId alias : view.aliases()) {
1772                     providingModuleIds.put(alias, view.id());
1773                     addModuleId(alias);
1774                 }
1775             }
1776         }
1777 
1778         void remove(ModuleInfo mi) throws IOException {
1779             modules.remove(mi.id());
1780             for (ModuleView view : mi.views()) {
1781                 providingModuleIds.remove(view.id());
1782                 Set<ModuleId> mids = moduleIdsForName.get(view.id().name());
1783                 if (mids != null)
1784                     mids.remove(view.id());
1785                 for (ModuleId alias : view.aliases()) {
1786                     providingModuleIds.remove(alias);
1787                     mids = moduleIdsForName.get(alias.name());
1788                     if (mids != null)
1789                         mids.remove(view.id());
1790                 }
1791             }
1792             File md = moduleDir(root, mi.id());
1793             delete(md);
1794         }
1795 
1796         private void delete(File md) throws IOException {
1797             if (!md.exists())
1798                 return;
1799 
1800             checkModuleDir(md);

1801             ModuleFile.Reader.remove(md);
1802             File parent = md.getParentFile();
1803             if (parent.list().length == 0)
1804                 parent.delete();
1805         }
1806         
1807         void refresh() throws IOException {
1808             providingModuleIds = new LinkedHashMap<>();
1809             moduleIdsForName = new LinkedHashMap<>();
1810             modules = new HashSet<>();
1811 
1812             try (DirectoryStream<Path> ds = java.nio.file.Files.newDirectoryStream(root.toPath())) {
1813                 for (Path mnp : ds) {
1814                     String mn = mnp.toFile().getName();
1815                     if (mn.startsWith(FileConstants.META_PREFIX)) {

1816                         continue;
1817                     }
1818 
1819                     try (DirectoryStream<Path> mds = java.nio.file.Files.newDirectoryStream(mnp)) {
1820                         for (Path versionp : mds) {
1821                             File v = versionp.toFile();
1822                             if (!v.isDirectory()) {
1823                                 throw new IOException(versionp + ": Not a directory");
1824                             }
1825                             modules.add(jms.parseModuleId(mn, v.getName()));
1826                         }
1827                     }
1828                 }
1829             }
1830             for (ModuleId mid : modules) {
1831                 byte[] bs = Files.load(new File(moduleDir(root, mid), "info"));
1832                 ModuleInfo mi = jms.parseModuleInfo(bs);
1833                 addToDirectory(mi);
1834             }
1835         }
1836 
1837         private File ensureNewModule(ModuleInfo mi)
1838                 throws ConfigurationException, IOException
1839         {
1840             for (ModuleView view : mi.views()) {
1841                 if (providingModuleIds.containsKey(view.id())) {
1842                     throw new ConfigurationException("module view " + view.id()
1843                             + " already installed");
1844                 }
1845                 for (ModuleId alias : view.aliases()) {
1846                     ModuleId mid = alias;
1847                     if (providingModuleIds.containsKey(mid)) {
1848                         throw new ConfigurationException("alias " + alias
1849                                 + " already installed");
1850                     }
1851                 }
1852             }
1853             File md = moduleDir(root, mi.id());
1854             if (md.exists()) {
1855                 throw new ConfigurationException("module " + mi.id()
1856                         + " already installed");
1857             }
1858             if (!md.mkdirs()) {
1859                 throw new IOException(md + ": Cannot create");
1860             }
1861             return md;
1862         }
1863     }
1864 
1865     // -- Repositories --
1866 
1867     private static class RepoList
1868         implements RemoteRepositoryList
1869     {
1870 
1871         private static final int MINOR_VERSION = 0;
1872         private static final int MAJOR_VERSION = 0;
1873 
1874         private final File root;
1875         private final File listFile;
1876 
1877         private RepoList(File r) {
1878             root = new File(r, FileConstants.META_PREFIX + "repos");
1879             listFile = new File(root, FileConstants.META_PREFIX + "list");
1880         }
1881 
1882         private static FileHeader fileHeader() {
1883             return (new FileHeader()
1884                     .type(FileConstants.Type.REMOTE_REPO_LIST)
1885                     .majorVersion(MAJOR_VERSION)
1886                     .minorVersion(MINOR_VERSION));
1887         }
1888 
1889         private List<RemoteRepository> repos = null;
1890         private long nextRepoId = 0;
1891 
1892         private File repoDir(long id) {
1893             return new File(root, Long.toHexString(id));
1894         }
1895 
1896         private void load() throws IOException {
1897 
1898             repos = new ArrayList<>();
1899             if (!root.exists() || !listFile.exists())
1900                 return;
1901             FileInputStream fin = new FileInputStream(listFile);
1902             DataInputStream in
1903                 = new DataInputStream(new BufferedInputStream(fin));
1904             try {
1905 
1906                 FileHeader fh = fileHeader();
1907                 fh.read(in);
1908                 nextRepoId = in.readLong();
1909                 int n = in.readInt();
1910                 long[] ids = new long[n];
1911                 for (int i = 0; i < n; i++)
1912                     ids[i] = in.readLong();
1913                 RemoteRepository parent = null;
1914 
1915                 // Load in reverse order so that parents are correct
1916                 for (int i = n - 1; i >= 0; i--) {
1917                     long id = ids[i];
1918                     RemoteRepository rr
1919                         = RemoteRepository.open(repoDir(id), id, parent);
1920                     repos.add(rr);
1921                     parent = rr;
1922                 }
1923                 Collections.reverse(repos);
1924 
1925             } finally {
1926                 in.close();
1927             }
1928 
1929         }
1930 
1931         private List<RemoteRepository> roRepos = null;
1932 
1933         // Unmodifiable
1934         public List<RemoteRepository> repositories() throws IOException {
1935             if (repos == null) {
1936                 load();
1937                 roRepos = Collections.unmodifiableList(repos);
1938             }
1939             return roRepos;
1940         }
1941 
1942         public RemoteRepository firstRepository() throws IOException {
1943             repositories();
1944             return repos.isEmpty() ? null : repos.get(0);
1945         }
1946 
1947         private void store() throws IOException {
1948             File newfn = new File(root, "list.new");
1949             FileOutputStream fout = new FileOutputStream(newfn);
1950             DataOutputStream out
1951                 = new DataOutputStream(new BufferedOutputStream(fout));
1952             try {
1953                 try {
1954                     fileHeader().write(out);
1955                     out.writeLong(nextRepoId);
1956                     out.writeInt(repos.size());
1957                     for (RemoteRepository rr : repos)
1958                         out.writeLong(rr.id());
1959                 } finally {
1960                     out.close();
1961                 }
1962             } catch (IOException x) {
1963                 newfn.delete();
1964                 throw x;
1965             }
1966             java.nio.file.Files.move(newfn.toPath(), listFile.toPath(), ATOMIC_MOVE);
1967         }
1968 
1969         public RemoteRepository add(URI u, int position)
1970             throws IOException
1971         {
1972 
1973             if (repos == null)
1974                 load();
1975             for (RemoteRepository rr : repos) {
1976                 if (rr.location().equals(u)) // ## u not canonical
1977                     throw new IllegalStateException(u + ": Already in"
1978                                                     + " repository list");
1979             }
1980             if (!root.exists()) {
1981                 if (!root.mkdir())
1982                     throw new IOException(root + ": Cannot create directory");
1983             }
1984 
1985             if (repos.size() == Integer.MAX_VALUE)
1986                 throw new IllegalStateException("Too many repositories");
1987             if (position < 0)
1988                 throw new IllegalArgumentException("Invalid index");
1989 
1990             long id = nextRepoId++;
1991             RemoteRepository rr = RemoteRepository.create(repoDir(id), u, id);
1992             try {
1993                 rr.updateCatalog(true);
1994             } catch (IOException x) {
1995                 rr.delete();
1996                 nextRepoId--;
1997                 throw x;
1998             }
1999 
2000             if (position >= repos.size()) {
2001                 repos.add(rr);
2002             } else if (position >= 0) {
2003                 List<RemoteRepository> prefix
2004                     = new ArrayList<>(repos.subList(0, position));
2005                 List<RemoteRepository> suffix
2006                     = new ArrayList<>(repos.subList(position, repos.size()));
2007                 repos.clear();
2008                 repos.addAll(prefix);
2009                 repos.add(rr);
2010                 repos.addAll(suffix);
2011             }
2012             store();
2013 
2014             return rr;
2015 
2016         }
2017 
2018         public boolean remove(RemoteRepository rr)
2019             throws IOException
2020         {
2021             if (!repos.remove(rr))
2022                 return false;
2023             store();
2024             File rd = repoDir(rr.id());
2025             for (File f : rd.listFiles()) {
2026                 if (!f.delete())
2027                     throw new IOException(f + ": Cannot delete");
2028             }
2029             if (!rd.delete())
2030                 throw new IOException(rd + ": Cannot delete");
2031             return true;
2032         }
2033 
2034         public boolean areCatalogsStale() throws IOException {
2035             for (RemoteRepository rr : repos) {
2036                 if (rr.isCatalogStale())
2037                     return true;
2038             }
2039             return false;
2040         }
2041 
2042         public boolean updateCatalogs(boolean force) throws IOException {
2043             boolean updated = false;
2044             for (RemoteRepository rr : repos) {
2045                 if (rr.updateCatalog(force))
2046                     updated = true;
2047             }
2048             return updated;
2049         }
2050 
2051     }
2052 
2053     private RemoteRepositoryList repoList = null;
2054 
2055     public RemoteRepositoryList repositoryList()
2056         throws IOException
2057     {
2058         if (repoList == null)
2059             repoList = new RepoList(root);
2060         return repoList;
2061     }
2062 
2063 }
--- EOF ---