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.nio.file.attribute.BasicFileAttributes;
  33 import java.security.*;
  34 import java.security.cert.*;
  35 import java.util.*;
  36 import java.util.jar.*;
  37 import java.util.zip.*;
  38 
  39 import static java.nio.file.StandardCopyOption.*;
  40 
  41 /**
  42  * A simple module library which stores data directly in the filesystem
  43  *
  44  * @see Library
  45  */
  46 
  47 // ## TODO: Move remaining parent-searching logic upward into Library class
  48 
  49 // On-disk library layout
  50 //
  51 //   $LIB/%jigsaw-library
  52 //        com.foo.bar/1.2.3/info (= module-info.class)
  53 //                          index (list of defined classes)
  54 //                          config (resolved configuration, if a root)
  55 //                          classes/com/foo/bar/...
  56 //                          resources/com/foo/bar/...
  57 //                          lib/libbar.so
  58 //                          bin/bar
  59 //                          signer (signer's certchain & timestamp)
  60 //
  61 // ## Issue: Concurrent access to the module library
  62 // ## e.g. a module is being removed while a running application
  63 // ## is depending on it
  64 
  65 public final class SimpleLibrary
  66     extends Library
  67 {
  68 
  69     private static abstract class MetaData {
  70 
  71         protected final int maxMajorVersion;
  72         protected final int maxMinorVersion;
  73         protected int majorVersion;
  74         protected int minorVersion;
  75         private final FileConstants.Type type;
  76         private final File file;
  77 
  78         protected MetaData(int maxMajor, int maxMinor,
  79                            FileConstants.Type t, File f)
  80         {
  81             maxMajorVersion = majorVersion = maxMajor;
  82             maxMinorVersion = minorVersion = maxMinor;
  83             type = t;
  84             file = f;
  85         }
  86 
  87         protected abstract void storeRest(DataOutputStream out)
  88             throws IOException;
  89 
  90         void store() throws IOException {
  91             try (OutputStream fos = new FileOutputStream(file);
  92                  BufferedOutputStream bos = new BufferedOutputStream(fos);
  93                  DataOutputStream out = new DataOutputStream(bos)) {
  94                 out.writeInt(FileConstants.MAGIC);
  95                 out.writeShort(type.value());
  96                 out.writeShort(majorVersion);
  97                 out.writeShort(minorVersion);
  98                 storeRest(out);
  99             }
 100         }
 101 
 102         protected abstract void loadRest(DataInputStream in)
 103             throws IOException;
 104 
 105         protected void load() throws IOException {
 106             try (InputStream fis = new FileInputStream(file);
 107                  BufferedInputStream bis = new BufferedInputStream(fis);
 108                  DataInputStream in = new DataInputStream(bis)) {
 109                 if (in.readInt() != FileConstants.MAGIC)
 110                     throw new IOException(file + ": Invalid magic number");
 111                 if (in.readShort() != type.value())
 112                     throw new IOException(file + ": Invalid file type");
 113                 int maj = in.readShort();
 114                 int min = in.readShort();
 115                 if (   maj > maxMajorVersion
 116                     || (maj == maxMajorVersion && min > maxMinorVersion)) {
 117                     throw new IOException(file
 118                                           + ": Futuristic version number");
 119                 }
 120                 majorVersion = maj;
 121                 minorVersion = min;
 122                 loadRest(in);
 123             } catch (EOFException x) {
 124                 throw new IOException(file + ": Invalid library metadata", x);
 125             }
 126         }
 127     }
 128 
 129     /**
 130      * Defines the storage options that SimpleLibrary supports.
 131      */
 132     public static enum StorageOption {
 133         DEFLATED,
 134     }
 135 
 136     private static final class Header
 137         extends MetaData
 138     {
 139         private static final String FILE
 140             = FileConstants.META_PREFIX + "jigsaw-library";
 141 
 142         private static final int MAJOR_VERSION = 0;
 143         private static final int MINOR_VERSION = 1;
 144 
 145         private static final int DEFLATED = 1 << 0;
 146 
 147         private File parent;
 148         // location of native libs for this library (may be outside the library)
 149         // null:default, to use a per-module 'lib' directory
 150         private File natlibs;
 151         // location of native cmds for this library (may be outside the library)
 152         // null:default, to use a per-module 'bin' directory
 153         private File natcmds;
 154         // location of config files for this library (may be outside the library)
 155         // null:default, to use a per-module 'etc' directory
 156         private File configs;
 157         private Set<StorageOption> opts;
 158 
 159         public File parent()  { return parent;  }
 160         public File natlibs() { return natlibs; }
 161         public File natcmds() { return natcmds; }
 162         public File configs() { return configs; }
 163         public boolean isDeflated() {
 164             return opts.contains(StorageOption.DEFLATED);
 165         }
 166 
 167         private Header(File root) {
 168             super(MAJOR_VERSION, MINOR_VERSION,
 169                   FileConstants.Type.LIBRARY_HEADER,
 170                   new File(root, FILE));
 171         }
 172 
 173         private Header(File root, File parent, File natlibs, File natcmds,
 174                        File configs, Set<StorageOption> opts) {
 175             this(root);
 176             this.parent = parent;
 177             this.natlibs = natlibs;
 178             this.natcmds = natcmds;
 179             this.configs = configs;
 180             this.opts = new HashSet<>(opts);
 181         }
 182 
 183         private void storePath(File p, DataOutputStream out) throws IOException {
 184             if (p != null) {
 185                 out.writeByte(1);
 186                 out.writeUTF(Files.convertSeparator(p.toString()));
 187             } else {
 188                 out.write(0);
 189             }
 190         }
 191 
 192         protected void storeRest(DataOutputStream out) throws IOException {
 193             int flags = 0;
 194             if (isDeflated())
 195                 flags |= DEFLATED;
 196             out.writeShort(flags);
 197 
 198             storePath(parent, out);
 199             storePath(natlibs, out);
 200             storePath(natcmds, out);
 201             storePath(configs, out);
 202         }
 203 
 204         private File loadPath(DataInputStream in) throws IOException {
 205             if (in.readByte() != 0)
 206                 return new File(Files.platformSeparator(in.readUTF()));
 207             return null;
 208         }
 209 
 210         protected void loadRest(DataInputStream in) throws IOException {
 211             opts = new HashSet<StorageOption>();
 212             int flags = in.readShort();
 213             if ((flags & DEFLATED) == DEFLATED)
 214                 opts.add(StorageOption.DEFLATED);
 215             parent = loadPath(in);
 216             natlibs = loadPath(in);
 217             natcmds = loadPath(in);
 218             configs = loadPath(in);
 219         }
 220 
 221         private static Header load(File f) throws IOException {
 222             Header h = new Header(f);
 223             h.load();
 224             return h;
 225         }
 226     }
 227 
 228     private final File root;
 229     private final File canonicalRoot;
 230     private final File parentPath;
 231     private final File natlibs;
 232     private final File natcmds;
 233     private final File configs;
 234     private final SimpleLibrary parent;
 235     private final Header hd;
 236 
 237     public String name() { return root.toString(); }
 238     public File root() { return canonicalRoot; }
 239     public int majorVersion() { return hd.majorVersion; }
 240     public int minorVersion() { return hd.minorVersion; }
 241     public SimpleLibrary parent() { return parent; }
 242     public File natlibs() { return natlibs; }
 243     public File natcmds() { return natcmds; }
 244     public File configs() { return configs; }
 245     public boolean isDeflated() { return hd.isDeflated(); }
 246 
 247     private URI location = null;
 248     public URI location() {
 249         if (location == null)
 250             location = root().toURI();
 251         return location;
 252     }
 253 
 254     @Override
 255     public String toString() {
 256         return (this.getClass().getName()
 257                 + "[" + canonicalRoot
 258                 + ", v" + hd.majorVersion + "." + hd.minorVersion + "]");
 259     }
 260 
 261 
 262     private static File resolveAndEnsurePath(File path) throws IOException {
 263         if (path == null) { return null; }
 264 
 265         File p = path.getCanonicalFile();
 266         if (!p.exists()) {
 267             Files.mkdirs(p, p.toString());
 268         } else {
 269             Files.ensureIsDirectory(p);
 270             Files.ensureWriteable(p);
 271         }
 272         return p;
 273     }
 274 
 275     private File relativize(File path) throws IOException {
 276         if (path == null) { return null; }
 277         // Return the path relative to the canonical root
 278         return (canonicalRoot.toPath().relativize(path.toPath().toRealPath())).toFile();
 279     }
 280 
 281     // Opens an existing library
 282     private SimpleLibrary(File path) throws IOException {
 283         root = path;
 284         canonicalRoot = root.getCanonicalFile();
 285         Files.ensureIsDirectory(root);
 286         hd = Header.load(root);
 287 
 288         parentPath = hd.parent();
 289         parent = parentPath != null ? open(parentPath) : null;
 290 
 291         natlibs = hd.natlibs() == null ? null :
 292             new File(canonicalRoot, hd.natlibs().toString()).getCanonicalFile();
 293         natcmds = hd.natcmds() == null ? null :
 294             new File(canonicalRoot, hd.natcmds().toString()).getCanonicalFile();
 295         configs = hd.configs() == null ? null :
 296             new File(canonicalRoot, hd.configs().toString()).getCanonicalFile();
 297     }
 298 
 299     // Creates a new library
 300     private SimpleLibrary(File path, File parentPath, File natlibs, File natcmds,
 301                           File configs, Set<StorageOption> opts)
 302         throws IOException
 303     {
 304         root = path;
 305         canonicalRoot = root.getCanonicalFile();
 306         if (root.exists()) {
 307             Files.ensureIsDirectory(root);
 308             if (root.list().length != 0)
 309                 throw new IOException(root + ": Already Exists");
 310             Files.ensureWriteable(root);
 311         } else
 312             Files.mkdirs(root, root.toString());
 313 
 314         this.parent = parentPath != null ? open(parentPath) : null;
 315         this.parentPath = parentPath != null ? this.parent.root() : null;
 316 
 317         this.natlibs = resolveAndEnsurePath(natlibs);
 318         this.natcmds = resolveAndEnsurePath(natcmds);
 319         this.configs = resolveAndEnsurePath(configs);
 320 
 321         hd = new Header(canonicalRoot, this.parentPath, relativize(this.natlibs),
 322                         relativize(this.natcmds), relativize(this.configs), opts);
 323         hd.store();
 324     }
 325 
 326     public static SimpleLibrary create(File path, File parent, File natlibs,
 327                                        File natcmds, File configs,
 328                                        Set<StorageOption> opts)
 329         throws IOException
 330     {
 331         return new SimpleLibrary(path, parent, natlibs, natcmds, configs, opts);
 332     }
 333 
 334     public static SimpleLibrary create(File path, File parent, Set<StorageOption> opts)
 335         throws IOException
 336     {
 337         return new SimpleLibrary(path, parent, null, null, null, opts);
 338     }
 339 
 340     public static SimpleLibrary create(File path, File parent)
 341         throws IOException
 342     {
 343         return SimpleLibrary.create(path, parent, Collections.<StorageOption>emptySet());
 344     }
 345 
 346     public static SimpleLibrary create(File path, Set<StorageOption> opts)
 347         throws IOException
 348     {
 349         // ## Should default parent to $JAVA_HOME/lib/modules
 350         return SimpleLibrary.create(path, null, opts);
 351     }
 352 
 353     public static SimpleLibrary open(File path)
 354         throws IOException
 355     {
 356         return new SimpleLibrary(path);
 357     }
 358 
 359     private static final JigsawModuleSystem jms
 360         = JigsawModuleSystem.instance();
 361 
 362     private static final class Index
 363         extends MetaData
 364     {
 365 
 366         private static String FILE = "index";
 367 
 368         private static int MAJOR_VERSION = 0;
 369         private static int MINOR_VERSION = 1;
 370 
 371         private Set<String> publicClasses;
 372         public Set<String> publicClasses() { return publicClasses; }
 373 
 374         private Set<String> otherClasses;
 375         public Set<String> otherClasses() { return otherClasses; }
 376 
 377         private Index(File root) {
 378             super(MAJOR_VERSION, MINOR_VERSION,
 379                   FileConstants.Type.LIBRARY_MODULE_INDEX,
 380                   new File(root, FILE));
 381             // Unsorted on input, because we don't need it sorted
 382             publicClasses = new HashSet<String>();
 383             otherClasses = new HashSet<String>();
 384         }
 385 
 386         private void storeSet(Set<String> cnset, DataOutputStream out)
 387             throws IOException
 388         {
 389             // Sorted on output, because we can afford it
 390             List<String> cns = new ArrayList<String>(cnset);
 391             Collections.sort(cns);
 392             out.writeInt(cns.size());
 393             for (String cn : cns)
 394                 out.writeUTF(cn);
 395         }
 396 
 397         protected void storeRest(DataOutputStream out)
 398             throws IOException
 399         {
 400             storeSet(publicClasses, out);
 401             storeSet(otherClasses, out);
 402         }
 403 
 404         private void loadSet(DataInputStream in, Set<String> cnset)
 405             throws IOException
 406         {
 407             int n = in.readInt();
 408             for (int i = 0; i < n; i++)
 409                 cnset.add(in.readUTF());
 410         }
 411 
 412         protected void loadRest(DataInputStream in)
 413             throws IOException
 414         {
 415             loadSet(in, publicClasses);
 416             loadSet(in, otherClasses);
 417         }
 418 
 419         private static Index load(File f)
 420             throws IOException
 421         {
 422             Index ix = new Index(f);
 423             ix.load();
 424             return ix;
 425         }
 426 
 427     }
 428 
 429     private static final class StoredConfiguration
 430         extends MetaData
 431     {
 432 
 433         private static String FILE = "config";
 434 
 435         private static int MAJOR_VERSION = 0;
 436         private static int MINOR_VERSION = 1;
 437 
 438         private Configuration<Context> cf;
 439 
 440         private static void delete(File root) {
 441             new File(root, FILE).delete();
 442         }
 443 
 444         private StoredConfiguration(File root, Configuration<Context> conf)
 445         {
 446             super(MAJOR_VERSION, MINOR_VERSION,
 447                   FileConstants.Type.LIBRARY_MODULE_CONFIG,
 448                   new File(root, FILE));
 449             cf = conf;
 450         }
 451 
 452         protected void storeRest(DataOutputStream out)
 453             throws IOException
 454         {
 455             // Roots
 456             out.writeInt(cf.roots().size());
 457             for (ModuleId mid : cf.roots()) {
 458                 out.writeUTF(mid.toString());
 459             }
 460             // Contexts
 461             out.writeInt(cf.contexts().size());
 462             for (Context cx : cf.contexts()) {
 463                 out.writeUTF(cx.name());
 464                 // Module ids, and their libraries
 465                 out.writeInt(cx.modules().size());
 466                 for (ModuleId mid : cx.modules()) {
 467                     out.writeUTF(mid.toString());
 468                     File lp = cx.findLibraryPathForModule(mid);
 469                     if (lp == null)
 470                         out.writeUTF("");
 471                     else
 472                         out.writeUTF(lp.toString());
 473 
 474                     // Module views
 475                     out.writeInt(cx.views(mid).size());
 476                     for (ModuleId id : cx.views(mid)) {
 477                         out.writeUTF(id.toString());
 478                     }
 479                 }
 480 
 481                 // Local class map
 482                 out.writeInt(cx.localClasses().size());
 483                 for (Map.Entry<String,ModuleId> me
 484                          : cx.moduleForLocalClassMap().entrySet()) {
 485                     out.writeUTF(me.getKey());
 486                     out.writeUTF(me.getValue().toString());
 487                 }
 488 
 489                 // Remote package map
 490                 out.writeInt(cx.contextForRemotePackageMap().size());
 491                 for (Map.Entry<String,String> me
 492                          : cx.contextForRemotePackageMap().entrySet()) {
 493                     out.writeUTF(me.getKey());
 494                     out.writeUTF(me.getValue());
 495                 }
 496 
 497                 // Suppliers
 498                 out.writeInt(cx.remoteContexts().size());
 499                 for (String cxn : cx.remoteContexts()) {
 500                     out.writeUTF(cxn);
 501                 }
 502 
 503             }
 504         }
 505 
 506         protected void loadRest(DataInputStream in)
 507             throws IOException
 508         {
 509             // Roots
 510             int nRoots = in.readInt();
 511             List<ModuleId> roots = new ArrayList<>();
 512             for (int i = 0; i < nRoots; i++) {
 513                 String root = in.readUTF();
 514                 ModuleId rmid = jms.parseModuleId(root);
 515                 roots.add(rmid);
 516             }
 517             cf = new Configuration<Context>(roots);
 518             // Contexts
 519             int nContexts = in.readInt();
 520             for (int i = 0; i < nContexts; i++) {
 521                 Context cx = new Context();
 522                 String cxn = in.readUTF();
 523                 // Module ids
 524                 int nModules = in.readInt();
 525                 for (int j = 0; j < nModules; j++) {
 526                     ModuleId mid = jms.parseModuleId(in.readUTF());
 527                     String lps = in.readUTF();
 528                     if (lps.length() > 0)
 529                         cx.putLibraryPathForModule(mid, new File(lps));
 530                     // Module Views
 531                     int nViews = in.readInt();
 532                     Set<ModuleId> views = new HashSet<>();
 533                     for (int k = 0; k < nViews; k++) {
 534                         ModuleId id = jms.parseModuleId(in.readUTF());
 535                         views.add(id);
 536                         cf.put(id.name(), cx);
 537                     }
 538                     cx.add(mid, views);
 539                 }
 540                 cx.freeze();
 541                 assert cx.name().equals(cxn);
 542                 cf.add(cx);
 543                 // Local class map
 544                 int nClasses = in.readInt();
 545                 for (int j = 0; j < nClasses; j++)
 546                     cx.putModuleForLocalClass(in.readUTF(),
 547                                               jms.parseModuleId(in.readUTF()));
 548                 // Remote package map
 549                 int nPackages = in.readInt();
 550                 for (int j = 0; j < nPackages; j++)
 551                     cx.putContextForRemotePackage(in.readUTF(), in.readUTF());
 552 
 553                 // Suppliers
 554                 int nSuppliers = in.readInt();
 555                 for (int j = 0; j < nSuppliers; j++)
 556                     cx.addSupplier(in.readUTF());
 557             }
 558 
 559         }
 560 
 561         private static StoredConfiguration load(File f)
 562             throws IOException
 563         {
 564             StoredConfiguration sp = new StoredConfiguration(f, null);
 565             sp.load();
 566             return sp;
 567         }
 568 
 569     }
 570 
 571     private static final class Signers
 572         extends MetaData {
 573 
 574         private static String FILE = "signer";
 575         private static int MAJOR_VERSION = 0;
 576         private static int MINOR_VERSION = 1;
 577 
 578         private CertificateFactory cf = null;
 579         private Set<CodeSigner> signers;
 580         private Set<CodeSigner> signers() { return signers; }
 581 
 582         private Signers(File root, Set<CodeSigner> signers) {
 583             super(MAJOR_VERSION, MINOR_VERSION,
 584                   FileConstants.Type.LIBRARY_MODULE_SIGNER,
 585                   new File(root, FILE));
 586             this.signers = signers;
 587         }
 588 
 589         protected void storeRest(DataOutputStream out)
 590             throws IOException
 591         {
 592             out.writeInt(signers.size());
 593             for (CodeSigner signer : signers) {
 594                 try {
 595                     CertPath signerCertPath = signer.getSignerCertPath();
 596                     out.write(signerCertPath.getEncoded("PkiPath"));
 597                     Timestamp ts = signer.getTimestamp();
 598                     out.writeByte((ts != null) ? 1 : 0);
 599                     if (ts != null) {
 600                         out.writeLong(ts.getTimestamp().getTime());
 601                         out.write(ts.getSignerCertPath().getEncoded("PkiPath"));
 602                     }
 603                 } catch (CertificateEncodingException cee) {
 604                     throw new IOException(cee);
 605                 }
 606             }
 607         }
 608 
 609         protected void loadRest(DataInputStream in)
 610             throws IOException
 611         {
 612             int size = in.readInt();
 613             for (int i = 0; i < size; i++) {
 614                 try {
 615                     if (cf == null)
 616                         cf = CertificateFactory.getInstance("X.509");
 617                     CertPath signerCertPath = cf.generateCertPath(in, "PkiPath");
 618                     int b = in.readByte();
 619                     if (b != 0) {
 620                         Date timestamp = new Date(in.readLong());
 621                         CertPath tsaCertPath = cf.generateCertPath(in, "PkiPath");
 622                         Timestamp ts = new Timestamp(timestamp, tsaCertPath);
 623                         signers.add(new CodeSigner(signerCertPath, ts));
 624                     } else {
 625                         signers.add(new CodeSigner(signerCertPath, null));
 626                     }
 627                 } catch (CertificateException ce) {
 628                     throw new IOException(ce);
 629                 }
 630             }
 631         }
 632 
 633         private static Signers load(File f)
 634             throws IOException
 635         {
 636             Signers signers = new Signers(f, new HashSet<CodeSigner>());
 637             signers.load();
 638             return signers;
 639         }
 640     }
 641 
 642     private void gatherLocalModuleIds(File mnd, Set<ModuleId> mids)
 643         throws IOException
 644     {
 645         if (!mnd.isDirectory())
 646             throw new IOException(mnd + ": Not a directory");
 647         if (!mnd.canRead())
 648             throw new IOException(mnd + ": Not readable");
 649         for (String v : mnd.list()) {
 650             mids.add(jms.parseModuleId(mnd.getName(), v));
 651         }
 652     }
 653 
 654     private void gatherLocalModuleIds(Set<ModuleId> mids)
 655         throws IOException
 656     {
 657         File[] mnds = root.listFiles();
 658         for (File mnd : mnds) {
 659             if (mnd.getName().startsWith(FileConstants.META_PREFIX))
 660                 continue;
 661             gatherLocalModuleIds(mnd, mids);
 662         }
 663     }
 664 
 665     protected void gatherLocalModuleIds(String moduleName,
 666                                         Set<ModuleId> mids)
 667         throws IOException
 668     {
 669         if (moduleName == null) {
 670             gatherLocalModuleIds(mids);
 671             return;
 672         }
 673         File mnd = new File(root, moduleName);
 674         if (mnd.exists())
 675             gatherLocalModuleIds(mnd, mids);
 676     }
 677 
 678     private void checkModuleId(ModuleId mid) {
 679         Version v = mid.version();
 680         if (v == null)
 681             return;
 682         if (!(v instanceof JigsawVersion))
 683             throw new IllegalArgumentException(mid + ": Not a Jigsaw module id");
 684     }
 685 
 686     private File moduleDir(File root, ModuleId mid) {
 687         Version v = mid.version();
 688         String vs = (v != null) ? v.toString() : "default";
 689         return new File(new File(root, mid.name()), vs);
 690     }
 691 
 692     private void checkModuleDir(File md)
 693         throws IOException
 694     {
 695         if (!md.isDirectory())
 696             throw new IOException(md + ": Not a directory");
 697         if (!md.canRead())
 698             throw new IOException(md + ": Not readable");
 699     }
 700 
 701     private File findModuleDir(ModuleId mid)
 702         throws IOException
 703     {
 704         checkModuleId(mid);
 705         File md = moduleDir(root, mid);
 706         if (!md.exists())
 707             return null;
 708         checkModuleDir(md);
 709 
 710         // mid may be a view or alias of a module
 711         byte[] mib = Files.load(new File(md, "info"));
 712         ModuleInfo mi = jms.parseModuleInfo(mib);
 713         if (!mid.equals(mi.id())) {
 714             md = moduleDir(root, mi.id());
 715             if (!md.exists())
 716                 throw new IOException(mid + ": " + md + " does not exist");
 717             checkModuleDir(md);
 718         }
 719         return md;
 720     }
 721 
 722     private File makeModuleDir(File root, ModuleInfo mi)
 723         throws ConfigurationException, IOException
 724     {
 725         // view name is unique
 726         for (ModuleView mv : mi.views()) {
 727             File md = moduleDir(root, mv.id());
 728             if (md.exists()) {
 729                 throw new ConfigurationException("module view " +
 730                     mv.id() + " already installed");
 731             }
 732             if (!md.mkdirs()) {
 733                 throw new IOException(md + ": Cannot create");
 734             }
 735         }
 736 
 737         return moduleDir(root, mi.id());
 738     }
 739 
 740     private void deleteModuleDir(File root, ModuleInfo mi)
 741         throws IOException
 742     {
 743         // delete the default view and the module content
 744         ModuleId mid = mi.defaultView().id();
 745         File md = moduleDir(root, mid);
 746         if (md.exists())
 747             ModuleFile.Reader.remove(md);
 748         // delete all views
 749         for (ModuleView mv : mi.views()) {
 750             md = moduleDir(root, mv.id());
 751             if (md.exists()) {
 752                 Files.deleteTree(md);
 753             }
 754         }
 755     }
 756 
 757     private void deleteModuleDir(ModuleId mid)
 758         throws IOException
 759     {
 760         checkModuleId(mid);
 761         File md = moduleDir(root, mid);
 762         if (!md.exists())
 763             return;
 764         checkModuleDir(md);
 765 
 766         // mid may be a view or alias of a module
 767         byte[] mib = Files.load(new File(md, "info"));
 768         ModuleInfo mi = jms.parseModuleInfo(mib);
 769         if (!mid.equals(mi.id())) {
 770             throw new IOException(mi.id() + " found in the module directory for " + mid);
 771         }
 772         deleteModuleDir(root, mi);
 773     }
 774 
 775     private void copyModuleInfo(File root, ModuleInfo mi, byte[] mib)
 776         throws IOException
 777     {
 778         for (ModuleView mv : mi.views()) {
 779             if (mv.id().equals(mi.id())) {
 780                 continue;
 781             }
 782 
 783             File mvd = moduleDir(root, mv.id());
 784             Files.store(mib, new File(mvd, "info"));
 785         }
 786     }
 787     public byte[] readLocalModuleInfoBytes(ModuleId mid)
 788         throws IOException
 789     {
 790         File md = findModuleDir(mid);
 791         if (md == null)
 792             return null;
 793         return Files.load(new File(md, "info"));
 794     }
 795 
 796     public CodeSigner[] readLocalCodeSigners(ModuleId mid)
 797         throws IOException
 798     {
 799         File md = findModuleDir(mid);
 800         if (md == null)
 801             return null;
 802         // Only one signer is currently supported
 803         File f = new File(md, "signer");
 804         // ## concurrency issues : what is the expected behavior if file is
 805         // ## removed by another thread/process here?
 806         if (!f.exists())
 807             return null;
 808         return Signers.load(md).signers().toArray(new CodeSigner[0]);
 809     }
 810 
 811     // ## Close all zip files when we close this library
 812     private Map<ModuleId, Object> contentForModule = new HashMap<>();
 813     private Object NONE = new Object();
 814 
 815     private Object findContent(ModuleId mid)
 816         throws IOException
 817     {
 818         Object o = contentForModule.get(mid);
 819         if (o != null)
 820             return o;
 821         if (o == NONE)
 822             return null;
 823         File md = findModuleDir(mid);
 824         if (md == null) {
 825             contentForModule.put(mid, NONE);
 826             return null;
 827         }
 828         File cf = new File(md, "classes");
 829         if (cf.isFile()) {
 830             ZipFile zf = new ZipFile(cf);
 831             contentForModule.put(mid, zf);
 832             return zf;
 833         }
 834         if (cf.isDirectory()) {
 835             contentForModule.put(mid, cf);
 836             return cf;
 837         }
 838         contentForModule.put(mid, NONE);
 839         return null;
 840     }
 841 
 842     private byte[] loadContent(ZipFile zf, String path)
 843         throws IOException
 844     {
 845         ZipEntry ze = zf.getEntry(path);
 846         if (ze == null)
 847             return null;
 848         return Files.load(zf.getInputStream(ze), (int)ze.getSize());
 849     }
 850 
 851     private byte[] loadContent(ModuleId mid, String path)
 852         throws IOException
 853     {
 854         Object o = findContent(mid);
 855         if (o == null)
 856             return null;
 857         if (o instanceof ZipFile) {
 858             ZipFile zf = (ZipFile)o;
 859             ZipEntry ze = zf.getEntry(path);
 860             if (ze == null)
 861                 return null;
 862             return Files.load(zf.getInputStream(ze), (int)ze.getSize());
 863         }
 864         if (o instanceof File) {
 865             File f = new File((File)o, path);
 866             if (!f.exists())
 867                 return null;
 868             return Files.load(f);
 869         }
 870         assert false;
 871         return null;
 872     }
 873 
 874     private URI locateContent(ModuleId mid, String path)
 875         throws IOException
 876     {
 877         Object o = findContent(mid);
 878         if (o == null)
 879             return null;
 880         if (o instanceof ZipFile) {
 881             ZipFile zf = (ZipFile)o;
 882             ZipEntry ze = zf.getEntry(path);
 883             if (ze == null)
 884                 return null;
 885             return URI.create("jar:"
 886                               + new File(zf.getName()).toURI().toString()
 887                               + "!/" + path);
 888         }
 889         if (o instanceof File) {
 890             File f = new File((File)o, path);
 891             if (!f.exists())
 892                 return null;
 893             return f.toURI();
 894         }

 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 List<Path> listFiles(Path dir) throws IOException {
1013         final List<Path> files = new ArrayList<>();
1014         java.nio.file.Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
1015             @Override
1016             public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
1017                 throws IOException
1018             {
1019                 if (!file.endsWith("module-info.class"))
1020                     files.add(file);
1021 
1022                 return FileVisitResult.CONTINUE;
1023             }
1024         });
1025         return files;
1026     }
1027 
1028     private void install(Manifest mf, File dst, boolean strip)
1029         throws IOException
1030     {
1031         if (mf.classes().size() > 1)
1032             throw new IllegalArgumentException("Multiple module-class"
1033                                                + " directories"
1034                                                + " not yet supported");
1035         if (mf.classes().size() < 1)
1036             throw new IllegalArgumentException("At least one module-class"
1037                                                + " directory required");
1038         File classes = mf.classes().get(0);
1039         final String mn = mf.module();
1040 
1041         File mif = new File(classes, "module-info.class");
1042         File src = null;
1043         if (mif.exists()) {
1044             src = classes;
1045         } else {
1046             src = new File(classes, mn);
1047             mif = new File(src, "module-info.class");
1048         }
1049         byte[] bs =  Files.load(mif);
1050         ModuleInfo mi = jms.parseModuleInfo(bs);
1051         if (!mi.id().name().equals(mn)) {
1052             // ## Need a more appropriate throwable here
1053             throw new Error(mif + " is for module " + mi.id().name()
1054                             + ", not " + mn);
1055         }
1056         String m = mi.id().name();
1057         JigsawVersion v = (JigsawVersion)mi.id().version();
1058         String vs = (v == null) ? "default" : v.toString();
1059         deleteModuleDir(dst, mi);
1060 
1061          // view name is unique
1062         for (ModuleView mv : mi.views()) {
1063             File md = moduleDir(dst, mv.id());
1064             if (!md.mkdirs()) {
1065                 throw new IOException(md + ": Cannot create");
1066             }
1067         }
1068 
1069         File mdst = moduleDir(dst, mi.id());
1070         Files.store(bs, new File(mdst, "info"));
1071         File cldst = new File(mdst, "classes");
1072 
1073         // Delete the config file, if one exists
1074         StoredConfiguration.delete(mdst);
1075 
1076         if (false) {
1077 
1078             // ## Retained for now in case we later want to add an option
1079             // ## to install into a tree rather than a zip file
1080 
1081             // Copy class files and build index
1082             final Index ix = new Index(mdst);
1083             Files.copyTree(src, cldst, new Files.Filter<File>() {
1084                     public boolean accept(File f) throws IOException {
1085                         if (f.isDirectory())
1086                             return true;
1087                         if (f.getName().endsWith(".class")) {
1088                             return addToIndex(ClassInfo.read(f), ix);
1089                         } else {
1090                             return true;
1091                         }
1092                     }});
1093             ix.store();
1094         } else {
1095             // Copy class/resource files and build index
1096             Index ix = new Index(mdst);
1097             Path srcPath = src.toPath();
1098             List<Path> files = listFiles(srcPath);
1099 
1100             if (!files.isEmpty()) {
1101                 try (FileOutputStream fos = new FileOutputStream(new File(mdst, "classes"));
1102                      JarOutputStream jos = new JarOutputStream(new BufferedOutputStream(fos)))
1103                 {
1104                     boolean deflate = isDeflated();
1105                     for (Path path : files) {
1106                         File file = path.toFile();
1107                         String jp = Files.convertSeparator(srcPath.relativize(path).toString());
1108                         try (OutputStream out = Files.newOutputStream(jos, deflate, jp)) {
1109                             java.nio.file.Files.copy(path, out);




1110                         }
1111                         if (file.getName().endsWith(".class"))
1112                             addToIndex(ClassInfo.read(file), ix);
1113                     }
1114                 }
1115             }
1116             ix.store();
1117             copyModuleInfo(dst, mi, bs);

1118             if (strip)
1119                 strip(mdst);
1120         }
1121 
1122     }
1123 
1124     private void install(Collection<Manifest> mfs, File dst, boolean strip)
1125         throws IOException
1126     {
1127         for (Manifest mf : mfs)
1128             install(mf, dst, strip);
1129     }
1130 
1131     public void installFromManifests(Collection<Manifest> mfs, boolean strip)
1132         throws ConfigurationException, IOException
1133     {
1134         install(mfs, root, strip);
1135         configure(null);
1136     }
1137 
1138     @Override
1139     public void installFromManifests(Collection<Manifest> mfs)
1140         throws ConfigurationException, IOException
1141     {
1142         installFromManifests(mfs, false);
1143     }
1144 
1145     private ModuleFileVerifier.Parameters mfvParams;
1146     private ModuleId install(InputStream is, boolean verifySignature, boolean strip)
1147         throws ConfigurationException, IOException, SignatureException
1148     {
1149         BufferedInputStream bin = new BufferedInputStream(is);
1150         DataInputStream in = new DataInputStream(bin);
1151         ModuleInfo mi = null;
1152         try (ModuleFile.Reader mr = new ModuleFile.Reader(in)) {
1153             byte[] mib = mr.readStart();
1154             mi = jms.parseModuleInfo(mib);
1155             File md = makeModuleDir(root, mi);
1156             if (verifySignature && mr.hasSignature()) {
1157                 ModuleFileVerifier mfv = new SignedModule.PKCS7Verifier(mr);
1158                 if (mfvParams == null) {
1159                     mfvParams = new SignedModule.VerifierParameters();
1160                 }
1161                 // Verify the module signature and validate the signer's
1162                 // certificate chain
1163                 Set<CodeSigner> signers = mfv.verifySignature(mfvParams);
1164 
1165                 // Verify the module header hash and the module info hash
1166                 mfv.verifyHashesStart(mfvParams);
1167 
1168                 // ## Check policy - is signer trusted and what permissions
1169                 // ## should be granted?
1170 
1171                 // Store signer info
1172                 new Signers(md, signers).store();
1173 
1174                 // Read and verify the rest of the hashes
1175                 mr.readRest(md, isDeflated(), natlibs(), natcmds(), configs());
1176                 mfv.verifyHashesRest(mfvParams);
1177             } else {
1178                 mr.readRest(md, isDeflated(), natlibs(), natcmds(), configs());
1179             }
1180 
1181             if (strip)
1182                 strip(md);
1183             reIndex(mi.id());         // ## Could do this while reading module file
1184 
1185             // copy module-info.class to each view
1186             copyModuleInfo(root, mi, mib);
1187             return mi.id();
1188 
1189         } catch (IOException | SignatureException x) {
1190             if (mi != null) {
1191                 try {
1192                     deleteModuleDir(root, mi);
1193                 } catch (IOException y) {
1194                     y.initCause(x);
1195                     throw y;
1196                 }
1197             }
1198             throw x;
1199         }
1200     }
1201 
1202     private ModuleId installFromJarFile(File mf, boolean verifySignature, boolean strip)
1203         throws ConfigurationException, IOException, SignatureException
1204     {
1205         ModuleInfo mi = null;
1206         try (JarFile jf = new JarFile(mf, verifySignature)) {
1207             mi = jf.getModuleInfo();
1208             if (mi == null)
1209                 throw new ConfigurationException(mf + ": not a modular JAR file");
1210 
1211             File md = makeModuleDir(root, mi);
1212             ModuleId mid = mi.id();
1213 
1214             boolean signed = false;
1215 
1216             // copy the jar file to the module library
1217             File classesDir = new File(md, "classes");
1218             try (FileOutputStream fos = new FileOutputStream(classesDir);
1219                  BufferedOutputStream bos = new BufferedOutputStream(fos);
1220                  JarOutputStream jos = new JarOutputStream(bos)) {
1221                 jos.setLevel(0);
1222 
1223                 Enumeration<JarEntry> entries = jf.entries();
1224                 while (entries.hasMoreElements()) {
1225                     JarEntry je = entries.nextElement();
1226                     try (InputStream is = jf.getInputStream(je)) {
1227                         if (je.getName().equals(JarFile.MODULEINFO_NAME)) {
1228                             java.nio.file.Files.copy(is, md.toPath().resolve("info"));
1229                         } else {
1230                             writeJarEntry(is, je, jos);
1231                         }
1232                     }
1233                     if (!signed) {
1234                         String name = je.getName().toUpperCase(Locale.ENGLISH);
1235                         signed = name.startsWith("META-INF/")
1236                                  && name.endsWith(".SF");
1237                     }
1238                 }
1239             }
1240 
1241             try {
1242                 if (verifySignature && signed) {
1243                     // validate the code signers
1244                     Set<CodeSigner> signers = getSigners(jf);
1245                     SignedModule.validateSigners(signers);
1246                     // store the signers
1247                     new Signers(md, signers).store();
1248                 }
1249             } catch (CertificateException ce) {
1250                 throw new SignatureException(ce);
1251             }
1252 
1253             if (strip)
1254                 strip(md);
1255             reIndex(mid);
1256 
1257             // copy module-info.class to each view
1258             byte[] mib = java.nio.file.Files.readAllBytes(md.toPath().resolve("info"));
1259             copyModuleInfo(root, mi, mib);
1260             return mid;
1261         } catch (IOException | SignatureException x) {
1262             if (mi != null) {
1263                 try {
1264                     deleteModuleDir(root, mi);
1265                 } catch (IOException y) {
1266                     y.initCause(x);
1267                     throw y;
1268                 }
1269             }
1270             throw x;
1271         }
1272     }
1273 
1274     /**
1275      * Returns the set of signers of the specified jar file. Each signer
1276      * must have signed all relevant entries.
1277      */
1278     private static Set<CodeSigner> getSigners(JarFile jf)
1279         throws SignatureException
1280     {
1281         Set<CodeSigner> signers = new HashSet<>();
1282         Enumeration<JarEntry> entries = jf.entries();
1283         while (entries.hasMoreElements()) {
1284             JarEntry je = entries.nextElement();
1285             String name = je.getName().toUpperCase(Locale.ENGLISH);
1286             if (name.endsWith("/") || isSigningRelated(name))
1287                 continue;
1288 
1289             // A signed modular jar can be signed by multiple signers.
1290             // However, all entries must be signed by each of these signers.
1291             // Signers that only sign a subset of entries are ignored.
1292             CodeSigner[] jeSigners = je.getCodeSigners();
1293             if (jeSigners == null || jeSigners.length == 0)
1294                 throw new SignatureException("Found unsigned entry in "
1295                                              + "signed modular JAR");
1296 
1297             Set<CodeSigner> jeSignerSet =
1298                 new HashSet<>(Arrays.asList(jeSigners));
1299             if (signers.isEmpty())
1300                 signers.addAll(jeSignerSet);
1301             else {
1302                 if (signers.retainAll(jeSignerSet) && signers.isEmpty())
1303                     throw new SignatureException("No signers in common in "
1304                                                  + "signed modular JAR");
1305             }
1306         }
1307         return signers;
1308     }
1309 
1310     // true if file is part of the signature mechanism itself
1311     private static boolean isSigningRelated(String name) {
1312         if (!name.startsWith("META-INF/")) {
1313             return false;
1314         }
1315         name = name.substring(9);
1316         if (name.indexOf('/') != -1) {
1317             return false;
1318         }
1319         if (name.endsWith(".DSA") ||
1320             name.endsWith(".RSA") ||
1321             name.endsWith(".SF")  ||
1322             name.endsWith(".EC")  ||
1323             name.startsWith("SIG-") ||
1324             name.equals("MANIFEST.MF")) {
1325             return true;
1326         }
1327         return false;
1328     }
1329 
1330     private void writeJarEntry(InputStream is, JarEntry je, JarOutputStream jos)
1331         throws IOException, SignatureException
1332     {
1333         JarEntry entry = new JarEntry(je.getName());
1334         entry.setMethod(isDeflated() ? ZipEntry.DEFLATED : ZipEntry.STORED);
1335         entry.setTime(je.getTime());
1336         try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
1337             int size = 0;
1338             byte[] bs = new byte[1024];
1339             int cc = 0;
1340             // This will throw a SecurityException if a signature is invalid.
1341             while ((cc = is.read(bs)) > 0) {
1342                 baos.write(bs, 0, cc);
1343                 size += cc;
1344             }
1345             if (!isDeflated()) {
1346                 entry.setSize(size);
1347                 entry.setCrc(je.getCrc());
1348                 entry.setCompressedSize(size);
1349             }
1350             jos.putNextEntry(entry);
1351             if (baos.size() > 0)
1352                 baos.writeTo(jos);
1353             jos.closeEntry();
1354         } catch (SecurityException se) {
1355             throw new SignatureException(se);
1356         }
1357     }
1358 
1359     private ModuleId install(File mf, boolean verifySignature, boolean strip)
1360         throws ConfigurationException, IOException, SignatureException
1361     {
1362         if (mf.getName().endsWith(".jar"))
1363             return installFromJarFile(mf, verifySignature, strip);
1364         else {
1365             // Assume jmod file
1366             try (FileInputStream in = new FileInputStream(mf)) {
1367                 return install(in, verifySignature, strip);
1368             }
1369         }
1370     }
1371 
1372     public void install(Collection<File> mfs, boolean verifySignature, boolean strip)
1373         throws ConfigurationException, IOException, SignatureException
1374     {
1375         List<ModuleId> mids = new ArrayList<>();
1376         boolean complete = false;
1377         Throwable ox = null;
1378         try {
1379             for (File mf : mfs)
1380                 mids.add(install(mf, verifySignature, strip));
1381             configure(mids);
1382             complete = true;
1383         } catch (IOException|ConfigurationException x) {
1384             ox = x;
1385             throw x;
1386         } finally {
1387             if (!complete) {
1388                 try {
1389                     for (ModuleId mid : mids)
1390                         deleteModuleDir(mid);
1391                 } catch (IOException x) {
1392                     if (ox != null)
1393                         x.initCause(ox);
1394                     throw x;
1395                 }
1396             }
1397         }
1398     }
1399 
1400     @Override
1401     public void install(Collection<File> mfs, boolean verifySignature)
1402         throws ConfigurationException, IOException, SignatureException
1403     {
1404         install(mfs, verifySignature, false);
1405     }
1406 
1407     // Public entry point, since the Resolver itself is package-private
1408     //
1409     public Resolution resolve(Collection<ModuleIdQuery> midqs)
1410         throws ConfigurationException, IOException
1411     {
1412         return Resolver.run(this, midqs);
1413     }
1414 
1415     public void install(Resolution res, boolean verifySignature, boolean strip)
1416         throws ConfigurationException, IOException, SignatureException
1417     {
1418         // ## Handle case of installing multiple root modules
1419         assert res.rootQueries.size() == 1;
1420         ModuleIdQuery midq = res.rootQueries.iterator().next();
1421         ModuleInfo root = null;
1422         for (String mn : res.moduleViewForName.keySet()) {
1423             ModuleView mv = res.moduleViewForName.get(mn);
1424             if (midq.matches(mv.id())) {
1425                 root = mv.moduleInfo();
1426                 break;
1427             }
1428         }
1429         assert root != null;
1430 
1431         // Download
1432         //
1433         for (ModuleId mid : res.modulesNeeded()) {
1434             URI u = res.locationForName.get(mid.name());
1435             assert u != null;
1436             RemoteRepository rr = repositoryList().firstRepository();
1437             assert rr != null;
1438             install(rr.fetch(mid), verifySignature, strip);
1439             res.locationForName.put(mid.name(), location());
1440             // ## If something goes wrong, delete all our modules
1441         }
1442 
1443         // Configure
1444         //
1445         Configuration<Context> cf
1446             = Configurator.configure(this, res);
1447         new StoredConfiguration(findModuleDir(root.id()), cf).store();
1448     }
1449 
1450     @Override
1451     public void install(Resolution res, boolean verifySignature)
1452         throws ConfigurationException, IOException, SignatureException
1453     {
1454         install(res, verifySignature, false);
1455     }
1456 
1457     /**
1458      * <p> Pre-install one or more modules to an arbitrary destination
1459      * directory. </p>
1460      *
1461      * <p> A pre-installed module has the same format as within the library
1462      * itself, except that there is never a configuration file. </p>
1463      *
1464      * <p> This method is provided for use by the module-packaging tool. </p>
1465      *
1466      * @param   mfs
1467      *          The manifest describing the contents of the modules to be
1468      *          pre-installed
1469      *
1470      * @param   dst
1471      *          The destination directory, with one subdirectory per module
1472      *          name, each of which contains one subdirectory per version
1473      */
1474     public void preInstall(Collection<Manifest> mfs, File dst)
1475         throws IOException
1476     {
1477         Files.mkdirs(dst, "module destination");
1478         install(mfs, dst, false);
1479     }
1480 
1481     public void preInstall(Manifest mf, File dst)
1482         throws IOException
1483     {
1484         preInstall(Collections.singleton(mf), dst);
1485     }
1486 
1487     /**
1488      * <p> Update the configurations of any root modules affected by the
1489      * copying of the named modules, in pre-installed format, into this
1490      * library. </p>
1491      *
1492      * @param   mids
1493      *          The module ids of the new or updated modules, or
1494      *          {@code null} if the configuration of every root module
1495      *          should be (re)computed
1496      */
1497     public void configure(List<ModuleId> mids)
1498         throws ConfigurationException, IOException
1499     {
1500         // ## mids not used yet
1501         List<ModuleId> roots = new ArrayList<>();
1502         for (ModuleView mv : listLocalRootModuleViews()) {
1503             // each module can have multiple entry points
1504             // only configure once for each module.
1505             if (!roots.contains(mv.moduleInfo().id()))
1506                 roots.add(mv.moduleInfo().id());
1507         }
1508 
1509         for (ModuleId mid : roots) {
1510             // ## We could be a lot more clever about this!
1511             Configuration<Context> cf
1512                 = Configurator.configure(this, mid.toQuery());
1513             new StoredConfiguration(findModuleDir(mid), cf).store();
1514         }
1515     }
1516 
1517     public URI findLocalResource(ModuleId mid, String name)
1518         throws IOException
1519     {
1520         return locateContent(mid, name);
1521     }
1522 
1523     public File findLocalNativeLibrary(ModuleId mid, String name)
1524         throws IOException
1525     {
1526         File f = natlibs();
1527         if (f == null) {
1528             f = findModuleDir(mid);
1529             if (f == null)
1530                 return null;
1531             f = new File(f, "lib");
1532         }
1533         f = new File(f, name);
1534         if (!f.exists())
1535             return null;
1536         return f;
1537     }
1538 
1539     public File classPath(ModuleId mid)
1540         throws IOException
1541     {
1542         File md = findModuleDir(mid);
1543         if (md == null) {
1544             if (parent != null)
1545                 return parent.classPath(mid);
1546             return null;
1547         }
1548         // ## Check for other formats here
1549         return new File(md, "classes");
1550     }
1551 
1552     /**
1553      * <p> Re-index the classes of the named previously-installed modules, and
1554      * then update the configurations of any affected root modules. </p>
1555      *
1556      * <p> This method is intended for use during development, when a build
1557      * process may update a previously-installed module in place, adding or
1558      * removing classes. </p>
1559      *
1560      * @param   mids
1561      *          The module ids of the new or updated modules, or
1562      *          {@code null} if the configuration of every root module
1563      *          should be (re)computed
1564      */
1565     public void reIndex(List<ModuleId> mids)
1566         throws ConfigurationException, IOException
1567     {
1568         for (ModuleId mid : mids)
1569             reIndex(mid);
1570         configure(mids);
1571     }
1572 
1573 
1574     // -- Repositories --
1575 
1576     private static class RepoList
1577         implements RemoteRepositoryList
1578     {
1579 
1580         private static final int MINOR_VERSION = 0;
1581         private static final int MAJOR_VERSION = 0;
1582 
1583         private final File root;
1584         private final File listFile;
1585 
1586         private RepoList(File r) {
1587             root = new File(r, FileConstants.META_PREFIX + "repos");
1588             listFile = new File(root, FileConstants.META_PREFIX + "list");
1589         }
1590 
1591         private static FileHeader fileHeader() {
1592             return (new FileHeader()
1593                     .type(FileConstants.Type.REMOTE_REPO_LIST)
1594                     .majorVersion(MAJOR_VERSION)
1595                     .minorVersion(MINOR_VERSION));
1596         }
1597 
1598         private List<RemoteRepository> repos = null;
1599         private long nextRepoId = 0;
1600 
1601         private File repoDir(long id) {
1602             return new File(root, Long.toHexString(id));
1603         }
1604 
1605         private void load() throws IOException {
1606 
1607             repos = new ArrayList<>();
1608             if (!root.exists() || !listFile.exists())
1609                 return;
1610             FileInputStream fin = new FileInputStream(listFile);
1611             DataInputStream in
1612                 = new DataInputStream(new BufferedInputStream(fin));
1613             try {
1614 
1615                 FileHeader fh = fileHeader();
1616                 fh.read(in);
1617                 nextRepoId = in.readLong();
1618                 int n = in.readInt();
1619                 long[] ids = new long[n];
1620                 for (int i = 0; i < n; i++)
1621                     ids[i] = in.readLong();
1622                 RemoteRepository parent = null;
1623 
1624                 // Load in reverse order so that parents are correct
1625                 for (int i = n - 1; i >= 0; i--) {
1626                     long id = ids[i];
1627                     RemoteRepository rr
1628                         = RemoteRepository.open(repoDir(id), id, parent);
1629                     repos.add(rr);
1630                     parent = rr;
1631                 }
1632                 Collections.reverse(repos);
1633 
1634             } finally {
1635                 in.close();
1636             }
1637 
1638         }
1639 
1640         private List<RemoteRepository> roRepos = null;
1641 
1642         // Unmodifiable
1643         public List<RemoteRepository> repositories() throws IOException {
1644             if (repos == null) {
1645                 load();
1646                 roRepos = Collections.unmodifiableList(repos);
1647             }
1648             return roRepos;
1649         }
1650 
1651         public RemoteRepository firstRepository() throws IOException {
1652             repositories();
1653             return repos.isEmpty() ? null : repos.get(0);
1654         }
1655 
1656         private void store() throws IOException {
1657             File newfn = new File(root, "list.new");
1658             FileOutputStream fout = new FileOutputStream(newfn);
1659             DataOutputStream out
1660                 = new DataOutputStream(new BufferedOutputStream(fout));
1661             try {
1662                 try {
1663                     fileHeader().write(out);
1664                     out.writeLong(nextRepoId);
1665                     out.writeInt(repos.size());
1666                     for (RemoteRepository rr : repos)
1667                         out.writeLong(rr.id());
1668                 } finally {
1669                     out.close();
1670                 }
1671             } catch (IOException x) {
1672                 newfn.delete();
1673                 throw x;
1674             }
1675             java.nio.file.Files.move(newfn.toPath(), listFile.toPath(), ATOMIC_MOVE);
1676         }
1677 
1678         public RemoteRepository add(URI u, int position)
1679             throws IOException
1680         {
1681 
1682             if (repos == null)
1683                 load();
1684             for (RemoteRepository rr : repos) {
1685                 if (rr.location().equals(u)) // ## u not canonical
1686                     throw new IllegalStateException(u + ": Already in"
1687                                                     + " repository list");
1688             }
1689             if (!root.exists()) {
1690                 if (!root.mkdir())
1691                     throw new IOException(root + ": Cannot create directory");
1692             }
1693 
1694             if (repos.size() == Integer.MAX_VALUE)
1695                 throw new IllegalStateException("Too many repositories");
1696             if (position < 0)
1697                 throw new IllegalArgumentException("Invalid index");
1698 
1699             long id = nextRepoId++;
1700             RemoteRepository rr = RemoteRepository.create(repoDir(id), u, id);
1701             try {
1702                 rr.updateCatalog(true);
1703             } catch (IOException x) {
1704                 rr.delete();
1705                 nextRepoId--;
1706                 throw x;
1707             }
1708 
1709             if (position >= repos.size()) {
1710                 repos.add(rr);
1711             } else if (position >= 0) {
1712                 List<RemoteRepository> prefix
1713                     = new ArrayList<>(repos.subList(0, position));
1714                 List<RemoteRepository> suffix
1715                     = new ArrayList<>(repos.subList(position, repos.size()));
1716                 repos.clear();
1717                 repos.addAll(prefix);
1718                 repos.add(rr);
1719                 repos.addAll(suffix);
1720             }
1721             store();
1722 
1723             return rr;
1724 
1725         }
1726 
1727         public boolean remove(RemoteRepository rr)
1728             throws IOException
1729         {
1730             if (!repos.remove(rr))
1731                 return false;
1732             store();
1733             File rd = repoDir(rr.id());
1734             for (File f : rd.listFiles()) {
1735                 if (!f.delete())
1736                     throw new IOException(f + ": Cannot delete");
1737             }
1738             if (!rd.delete())
1739                 throw new IOException(rd + ": Cannot delete");
1740             return true;
1741         }
1742 
1743         public boolean areCatalogsStale() throws IOException {
1744             for (RemoteRepository rr : repos) {
1745                 if (rr.isCatalogStale())
1746                     return true;
1747             }
1748             return false;
1749         }
1750 
1751         public boolean updateCatalogs(boolean force) throws IOException {
1752             boolean updated = false;
1753             for (RemoteRepository rr : repos) {
1754                 if (rr.updateCatalog(force))
1755                     updated = true;
1756             }
1757             return updated;
1758         }
1759 
1760     }
1761 
1762     private RemoteRepositoryList repoList = null;
1763 
1764     public RemoteRepositoryList repositoryList()
1765         throws IOException
1766     {
1767         if (repoList == null)
1768             repoList = new RepoList(root);
1769         return repoList;
1770     }
1771 
1772 }
--- EOF ---