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.file.*;
  32 import java.security.*;
  33 import java.security.cert.*;
  34 import java.util.*;
  35 import java.util.jar.*;
  36 import java.util.zip.*;
  37 
  38 import static java.nio.file.StandardCopyOption.*;
  39 
  40 /**
  41  * A simple module library which stores data directly in the filesystem
  42  *
  43  * @see Library
  44  */
  45 
  46 // ## TODO: Move remaining parent-searching logic upward into Library class
  47 
  48 // On-disk library layout
  49 //
  50 //   $LIB/%jigsaw-library
  51 //        com.foo.bar/1.2.3/info (= module-info.class)
  52 //                          index (list of defined classes)
  53 //                          config (resolved configuration, if a root)
  54 //                          classes/com/foo/bar/...
  55 //                          resources/com/foo/bar/...
  56 //                          lib/libbar.so
  57 //                          bin/bar
  58 //                          signer (signer's certchain & timestamp)
  59 //
  60 // ## Issue: Concurrent access to the module library
  61 // ## e.g. a module is being removed while a running application
  62 // ## is depending on it
  63 
  64 public final class SimpleLibrary
  65     extends Library
  66 {
  67 
  68     private static abstract class MetaData {
  69 
  70         protected final int maxMajorVersion;
  71         protected final int maxMinorVersion;
  72         protected int majorVersion;
  73         protected int minorVersion;
  74         private final FileConstants.Type type;
  75         private final File file;
  76 
  77         protected MetaData(int maxMajor, int maxMinor,
  78                            FileConstants.Type t, File f)
  79         {
  80             maxMajorVersion = majorVersion = maxMajor;
  81             maxMinorVersion = minorVersion = maxMinor;
  82             type = t;
  83             file = f;
  84         }
  85 
  86         protected abstract void storeRest(DataOutputStream out)
  87             throws IOException;
  88 
  89         void store() throws IOException {
  90             try (OutputStream fos = new FileOutputStream(file);
  91                  BufferedOutputStream bos = new BufferedOutputStream(fos);
  92                  DataOutputStream out = new DataOutputStream(bos)) {
  93                 out.writeInt(FileConstants.MAGIC);
  94                 out.writeShort(type.value());
  95                 out.writeShort(majorVersion);
  96                 out.writeShort(minorVersion);
  97                 storeRest(out);
  98             }
  99         }
 100 
 101         protected abstract void loadRest(DataInputStream in)
 102             throws IOException;
 103 
 104         protected void load() throws IOException {
 105             try (InputStream fis = new FileInputStream(file);
 106                  BufferedInputStream bis = new BufferedInputStream(fis);
 107                  DataInputStream in = new DataInputStream(bis)) {
 108                 if (in.readInt() != FileConstants.MAGIC)
 109                     throw new IOException(file + ": Invalid magic number");
 110                 if (in.readShort() != type.value())
 111                     throw new IOException(file + ": Invalid file type");
 112                 int maj = in.readShort();
 113                 int min = in.readShort();
 114                 if (   maj > maxMajorVersion
 115                     || (maj == maxMajorVersion && min > maxMinorVersion)) {
 116                     throw new IOException(file
 117                                           + ": Futuristic version number");
 118                 }
 119                 majorVersion = maj;
 120                 minorVersion = min;
 121                 loadRest(in);
 122             } catch (EOFException x) {
 123                 throw new IOException(file + ": Invalid library metadata", x);
 124             }
 125         }
 126     }
 127 
 128     /**
 129      * Defines the storage options that SimpleLibrary supports.
 130      */
 131     public static enum StorageOption {
 132         DEFLATED,
 133     }
 134 
 135     private static final class Header
 136         extends MetaData
 137     {
 138         private static final String FILE
 139             = FileConstants.META_PREFIX + "jigsaw-library";
 140 
 141         private static final int MAJOR_VERSION = 0;
 142         private static final int MINOR_VERSION = 1;
 143 
 144         private static final int DEFLATED = 1 << 0;
 145 
 146         private File parent;
 147         // location of native libs for this library (may be outside the library)
 148         // null:default, to use a per-module 'lib' directory
 149         private File natlibs;
 150         // location of native cmds for this library (may be outside the library)
 151         // null:default, to use a per-module 'bin' directory
 152         private File natcmds;
 153         // location of config files for this library (may be outside the library)
 154         // null:default, to use a per-module 'etc' directory
 155         private File configs;
 156         private Set<StorageOption> opts;
 157 
 158         public File parent()  { return parent;  }
 159         public File natlibs() { return natlibs; }
 160         public File natcmds() { return natcmds; }
 161         public File configs() { return configs; }
 162         public boolean isDeflated() {
 163             return opts.contains(StorageOption.DEFLATED);
 164         }
 165 
 166         private Header(File root) {
 167             super(MAJOR_VERSION, MINOR_VERSION,
 168                   FileConstants.Type.LIBRARY_HEADER,
 169                   new File(root, FILE));
 170         }
 171 
 172         private Header(File root, File parent, File natlibs, File natcmds,
 173                        File configs, Set<StorageOption> opts) {
 174             this(root);
 175             this.parent = parent;
 176             this.natlibs = natlibs;
 177             this.natcmds = natcmds;
 178             this.configs = configs;
 179             this.opts = new HashSet<>(opts);
 180         }
 181 
 182         private void storePath(File p, DataOutputStream out) throws IOException {  
 183             if (p != null) {
 184                 out.writeByte(1);
 185                 out.writeUTF(Files.convertSeparator(p.toString()));
 186             } else {
 187                 out.write(0);
 188             }
 189         }
 190 
 191         protected void storeRest(DataOutputStream out) throws IOException {
 192             int flags = 0;
 193             if (isDeflated())
 194                 flags |= DEFLATED;
 195             out.writeShort(flags);
 196             
 197             storePath(parent, out);
 198             storePath(natlibs, out);
 199             storePath(natcmds, out);
 200             storePath(configs, out);
 201         }
 202         
 203         private File loadPath(DataInputStream in) throws IOException {  
 204             if (in.readByte() != 0)
 205                 return new File(Files.platformSeparator(in.readUTF()));
 206             return null;
 207         }
 208 
 209         protected void loadRest(DataInputStream in) throws IOException {
 210             opts = new HashSet<StorageOption>();
 211             int flags = in.readShort();
 212             if ((flags & DEFLATED) == DEFLATED)
 213                 opts.add(StorageOption.DEFLATED);
 214             parent = loadPath(in);
 215             natlibs = loadPath(in);
 216             natcmds = loadPath(in);
 217             configs = loadPath(in);
 218         }
 219 
 220         private static Header load(File f) throws IOException {
 221             Header h = new Header(f);
 222             h.load();
 223             return h;
 224         }
 225     }
 226 
 227     private final File root;
 228     private final File canonicalRoot;
 229     private final File parentPath;
 230     private final File natlibs;
 231     private final File natcmds;
 232     private final File configs;
 233     private final SimpleLibrary parent;
 234     private final Header hd;
 235 
 236     public String name() { return root.toString(); }
 237     public File root() { return canonicalRoot; }
 238     public int majorVersion() { return hd.majorVersion; }
 239     public int minorVersion() { return hd.minorVersion; }
 240     public SimpleLibrary parent() { return parent; }
 241     public File natlibs() { return natlibs; }
 242     public File natcmds() { return natcmds; }
 243     public File configs() { return configs; }
 244     public boolean isDeflated() { return hd.isDeflated(); }
 245 
 246     private URI location = null;
 247     public URI location() {
 248         if (location == null)
 249             location = root().toURI();
 250         return location;
 251     }
 252 
 253     @Override
 254     public String toString() {
 255         return (this.getClass().getName()
 256                 + "[" + canonicalRoot
 257                 + ", v" + hd.majorVersion + "." + hd.minorVersion + "]");
 258     }
 259 
 260 
 261     private static File resolveAndEnsurePath(File path) throws IOException {
 262         if (path == null) { return null; }
 263         
 264         File p = path.getCanonicalFile();
 265         if (!p.exists()) {
 266             Files.mkdirs(p, p.toString());
 267         } else {
 268             Files.ensureIsDirectory(p);
 269             Files.ensureWriteable(p);
 270         }
 271         return p;
 272     }
 273 
 274     private File relativize(File path) throws IOException {
 275         if (path == null) { return null; }
 276         // Return the path relative to the canonical root
 277         return (canonicalRoot.toPath().relativize(path.toPath().toRealPath())).toFile();
 278     }
 279 
 280     // Opens an existing library
 281     private SimpleLibrary(File path) throws IOException {
 282         root = path;
 283         canonicalRoot = root.getCanonicalFile();
 284         Files.ensureIsDirectory(root);
 285         hd = Header.load(root);
 286         
 287         parentPath = hd.parent();
 288         parent = parentPath != null ? open(parentPath) : null;
 289 
 290         natlibs = hd.natlibs() == null ? null :
 291             new File(canonicalRoot, hd.natlibs().toString()).getCanonicalFile();
 292         natcmds = hd.natcmds() == null ? null :
 293             new File(canonicalRoot, hd.natcmds().toString()).getCanonicalFile();
 294         configs = hd.configs() == null ? null :
 295             new File(canonicalRoot, hd.configs().toString()).getCanonicalFile();
 296     }
 297 
 298     // Creates a new library
 299     private SimpleLibrary(File path, File parentPath, File natlibs, File natcmds,
 300                           File configs, Set<StorageOption> opts)
 301         throws IOException
 302     {
 303         root = path;
 304         canonicalRoot = root.getCanonicalFile();
 305         if (root.exists()) {
 306             Files.ensureIsDirectory(root);
 307             if (root.list().length != 0)
 308                 throw new IOException(root + ": Already Exists");
 309             Files.ensureWriteable(root);
 310         } else
 311             Files.mkdirs(root, root.toString());
 312 
 313         this.parent = parentPath != null ? open(parentPath) : null;
 314         this.parentPath = parentPath != null ? this.parent.root() : null;
 315 
 316         this.natlibs = resolveAndEnsurePath(natlibs);
 317         this.natcmds = resolveAndEnsurePath(natcmds);
 318         this.configs = resolveAndEnsurePath(configs);
 319 
 320         hd = new Header(canonicalRoot, this.parentPath, relativize(this.natlibs),
 321                         relativize(this.natcmds), relativize(this.configs), opts);
 322         hd.store();
 323     }
 324 
 325     public static SimpleLibrary create(File path, File parent, File natlibs,
 326                                        File natcmds, File configs,
 327                                        Set<StorageOption> opts)
 328         throws IOException
 329     {
 330         return new SimpleLibrary(path, parent, natlibs, natcmds, configs, opts);
 331     }
 332 
 333     public static SimpleLibrary create(File path, File parent, Set<StorageOption> opts)
 334         throws IOException
 335     {
 336         return new SimpleLibrary(path, parent, null, null, null, opts);
 337     }
 338 
 339     public static SimpleLibrary create(File path, File parent)
 340         throws IOException
 341     {
 342         return SimpleLibrary.create(path, parent, Collections.<StorageOption>emptySet());
 343     }
 344 
 345     public static SimpleLibrary create(File path, Set<StorageOption> opts)
 346         throws IOException
 347     {
 348         // ## Should default parent to $JAVA_HOME/lib/modules
 349         return SimpleLibrary.create(path, null, opts);
 350     }
 351 
 352     public static SimpleLibrary open(File path)
 353         throws IOException
 354     {
 355         return new SimpleLibrary(path);
 356     }
 357 
 358     private static final JigsawModuleSystem jms
 359         = JigsawModuleSystem.instance();
 360 
 361     private static final class Index
 362         extends MetaData
 363     {
 364 
 365         private static String FILE = "index";
 366 
 367         private static int MAJOR_VERSION = 0;
 368         private static int MINOR_VERSION = 1;
 369 
 370         private Set<String> publicClasses;
 371         public Set<String> publicClasses() { return publicClasses; }
 372 
 373         private Set<String> otherClasses;
 374         public Set<String> otherClasses() { return otherClasses; }
 375 
 376         private Index(File root) {
 377             super(MAJOR_VERSION, MINOR_VERSION,
 378                   FileConstants.Type.LIBRARY_MODULE_INDEX,
 379                   new File(root, FILE));
 380             // Unsorted on input, because we don't need it sorted
 381             publicClasses = new HashSet<String>();
 382             otherClasses = new HashSet<String>();
 383         }
 384 
 385         private void storeSet(Set<String> cnset, DataOutputStream out)
 386             throws IOException
 387         {
 388             // Sorted on output, because we can afford it
 389             List<String> cns = new ArrayList<String>(cnset);
 390             Collections.sort(cns);
 391             out.writeInt(cns.size());
 392             for (String cn : cns)
 393                 out.writeUTF(cn);
 394         }
 395 
 396         protected void storeRest(DataOutputStream out)
 397             throws IOException
 398         {
 399             storeSet(publicClasses, out);
 400             storeSet(otherClasses, out);
 401         }
 402 
 403         private void loadSet(DataInputStream in, Set<String> cnset)
 404             throws IOException
 405         {
 406             int n = in.readInt();
 407             for (int i = 0; i < n; i++)
 408                 cnset.add(in.readUTF());
 409         }
 410 
 411         protected void loadRest(DataInputStream in)
 412             throws IOException
 413         {
 414             loadSet(in, publicClasses);
 415             loadSet(in, otherClasses);
 416         }
 417 
 418         private static Index load(File f)
 419             throws IOException
 420         {
 421             Index ix = new Index(f);
 422             ix.load();
 423             return ix;
 424         }
 425 
 426     }
 427 
 428     private static final class StoredConfiguration
 429         extends MetaData
 430     {
 431 
 432         private static String FILE = "config";
 433 
 434         private static int MAJOR_VERSION = 0;
 435         private static int MINOR_VERSION = 1;
 436 
 437         private Configuration<Context> cf;
 438 
 439         private static void delete(File root) {
 440             new File(root, FILE).delete();
 441         }
 442 
 443         private StoredConfiguration(File root, Configuration<Context> conf)
 444         {
 445             super(MAJOR_VERSION, MINOR_VERSION,
 446                   FileConstants.Type.LIBRARY_MODULE_CONFIG,
 447                   new File(root, FILE));
 448             cf = conf;
 449         }
 450 
 451         protected void storeRest(DataOutputStream out)
 452             throws IOException
 453         {
 454             // Roots
 455             out.writeInt(cf.roots().size());
 456             for (ModuleId mid : cf.roots()) {
 457                 out.writeUTF(mid.toString());
 458             }
 459             // Contexts
 460             out.writeInt(cf.contexts().size());
 461             for (Context cx : cf.contexts()) {
 462                 out.writeUTF(cx.name());
 463                 // Module ids, and their libraries
 464                 out.writeInt(cx.modules().size());
 465                 for (ModuleId mid : cx.modules()) {
 466                     out.writeUTF(mid.toString());
 467                     File lp = cx.findLibraryPathForModule(mid);
 468                     if (lp == null)
 469                         out.writeUTF("");
 470                     else
 471                         out.writeUTF(lp.toString());
 472 
 473                     // Module views
 474                     out.writeInt(cx.views(mid).size());
 475                     for (ModuleId id : cx.views(mid)) {
 476                         out.writeUTF(id.toString());
 477                     }
 478                 }
 479 
 480                 // Local class map
 481                 out.writeInt(cx.localClasses().size());
 482                 for (Map.Entry<String,ModuleId> me
 483                          : cx.moduleForLocalClassMap().entrySet()) {
 484                     out.writeUTF(me.getKey());
 485                     out.writeUTF(me.getValue().toString());
 486                 }
 487 
 488                 // Remote package map
 489                 out.writeInt(cx.contextForRemotePackageMap().size());
 490                 for (Map.Entry<String,String> me
 491                          : cx.contextForRemotePackageMap().entrySet()) {
 492                     out.writeUTF(me.getKey());
 493                     out.writeUTF(me.getValue());
 494                 }
 495 
 496                 // Suppliers
 497                 out.writeInt(cx.remoteContexts().size());
 498                 for (String cxn : cx.remoteContexts()) {
 499                     out.writeUTF(cxn);
 500                 }
 501 
 502             }
 503         }
 504 
 505         protected void loadRest(DataInputStream in)
 506             throws IOException
 507         {
 508             // Roots
 509             int nRoots = in.readInt();
 510             List<ModuleId> roots = new ArrayList<>();
 511             for (int i = 0; i < nRoots; i++) {
 512                 String root = in.readUTF();
 513                 ModuleId rmid = jms.parseModuleId(root);
 514                 roots.add(rmid);
 515             }
 516             cf = new Configuration<Context>(roots);
 517             // Contexts
 518             int nContexts = in.readInt();
 519             for (int i = 0; i < nContexts; i++) {
 520                 Context cx = new Context();
 521                 String cxn = in.readUTF();
 522                 // Module ids
 523                 int nModules = in.readInt();
 524                 for (int j = 0; j < nModules; j++) {
 525                     ModuleId mid = jms.parseModuleId(in.readUTF());
 526                     String lps = in.readUTF();
 527                     if (lps.length() > 0)
 528                         cx.putLibraryPathForModule(mid, new File(lps));
 529                     // Module Views
 530                     int nViews = in.readInt();
 531                     Set<ModuleId> views = new HashSet<>();
 532                     for (int k = 0; k < nViews; k++) {
 533                         ModuleId id = jms.parseModuleId(in.readUTF());
 534                         views.add(id);
 535                         cf.put(id.name(), cx);
 536                     }
 537                     cx.add(mid, views);
 538                 }
 539                 cx.freeze();
 540                 assert cx.name().equals(cxn);
 541                 cf.add(cx);
 542                 // Local class map
 543                 int nClasses = in.readInt();
 544                 for (int j = 0; j < nClasses; j++)
 545                     cx.putModuleForLocalClass(in.readUTF(),
 546                                               jms.parseModuleId(in.readUTF()));
 547                 // Remote package map
 548                 int nPackages = in.readInt();
 549                 for (int j = 0; j < nPackages; j++)
 550                     cx.putContextForRemotePackage(in.readUTF(), in.readUTF());
 551 
 552                 // Suppliers
 553                 int nSuppliers = in.readInt();
 554                 for (int j = 0; j < nSuppliers; j++)
 555                     cx.addSupplier(in.readUTF());
 556             }
 557 
 558         }
 559 
 560         private static StoredConfiguration load(File f)
 561             throws IOException
 562         {
 563             StoredConfiguration sp = new StoredConfiguration(f, null);
 564             sp.load();
 565             return sp;
 566         }
 567 
 568     }
 569 
 570     private static final class Signers
 571         extends MetaData {
 572 
 573         private static String FILE = "signer";
 574         private static int MAJOR_VERSION = 0;
 575         private static int MINOR_VERSION = 1;
 576 
 577         private CertificateFactory cf = null;
 578         private Set<CodeSigner> signers;
 579         private Set<CodeSigner> signers() { return signers; }
 580 
 581         private Signers(File root, Set<CodeSigner> signers) {
 582             super(MAJOR_VERSION, MINOR_VERSION,
 583                   FileConstants.Type.LIBRARY_MODULE_SIGNER,
 584                   new File(root, FILE));
 585             this.signers = signers;
 586         }
 587 
 588         protected void storeRest(DataOutputStream out)
 589             throws IOException
 590         {
 591             out.writeInt(signers.size());
 592             for (CodeSigner signer : signers) {
 593                 try {
 594                     CertPath signerCertPath = signer.getSignerCertPath();
 595                     out.write(signerCertPath.getEncoded("PkiPath"));
 596                     Timestamp ts = signer.getTimestamp();
 597                     out.writeByte((ts != null) ? 1 : 0);
 598                     if (ts != null) {
 599                         out.writeLong(ts.getTimestamp().getTime());
 600                         out.write(ts.getSignerCertPath().getEncoded("PkiPath"));
 601                     }
 602                 } catch (CertificateEncodingException cee) {
 603                     throw new IOException(cee);
 604                 }
 605             }
 606         }
 607 
 608         protected void loadRest(DataInputStream in)
 609             throws IOException
 610         {
 611             int size = in.readInt();
 612             for (int i = 0; i < size; i++) {
 613                 try {
 614                     if (cf == null)
 615                         cf = CertificateFactory.getInstance("X.509");
 616                     CertPath signerCertPath = cf.generateCertPath(in, "PkiPath");
 617                     int b = in.readByte();
 618                     if (b != 0) {
 619                         Date timestamp = new Date(in.readLong());
 620                         CertPath tsaCertPath = cf.generateCertPath(in, "PkiPath");
 621                         Timestamp ts = new Timestamp(timestamp, tsaCertPath);
 622                         signers.add(new CodeSigner(signerCertPath, ts));
 623                     } else {
 624                         signers.add(new CodeSigner(signerCertPath, null));
 625                     }
 626                 } catch (CertificateException ce) {
 627                     throw new IOException(ce);
 628                 }
 629             }
 630         }
 631 
 632         private static Signers load(File f)
 633             throws IOException
 634         {
 635             Signers signers = new Signers(f, new HashSet<CodeSigner>());
 636             signers.load();
 637             return signers;
 638         }
 639     }
 640 
 641     private void gatherLocalModuleIds(File mnd, Set<ModuleId> mids)
 642         throws IOException
 643     {
 644         if (!mnd.isDirectory())
 645             throw new IOException(mnd + ": Not a directory");
 646         if (!mnd.canRead())
 647             throw new IOException(mnd + ": Not readable");
 648         for (String v : mnd.list()) {
 649             mids.add(jms.parseModuleId(mnd.getName(), v));
 650         }
 651     }
 652 
 653     private void gatherLocalModuleIds(Set<ModuleId> mids)
 654         throws IOException
 655     {
 656         File[] mnds = root.listFiles();
 657         for (File mnd : mnds) {
 658             if (mnd.getName().startsWith(FileConstants.META_PREFIX))
 659                 continue;
 660             gatherLocalModuleIds(mnd, mids);
 661         }
 662     }
 663 
 664     protected void gatherLocalModuleIds(String moduleName,
 665                                         Set<ModuleId> mids)
 666         throws IOException
 667     {
 668         if (moduleName == null) {
 669             gatherLocalModuleIds(mids);
 670             return;
 671         }
 672         File mnd = new File(root, moduleName);
 673         if (mnd.exists())
 674             gatherLocalModuleIds(mnd, mids);
 675     }
 676 
 677     private void checkModuleId(ModuleId mid) {
 678         Version v = mid.version();
 679         if (v == null)
 680             return;
 681         if (!(v instanceof JigsawVersion))
 682             throw new IllegalArgumentException(mid + ": Not a Jigsaw module id");
 683     }
 684         
 685     private File moduleDir(File root, ModuleId mid) {
 686         Version v = mid.version();
 687         String vs = (v != null) ? v.toString() : "default";
 688         return new File(new File(root, mid.name()), vs);
 689     }
 690 
 691     private void checkModuleDir(File md)
 692         throws IOException
 693     {
 694         if (!md.isDirectory())
 695             throw new IOException(md + ": Not a directory");
 696         if (!md.canRead())
 697             throw new IOException(md + ": Not readable");
 698     }
 699 
 700     private File findModuleDir(ModuleId mid)
 701         throws IOException
 702     {
 703         checkModuleId(mid);
 704         File md = moduleDir(root, mid);
 705         if (!md.exists())
 706             return null;
 707         checkModuleDir(md);
 708                 
 709         // mid may be a view or alias of a module
 710         byte[] mib = Files.load(new File(md, "info"));
 711         ModuleInfo mi = jms.parseModuleInfo(mib);
 712         if (!mid.equals(mi.id())) {
 713             md = moduleDir(root, mi.id());
 714             if (!md.exists())
 715                 throw new IOException(mid + ": " + md + " does not exist");
 716             checkModuleDir(md);
 717         }
 718         return md;
 719     }
 720 
 721     private File makeModuleDir(File root, ModuleInfo mi)
 722         throws ConfigurationException, IOException
 723     {
 724         // view name is unique
 725         for (ModuleView mv : mi.views()) {
 726             File md = moduleDir(root, mv.id());
 727             if (md.exists()) {
 728                 throw new ConfigurationException("module view " +
 729                     mv.id() + " already installed");
 730             }
 731             if (!md.mkdirs()) {
 732                 throw new IOException(md + ": Cannot create");
 733             }
 734         }
 735 
 736         return moduleDir(root, mi.id());
 737     }
 738     
 739     private void deleteModuleDir(File root, ModuleInfo mi)
 740         throws IOException
 741     {
 742         // delete the default view and the module content
 743         ModuleId mid = mi.defaultView().id();
 744         File md = moduleDir(root, mid);
 745         if (md.exists())
 746             ModuleFile.Reader.remove(md);
 747         // delete all views
 748         for (ModuleView mv : mi.views()) {
 749             md = moduleDir(root, mv.id());
 750             if (md.exists()) {
 751                 Files.deleteTree(md);
 752             }
 753         }
 754     }
 755 
 756     private void deleteModuleDir(ModuleId mid)
 757         throws IOException
 758     {
 759         checkModuleId(mid);
 760         File md = moduleDir(root, mid);
 761         if (!md.exists())
 762             return;
 763         checkModuleDir(md);
 764 
 765         // mid may be a view or alias of a module
 766         byte[] mib = Files.load(new File(md, "info"));
 767         ModuleInfo mi = jms.parseModuleInfo(mib);
 768         if (!mid.equals(mi.id())) {
 769             throw new IOException(mi.id() + " found in the module directory for " + mid);
 770         }
 771         deleteModuleDir(root, mi);
 772     }
 773 
 774     private void copyModuleInfo(File root, ModuleInfo mi, byte[] mib)
 775         throws IOException
 776     {
 777         for (ModuleView mv : mi.views()) {
 778             if (mv.id().equals(mi.id())) {
 779                 continue;
 780             }
 781 
 782             File mvd = moduleDir(root, mv.id());
 783             Files.store(mib, new File(mvd, "info"));
 784         }
 785     }
 786     public byte[] readLocalModuleInfoBytes(ModuleId mid)
 787         throws IOException
 788     {
 789         File md = findModuleDir(mid);
 790         if (md == null)
 791             return null;
 792         return Files.load(new File(md, "info"));
 793     }
 794 
 795     public CodeSigner[] readLocalCodeSigners(ModuleId mid)
 796         throws IOException
 797     {
 798         File md = findModuleDir(mid);
 799         if (md == null)
 800             return null;
 801         // Only one signer is currently supported
 802         File f = new File(md, "signer");
 803         // ## concurrency issues : what is the expected behavior if file is
 804         // ## removed by another thread/process here?
 805         if (!f.exists())
 806             return null;
 807         return Signers.load(md).signers().toArray(new CodeSigner[0]);
 808     }
 809 
 810     // ## Close all zip files when we close this library
 811     private Map<ModuleId, Object> contentForModule = new HashMap<>();
 812     private Object NONE = new Object();
 813 
 814     private Object findContent(ModuleId mid)
 815         throws IOException
 816     {
 817         Object o = contentForModule.get(mid);
 818         if (o != null)
 819             return o;
 820         if (o == NONE)
 821             return null;
 822         File md = findModuleDir(mid);
 823         if (md == null) {
 824             contentForModule.put(mid, NONE);
 825             return null;
 826         }
 827         File cf = new File(md, "classes");
 828         if (cf.isFile()) {
 829             ZipFile zf = new ZipFile(cf);
 830             contentForModule.put(mid, zf);
 831             return zf;
 832         }
 833         if (cf.isDirectory()) {
 834             contentForModule.put(mid, cf);
 835             return cf;
 836         }
 837         contentForModule.put(mid, NONE);
 838         return null;
 839     }
 840 
 841     private byte[] loadContent(ZipFile zf, String path)
 842         throws IOException
 843     {
 844         ZipEntry ze = zf.getEntry(path);
 845         if (ze == null)
 846             return null;
 847         return Files.load(zf.getInputStream(ze), (int)ze.getSize());
 848     }
 849 
 850     private byte[] loadContent(ModuleId mid, String path)
 851         throws IOException
 852     {
 853         Object o = findContent(mid);
 854         if (o == null)
 855             return null;
 856         if (o instanceof ZipFile) {
 857             ZipFile zf = (ZipFile)o;
 858             ZipEntry ze = zf.getEntry(path);
 859             if (ze == null)
 860                 return null;
 861             return Files.load(zf.getInputStream(ze), (int)ze.getSize());
 862         }
 863         if (o instanceof File) {
 864             File f = new File((File)o, path);
 865             if (!f.exists())
 866                 return null;
 867             return Files.load(f);
 868         }
 869         assert false;
 870         return null;
 871     }
 872 
 873     private URI locateContent(ModuleId mid, String path)
 874         throws IOException
 875     {
 876         Object o = findContent(mid);
 877         if (o == null)
 878             return null;
 879         if (o instanceof ZipFile) {
 880             ZipFile zf = (ZipFile)o;
 881             ZipEntry ze = zf.getEntry(path);
 882             if (ze == null)
 883                 return null;
 884             return URI.create("jar:"
 885                               + new File(zf.getName()).toURI().toString()
 886                               + "!/" + path);
 887         }
 888         if (o instanceof File) {
 889             File f = new File((File)o, path);
 890             if (!f.exists())
 891                 return null;
 892             return f.toURI();
 893         }
 894         assert false;
 895         return null;
 896     }
 897 
 898     public byte[] readLocalClass(ModuleId mid, String className)
 899         throws IOException
 900     {
 901         return loadContent(mid, className.replace('.', '/') + ".class");
 902     }
 903 
 904     public List<String> listLocalClasses(ModuleId mid, boolean all)
 905         throws IOException
 906     {
 907         File md = findModuleDir(mid);
 908         if (md == null)
 909             return null;
 910         Index ix = Index.load(md);
 911         int os = all ? ix.otherClasses().size() : 0;
 912         ArrayList<String> cns
 913             = new ArrayList<String>(ix.publicClasses().size() + os);
 914         cns.addAll(ix.publicClasses());
 915         if (all)
 916             cns.addAll(ix.otherClasses());
 917         return cns;
 918     }
 919 
 920     public Configuration<Context> readConfiguration(ModuleId mid)
 921         throws IOException
 922     {
 923         File md = findModuleDir(mid);
 924         if (md == null) {
 925             if (parent != null)
 926                 return parent.readConfiguration(mid);
 927             return null;
 928         }
 929         StoredConfiguration scf = StoredConfiguration.load(md);
 930         return scf.cf;
 931     }
 932 
 933     private boolean addToIndex(ClassInfo ci, Index ix)
 934         throws IOException
 935     {
 936         if (ci.isModuleInfo())
 937             return false;
 938         if (ci.moduleName() != null) {
 939             // ## From early Jigsaw development; can probably delete now
 940             throw new IOException("Old-style class file with"
 941                                   + " module attribute");
 942         }
 943         if (ci.isPublic())
 944             ix.publicClasses().add(ci.name());
 945         else
 946             ix.otherClasses().add(ci.name());
 947         return true;
 948     }
 949 
 950     private void reIndex(ModuleId mid)
 951         throws IOException
 952     {
 953 
 954         File md = findModuleDir(mid);
 955         if (md == null)
 956             throw new IllegalArgumentException(mid + ": No such module");
 957         File cd = new File(md, "classes");
 958         final Index ix = new Index(md);
 959 
 960         if (cd.isDirectory()) {
 961             Files.walkTree(cd, new Files.Visitor<File>() {
 962                 public void accept(File f) throws IOException {
 963                     if (f.getPath().endsWith(".class"))
 964                         addToIndex(ClassInfo.read(f), ix);
 965                 }
 966             });
 967         } else if (cd.isFile()) {
 968             FileInputStream fis = new FileInputStream(cd);
 969             ZipInputStream zis = new ZipInputStream(fis);
 970             ZipEntry ze;
 971             while ((ze = zis.getNextEntry()) != null) {
 972                 if (!ze.getName().endsWith(".class"))
 973                     continue;
 974                 addToIndex(ClassInfo.read(Files.nonClosingStream(zis),
 975                                           ze.getSize(),
 976                                           mid + ":" + ze.getName()),
 977                            ix);
 978             }
 979         }
 980 
 981         ix.store();
 982     }
 983 
 984     /**
 985      * Strip the debug attributes from the classes in a given module
 986      * directory.
 987      */
 988     private void strip(File md) throws IOException {
 989         File classes = new File(md, "classes");
 990         if (classes.isFile()) {
 991             File pf = new File(md, "classes.pack");
 992             try (JarFile jf = new JarFile(classes);
 993                 FileOutputStream out = new FileOutputStream(pf))
 994             {
 995                 Pack200.Packer packer = Pack200.newPacker();
 996                 Map<String,String> p = packer.properties();
 997                 p.put("com.sun.java.util.jar.pack.strip.debug", Pack200.Packer.TRUE);
 998                 packer.pack(jf, out);
 999             }
1000 
1001             try (OutputStream out = new FileOutputStream(classes);
1002                  JarOutputStream jos = new JarOutputStream(out))
1003             {
1004                 Pack200.Unpacker unpacker = Pack200.newUnpacker();
1005                 unpacker.unpack(pf, jos);
1006             } finally {
1007                 pf.delete();
1008            }
1009         }
1010     }
1011 
1012     private void install(Manifest mf, File dst, boolean strip)
1013         throws IOException
1014     {
1015         if (mf.classes().size() > 1)
1016             throw new IllegalArgumentException("Multiple module-class"
1017                                                + " directories"
1018                                                + " not yet supported");
1019         if (mf.classes().size() < 1)
1020             throw new IllegalArgumentException("At least one module-class"
1021                                                + " directory required");
1022         File classes = mf.classes().get(0);
1023         final String mn = mf.module();
1024 
1025         File mif = new File(classes, "module-info.class");
1026         File src = null;
1027         if (mif.exists()) {
1028             src = classes;
1029         } else {
1030             src = new File(classes, mn);
1031             mif = new File(src, "module-info.class");
1032         }
1033         byte[] bs =  Files.load(mif);
1034         ModuleInfo mi = jms.parseModuleInfo(bs);
1035         if (!mi.id().name().equals(mn)) {
1036             // ## Need a more appropriate throwable here
1037             throw new Error(mif + " is for module " + mi.id().name()
1038                             + ", not " + mn);
1039         }
1040         String m = mi.id().name();
1041         JigsawVersion v = (JigsawVersion)mi.id().version();
1042         String vs = (v == null) ? "default" : v.toString();
1043         deleteModuleDir(dst, mi);
1044 
1045          // view name is unique
1046         for (ModuleView mv : mi.views()) {
1047             File md = moduleDir(dst, mv.id());
1048             if (!md.mkdirs()) {
1049                 throw new IOException(md + ": Cannot create");
1050             }
1051         }
1052 
1053         File mdst = moduleDir(dst, mi.id());
1054         Files.store(bs, new File(mdst, "info"));
1055         File cldst = new File(mdst, "classes");
1056 
1057         // Delete the config file, if one exists
1058         StoredConfiguration.delete(mdst);
1059 
1060         if (false) {
1061 
1062             // ## Retained for now in case we later want to add an option
1063             // ## to install into a tree rather than a zip file
1064 
1065             // Copy class files and build index
1066             final Index ix = new Index(mdst);
1067             Files.copyTree(src, cldst, new Files.Filter<File>() {
1068                     public boolean accept(File f) throws IOException {
1069                         if (f.isDirectory())
1070                             return true;
1071                         if (f.getName().endsWith(".class")) {
1072                             return addToIndex(ClassInfo.read(f), ix);
1073                         } else {
1074                             return true;
1075                         }
1076                     }});
1077             ix.store();
1078         } else {
1079             try (FileOutputStream fos = new FileOutputStream(new File(mdst, "classes"));
1080                  JarOutputStream jos = new JarOutputStream(new BufferedOutputStream(fos)))
1081             {
1082                 // Copy class files and build index
1083                 final Index ix = new Index(mdst);
1084                 Files.storeTree(src, jos, isDeflated(), new Files.Filter<File>() {
1085                         public boolean accept(File f) throws IOException {
1086                             if (f.isDirectory())
1087                                 return true;
1088                             if (f.getName().endsWith(".class")) {
1089                                 return addToIndex(ClassInfo.read(f), ix);
1090                             } else {
1091                                 return true;
1092                             }
1093                         }});
1094                 ix.store();
1095                 copyModuleInfo(dst, mi, bs);
1096             }
1097             if (strip)
1098                 strip(mdst);
1099         }
1100 
1101     }
1102 
1103     private void install(Collection<Manifest> mfs, File dst, boolean strip)
1104         throws IOException
1105     {
1106         for (Manifest mf : mfs)
1107             install(mf, dst, strip);
1108     }
1109 
1110     public void installFromManifests(Collection<Manifest> mfs, boolean strip)
1111         throws ConfigurationException, IOException
1112     {
1113         install(mfs, root, strip);
1114         configure(null);
1115     }
1116 
1117     @Override
1118     public void installFromManifests(Collection<Manifest> mfs)
1119         throws ConfigurationException, IOException
1120     {
1121         installFromManifests(mfs, false);
1122     }
1123 
1124     private ModuleFileVerifier.Parameters mfvParams;
1125     private ModuleId install(InputStream is, boolean verifySignature, boolean strip)
1126         throws ConfigurationException, IOException, SignatureException
1127     {
1128         BufferedInputStream bin = new BufferedInputStream(is);
1129         DataInputStream in = new DataInputStream(bin);
1130         ModuleInfo mi = null;
1131         try (ModuleFile.Reader mr = new ModuleFile.Reader(in)) {
1132             byte[] mib = mr.readStart();
1133             mi = jms.parseModuleInfo(mib);        
1134             File md = makeModuleDir(root, mi);
1135             if (verifySignature && mr.hasSignature()) {
1136                 ModuleFileVerifier mfv = new SignedModule.PKCS7Verifier(mr);
1137                 if (mfvParams == null) {
1138                     mfvParams = new SignedModule.VerifierParameters();
1139                 }
1140                 // Verify the module signature and validate the signer's
1141                 // certificate chain
1142                 Set<CodeSigner> signers = mfv.verifySignature(mfvParams);
1143 
1144                 // Verify the module header hash and the module info hash
1145                 mfv.verifyHashesStart(mfvParams);
1146 
1147                 // ## Check policy - is signer trusted and what permissions
1148                 // ## should be granted?
1149 
1150                 // Store signer info
1151                 new Signers(md, signers).store();
1152 
1153                 // Read and verify the rest of the hashes
1154                 mr.readRest(md, isDeflated(), natlibs(), natcmds(), configs());
1155                 mfv.verifyHashesRest(mfvParams);
1156             } else {
1157                 mr.readRest(md, isDeflated(), natlibs(), natcmds(), configs());
1158             }
1159 
1160             if (strip)
1161                 strip(md);
1162             reIndex(mi.id());         // ## Could do this while reading module file
1163 
1164             // copy module-info.class to each view
1165             copyModuleInfo(root, mi, mib);
1166             return mi.id();
1167 
1168         } catch (IOException | SignatureException x) {
1169             if (mi != null) {
1170                 try {
1171                     deleteModuleDir(root, mi);
1172                 } catch (IOException y) {
1173                     y.initCause(x);
1174                     throw y;
1175                 }
1176             }
1177             throw x;
1178         }
1179     }
1180 
1181     private ModuleId installFromJarFile(File mf, boolean verifySignature, boolean strip)
1182         throws ConfigurationException, IOException, SignatureException
1183     {
1184         ModuleInfo mi = null;
1185         try (JarFile jf = new JarFile(mf, verifySignature)) {
1186             mi = jf.getModuleInfo();
1187             if (mi == null)
1188                 throw new ConfigurationException(mf + ": not a modular JAR file");
1189 
1190             File md = makeModuleDir(root, mi);
1191             ModuleId mid = mi.id();
1192 
1193             boolean signed = false;
1194 
1195             // copy the jar file to the module library
1196             File classesDir = new File(md, "classes");
1197             try (FileOutputStream fos = new FileOutputStream(classesDir);
1198                  BufferedOutputStream bos = new BufferedOutputStream(fos);
1199                  JarOutputStream jos = new JarOutputStream(bos)) {
1200                 jos.setLevel(0);
1201 
1202                 Enumeration<JarEntry> entries = jf.entries();
1203                 while (entries.hasMoreElements()) {
1204                     JarEntry je = entries.nextElement();
1205                     try (InputStream is = jf.getInputStream(je)) {
1206                         if (je.getName().equals(JarFile.MODULEINFO_NAME)) {                            
1207                             java.nio.file.Files.copy(is, md.toPath().resolve("info"));
1208                         } else {
1209                             writeJarEntry(is, je, jos);
1210                         }
1211                     }
1212                     if (!signed) {
1213                         String name = je.getName().toUpperCase(Locale.ENGLISH);
1214                         signed = name.startsWith("META-INF/")
1215                                  && name.endsWith(".SF");
1216                     }
1217                 }
1218             }
1219 
1220             try {
1221                 if (verifySignature && signed) {
1222                     // validate the code signers
1223                     Set<CodeSigner> signers = getSigners(jf);
1224                     SignedModule.validateSigners(signers);
1225                     // store the signers
1226                     new Signers(md, signers).store();
1227                 }
1228             } catch (CertificateException ce) {
1229                 throw new SignatureException(ce);
1230             }
1231 
1232             if (strip)
1233                 strip(md);
1234             reIndex(mid);
1235             
1236             // copy module-info.class to each view
1237             byte[] mib = java.nio.file.Files.readAllBytes(md.toPath().resolve("info"));
1238             copyModuleInfo(root, mi, mib);
1239             return mid;
1240         } catch (IOException | SignatureException x) {
1241             if (mi != null) {
1242                 try {
1243                     deleteModuleDir(root, mi);
1244                 } catch (IOException y) {
1245                     y.initCause(x);
1246                     throw y;
1247                 }
1248             }
1249             throw x;
1250         }
1251     }
1252 
1253     /**
1254      * Returns the set of signers of the specified jar file. Each signer
1255      * must have signed all relevant entries.
1256      */
1257     private static Set<CodeSigner> getSigners(JarFile jf)
1258         throws SignatureException
1259     {
1260         Set<CodeSigner> signers = new HashSet<>();
1261         Enumeration<JarEntry> entries = jf.entries();
1262         while (entries.hasMoreElements()) {
1263             JarEntry je = entries.nextElement();
1264             String name = je.getName().toUpperCase(Locale.ENGLISH);
1265             if (name.endsWith("/") || isSigningRelated(name))
1266                 continue;
1267 
1268             // A signed modular jar can be signed by multiple signers.
1269             // However, all entries must be signed by each of these signers.
1270             // Signers that only sign a subset of entries are ignored.
1271             CodeSigner[] jeSigners = je.getCodeSigners();
1272             if (jeSigners == null || jeSigners.length == 0)
1273                 throw new SignatureException("Found unsigned entry in "
1274                                              + "signed modular JAR");
1275 
1276             Set<CodeSigner> jeSignerSet =
1277                 new HashSet<>(Arrays.asList(jeSigners));
1278             if (signers.isEmpty())
1279                 signers.addAll(jeSignerSet);
1280             else {
1281                 if (signers.retainAll(jeSignerSet) && signers.isEmpty())
1282                     throw new SignatureException("No signers in common in "
1283                                                  + "signed modular JAR");
1284             }
1285         }
1286         return signers;
1287     }
1288 
1289     // true if file is part of the signature mechanism itself
1290     private static boolean isSigningRelated(String name) {
1291         if (!name.startsWith("META-INF/")) {
1292             return false;
1293         }
1294         name = name.substring(9);
1295         if (name.indexOf('/') != -1) {
1296             return false;
1297         }
1298         if (name.endsWith(".DSA") ||
1299             name.endsWith(".RSA") ||
1300             name.endsWith(".SF")  ||
1301             name.endsWith(".EC")  ||
1302             name.startsWith("SIG-") ||
1303             name.equals("MANIFEST.MF")) {
1304             return true;
1305         }
1306         return false;
1307     }
1308 
1309     private void writeJarEntry(InputStream is, JarEntry je, JarOutputStream jos)
1310         throws IOException, SignatureException
1311     {
1312         JarEntry entry = new JarEntry(je.getName());
1313         entry.setMethod(isDeflated() ? ZipEntry.DEFLATED : ZipEntry.STORED);
1314         entry.setTime(je.getTime());
1315         try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
1316             int size = 0;
1317             byte[] bs = new byte[1024];
1318             int cc = 0;
1319             // This will throw a SecurityException if a signature is invalid.
1320             while ((cc = is.read(bs)) > 0) {
1321                 baos.write(bs, 0, cc);
1322                 size += cc;
1323             }
1324             if (!isDeflated()) {
1325                 entry.setSize(size);
1326                 entry.setCrc(je.getCrc());
1327                 entry.setCompressedSize(size);
1328             }
1329             jos.putNextEntry(entry);
1330             if (baos.size() > 0)
1331                 baos.writeTo(jos);
1332             jos.closeEntry();
1333         } catch (SecurityException se) {
1334             throw new SignatureException(se);
1335         }
1336     }
1337 
1338     private ModuleId install(File mf, boolean verifySignature, boolean strip)
1339         throws ConfigurationException, IOException, SignatureException
1340     {
1341         if (mf.getName().endsWith(".jar"))
1342             return installFromJarFile(mf, verifySignature, strip);
1343         else {
1344             // Assume jmod file
1345             try (FileInputStream in = new FileInputStream(mf)) {
1346                 return install(in, verifySignature, strip);
1347             }
1348         }
1349     }
1350 
1351     public void install(Collection<File> mfs, boolean verifySignature, boolean strip)
1352         throws ConfigurationException, IOException, SignatureException
1353     {
1354         List<ModuleId> mids = new ArrayList<>();
1355         boolean complete = false;
1356         Throwable ox = null;
1357         try {
1358             for (File mf : mfs)
1359                 mids.add(install(mf, verifySignature, strip));
1360             configure(mids);
1361             complete = true;
1362         } catch (IOException|ConfigurationException x) {
1363             ox = x;
1364             throw x;
1365         } finally {
1366             if (!complete) {
1367                 try {
1368                     for (ModuleId mid : mids)
1369                         deleteModuleDir(mid);
1370                 } catch (IOException x) {
1371                     if (ox != null)
1372                         x.initCause(ox);
1373                     throw x;
1374                 }
1375             }
1376         }
1377     }
1378 
1379     @Override
1380     public void install(Collection<File> mfs, boolean verifySignature)
1381         throws ConfigurationException, IOException, SignatureException
1382     {
1383         install(mfs, verifySignature, false);
1384     }
1385 
1386     // Public entry point, since the Resolver itself is package-private
1387     //
1388     public Resolution resolve(Collection<ModuleIdQuery> midqs)
1389         throws ConfigurationException, IOException
1390     {
1391         return Resolver.run(this, midqs);
1392     }
1393 
1394     public void install(Resolution res, boolean verifySignature, boolean strip)
1395         throws ConfigurationException, IOException, SignatureException
1396     {
1397         // ## Handle case of installing multiple root modules
1398         assert res.rootQueries.size() == 1;
1399         ModuleIdQuery midq = res.rootQueries.iterator().next();
1400         ModuleInfo root = null;
1401         for (String mn : res.moduleViewForName.keySet()) {
1402             ModuleView mv = res.moduleViewForName.get(mn);
1403             if (midq.matches(mv.id())) {
1404                 root = mv.moduleInfo();
1405                 break;
1406             }
1407         }
1408         assert root != null;
1409 
1410         // Download
1411         //
1412         for (ModuleId mid : res.modulesNeeded()) {
1413             URI u = res.locationForName.get(mid.name());
1414             assert u != null;
1415             RemoteRepository rr = repositoryList().firstRepository();
1416             assert rr != null;
1417             install(rr.fetch(mid), verifySignature, strip);
1418             res.locationForName.put(mid.name(), location());
1419             // ## If something goes wrong, delete all our modules
1420         }
1421 
1422         // Configure
1423         //
1424         Configuration<Context> cf
1425             = Configurator.configure(this, res);
1426         new StoredConfiguration(findModuleDir(root.id()), cf).store();
1427     }
1428 
1429     @Override
1430     public void install(Resolution res, boolean verifySignature)
1431         throws ConfigurationException, IOException, SignatureException
1432     {
1433         install(res, verifySignature, false);
1434     }
1435 
1436     /**
1437      * <p> Pre-install one or more modules to an arbitrary destination
1438      * directory. </p>
1439      *
1440      * <p> A pre-installed module has the same format as within the library
1441      * itself, except that there is never a configuration file. </p>
1442      *
1443      * <p> This method is provided for use by the module-packaging tool. </p>
1444      *
1445      * @param   mfs
1446      *          The manifest describing the contents of the modules to be
1447      *          pre-installed
1448      *
1449      * @param   dst
1450      *          The destination directory, with one subdirectory per module
1451      *          name, each of which contains one subdirectory per version
1452      */
1453     public void preInstall(Collection<Manifest> mfs, File dst)
1454         throws IOException
1455     {
1456         Files.mkdirs(dst, "module destination");
1457         install(mfs, dst, false);
1458     }
1459 
1460     public void preInstall(Manifest mf, File dst)
1461         throws IOException
1462     {
1463         preInstall(Collections.singleton(mf), dst);
1464     }
1465 
1466     /**
1467      * <p> Update the configurations of any root modules affected by the
1468      * copying of the named modules, in pre-installed format, into this
1469      * library. </p>
1470      *
1471      * @param   mids
1472      *          The module ids of the new or updated modules, or
1473      *          {@code null} if the configuration of every root module
1474      *          should be (re)computed
1475      */
1476     public void configure(List<ModuleId> mids)
1477         throws ConfigurationException, IOException
1478     {
1479         // ## mids not used yet
1480         List<ModuleId> roots = new ArrayList<>();
1481         for (ModuleView mv : listLocalRootModuleViews()) {
1482             // each module can have multiple entry points
1483             // only configure once for each module.
1484             if (!roots.contains(mv.moduleInfo().id()))
1485                 roots.add(mv.moduleInfo().id());
1486         }
1487 
1488         for (ModuleId mid : roots) {
1489             // ## We could be a lot more clever about this!
1490             Configuration<Context> cf
1491                 = Configurator.configure(this, mid.toQuery());
1492             new StoredConfiguration(findModuleDir(mid), cf).store();
1493         }
1494     }
1495 
1496     public URI findLocalResource(ModuleId mid, String name)
1497         throws IOException
1498     {
1499         return locateContent(mid, name);
1500     }
1501 
1502     public File findLocalNativeLibrary(ModuleId mid, String name)
1503         throws IOException
1504     {
1505         File f = natlibs();
1506         if (f == null) {
1507             f = findModuleDir(mid);
1508             if (f == null)
1509                 return null;
1510             f = new File(f, "lib");
1511         }
1512         f = new File(f, name);
1513         if (!f.exists())
1514             return null;
1515         return f;
1516     }
1517 
1518     public File classPath(ModuleId mid)
1519         throws IOException
1520     {
1521         File md = findModuleDir(mid);
1522         if (md == null) {
1523             if (parent != null)
1524                 return parent.classPath(mid);
1525             return null;
1526         }
1527         // ## Check for other formats here
1528         return new File(md, "classes");
1529     }
1530 
1531     /**
1532      * <p> Re-index the classes of the named previously-installed modules, and
1533      * then update the configurations of any affected root modules. </p>
1534      *
1535      * <p> This method is intended for use during development, when a build
1536      * process may update a previously-installed module in place, adding or
1537      * removing classes. </p>
1538      *
1539      * @param   mids
1540      *          The module ids of the new or updated modules, or
1541      *          {@code null} if the configuration of every root module
1542      *          should be (re)computed
1543      */
1544     public void reIndex(List<ModuleId> mids)
1545         throws ConfigurationException, IOException
1546     {
1547         for (ModuleId mid : mids)
1548             reIndex(mid);
1549         configure(mids);
1550     }
1551 
1552 
1553     // -- Repositories --
1554 
1555     private static class RepoList
1556         implements RemoteRepositoryList
1557     {
1558 
1559         private static final int MINOR_VERSION = 0;
1560         private static final int MAJOR_VERSION = 0;
1561 
1562         private final File root;
1563         private final File listFile;
1564 
1565         private RepoList(File r) {
1566             root = new File(r, FileConstants.META_PREFIX + "repos");
1567             listFile = new File(root, FileConstants.META_PREFIX + "list");
1568         }
1569 
1570         private static FileHeader fileHeader() {
1571             return (new FileHeader()
1572                     .type(FileConstants.Type.REMOTE_REPO_LIST)
1573                     .majorVersion(MAJOR_VERSION)
1574                     .minorVersion(MINOR_VERSION));
1575         }
1576 
1577         private List<RemoteRepository> repos = null;
1578         private long nextRepoId = 0;
1579 
1580         private File repoDir(long id) {
1581             return new File(root, Long.toHexString(id));
1582         }
1583 
1584         private void load() throws IOException {
1585 
1586             repos = new ArrayList<>();
1587             if (!root.exists() || !listFile.exists())
1588                 return;
1589             FileInputStream fin = new FileInputStream(listFile);
1590             DataInputStream in
1591                 = new DataInputStream(new BufferedInputStream(fin));
1592             try {
1593 
1594                 FileHeader fh = fileHeader();
1595                 fh.read(in);
1596                 nextRepoId = in.readLong();
1597                 int n = in.readInt();
1598                 long[] ids = new long[n];
1599                 for (int i = 0; i < n; i++)
1600                     ids[i] = in.readLong();
1601                 RemoteRepository parent = null;
1602 
1603                 // Load in reverse order so that parents are correct
1604                 for (int i = n - 1; i >= 0; i--) {
1605                     long id = ids[i];
1606                     RemoteRepository rr
1607                         = RemoteRepository.open(repoDir(id), id, parent);
1608                     repos.add(rr);
1609                     parent = rr;
1610                 }
1611                 Collections.reverse(repos);
1612 
1613             } finally {
1614                 in.close();
1615             }
1616 
1617         }
1618 
1619         private List<RemoteRepository> roRepos = null;
1620 
1621         // Unmodifiable
1622         public List<RemoteRepository> repositories() throws IOException {
1623             if (repos == null) {
1624                 load();
1625                 roRepos = Collections.unmodifiableList(repos);
1626             }
1627             return roRepos;
1628         }
1629 
1630         public RemoteRepository firstRepository() throws IOException {
1631             repositories();
1632             return repos.isEmpty() ? null : repos.get(0);
1633         }
1634 
1635         private void store() throws IOException {
1636             File newfn = new File(root, "list.new");
1637             FileOutputStream fout = new FileOutputStream(newfn);
1638             DataOutputStream out
1639                 = new DataOutputStream(new BufferedOutputStream(fout));
1640             try {
1641                 try {
1642                     fileHeader().write(out);
1643                     out.writeLong(nextRepoId);
1644                     out.writeInt(repos.size());
1645                     for (RemoteRepository rr : repos)
1646                         out.writeLong(rr.id());
1647                 } finally {
1648                     out.close();
1649                 }
1650             } catch (IOException x) {
1651                 newfn.delete();
1652                 throw x;
1653             }
1654             java.nio.file.Files.move(newfn.toPath(), listFile.toPath(), ATOMIC_MOVE);
1655         }
1656 
1657         public RemoteRepository add(URI u, int position)
1658             throws IOException
1659         {
1660 
1661             if (repos == null)
1662                 load();
1663             for (RemoteRepository rr : repos) {
1664                 if (rr.location().equals(u)) // ## u not canonical
1665                     throw new IllegalStateException(u + ": Already in"
1666                                                     + " repository list");
1667             }
1668             if (!root.exists()) {
1669                 if (!root.mkdir())
1670                     throw new IOException(root + ": Cannot create directory");
1671             }
1672 
1673             if (repos.size() == Integer.MAX_VALUE)
1674                 throw new IllegalStateException("Too many repositories");
1675             if (position < 0)
1676                 throw new IllegalArgumentException("Invalid index");
1677 
1678             long id = nextRepoId++;
1679             RemoteRepository rr = RemoteRepository.create(repoDir(id), u, id);
1680             try {
1681                 rr.updateCatalog(true);
1682             } catch (IOException x) {
1683                 repoDir(id).delete();
1684                 throw x;
1685             }
1686 
1687             if (position >= repos.size()) {
1688                 repos.add(rr);
1689             } else if (position >= 0) {
1690                 List<RemoteRepository> prefix
1691                     = new ArrayList<>(repos.subList(0, position));
1692                 List<RemoteRepository> suffix
1693                     = new ArrayList<>(repos.subList(position, repos.size()));
1694                 repos.clear();
1695                 repos.addAll(prefix);
1696                 repos.add(rr);
1697                 repos.addAll(suffix);
1698             }
1699             store();
1700 
1701             return rr;
1702 
1703         }
1704 
1705         public boolean remove(RemoteRepository rr)
1706             throws IOException
1707         {
1708             if (!repos.remove(rr))
1709                 return false;
1710             store();
1711             File rd = repoDir(rr.id());
1712             for (File f : rd.listFiles()) {
1713                 if (!f.delete())
1714                     throw new IOException(f + ": Cannot delete");
1715             }
1716             if (!rd.delete())
1717                 throw new IOException(rd + ": Cannot delete");
1718             return true;
1719         }
1720 
1721         public boolean areCatalogsStale() throws IOException {
1722             for (RemoteRepository rr : repos) {
1723                 if (rr.isCatalogStale())
1724                     return true;
1725             }
1726             return false;
1727         }
1728 
1729         public boolean updateCatalogs(boolean force) throws IOException {
1730             boolean updated = false;
1731             for (RemoteRepository rr : repos) {
1732                 if (rr.updateCatalog(force))
1733                     updated = true;
1734             }
1735             return updated;
1736         }
1737 
1738     }
1739 
1740     private RemoteRepositoryList repoList = null;
1741 
1742     public RemoteRepositoryList repositoryList()
1743         throws IOException
1744     {
1745         if (repoList == null)
1746             repoList = new RepoList(root);
1747         return repoList;
1748     }
1749 
1750 }