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                 // Local service implementations
 503                 Map<String,Set<String>> services = cx.services();
 504                 out.writeInt(services.size());
 505                 for (Map.Entry<String,Set<String>> me: services.entrySet()) {
 506                     out.writeUTF(me.getKey());
 507                     Set<String> values = me.getValue();
 508                     out.writeInt(values.size());
 509                     for (String value: values) {
 510                         out.writeUTF(value);
 511                     }
 512                 }
 513                 
 514                 // Remote service suppliers
 515                 Map<String,Set<String>> serviceSuppliers = cx.serviceSuppliers();
 516                 out.writeInt(serviceSuppliers.size());
 517                 for (Map.Entry<String,Set<String>> entry: serviceSuppliers.entrySet()) {
 518                     out.writeUTF(entry.getKey());
 519                     Set<String> remotes = entry.getValue();
 520                     out.writeInt(remotes.size());
 521                     for (String rcxn: remotes) {
 522                         out.writeUTF(rcxn);
 523                     }
 524                 }                
 525 
 526             }
 527         }
 528 
 529         protected void loadRest(DataInputStream in)
 530             throws IOException
 531         {
 532             // Roots
 533             int nRoots = in.readInt();
 534             List<ModuleId> roots = new ArrayList<>();
 535             for (int i = 0; i < nRoots; i++) {
 536                 String root = in.readUTF();
 537                 ModuleId rmid = jms.parseModuleId(root);
 538                 roots.add(rmid);
 539             }
 540             cf = new Configuration<Context>(roots);
 541             // Contexts
 542             int nContexts = in.readInt();
 543             for (int i = 0; i < nContexts; i++) {
 544                 Context cx = new Context();
 545                 String cxn = in.readUTF();
 546                 // Module ids
 547                 int nModules = in.readInt();
 548                 for (int j = 0; j < nModules; j++) {
 549                     ModuleId mid = jms.parseModuleId(in.readUTF());
 550                     String lps = in.readUTF();
 551                     if (lps.length() > 0)
 552                         cx.putLibraryPathForModule(mid, new File(lps));
 553                     // Module Views
 554                     int nViews = in.readInt();
 555                     Set<ModuleId> views = new HashSet<>();
 556                     for (int k = 0; k < nViews; k++) {
 557                         ModuleId id = jms.parseModuleId(in.readUTF());
 558                         views.add(id);
 559                         cf.put(id.name(), cx);
 560                     }
 561                     cx.add(mid, views);
 562                 }
 563                 cx.freeze();
 564                 assert cx.name().equals(cxn);
 565                 cf.add(cx);
 566                 // Local class map
 567                 int nClasses = in.readInt();
 568                 for (int j = 0; j < nClasses; j++)
 569                     cx.putModuleForLocalClass(in.readUTF(),
 570                                               jms.parseModuleId(in.readUTF()));
 571                 // Remote package map
 572                 int nPackages = in.readInt();
 573                 for (int j = 0; j < nPackages; j++)
 574                     cx.putContextForRemotePackage(in.readUTF(), in.readUTF());
 575 
 576                 // Suppliers
 577                 int nSuppliers = in.readInt();
 578                 for (int j = 0; j < nSuppliers; j++)
 579                     cx.addSupplier(in.readUTF());             
 580                 
 581                 // Local service implementations
 582                 int nServices = in.readInt();
 583                 for (int j = 0; j < nServices; j++) {
 584                     String sn = in.readUTF();
 585                     int nImpl = in.readInt();
 586                     for (int k = 0; k < nImpl; k++) {
 587                         String cn = in.readUTF();                     
 588                         cx.putService(sn, cn);
 589                     }
 590                 }        
 591                 
 592                 // Remote service suppliers
 593                 int nRemoteServices = in.readInt();
 594                 for (int j = 0; j < nRemoteServices; j++) {
 595                     String sn = in.readUTF();
 596                     int nRemotes = in.readInt();
 597                     for (int k = 0; k < nRemotes; k++) {
 598                         String rcxn = in.readUTF();
 599                         cx.addServiceSupplier(sn, rcxn);
 600                     } 
 601                 } 
 602             }
 603 
 604         }
 605 
 606         private static StoredConfiguration load(File f)
 607             throws IOException
 608         {
 609             StoredConfiguration sp = new StoredConfiguration(f, null);
 610             sp.load();
 611             return sp;
 612         }
 613 
 614     }
 615 
 616     private static final class Signers
 617         extends MetaData {
 618 
 619         private static String FILE = "signer";
 620         private static int MAJOR_VERSION = 0;
 621         private static int MINOR_VERSION = 1;
 622 
 623         private CertificateFactory cf = null;
 624         private Set<CodeSigner> signers;
 625         private Set<CodeSigner> signers() { return signers; }
 626 
 627         private Signers(File root, Set<CodeSigner> signers) {
 628             super(MAJOR_VERSION, MINOR_VERSION,
 629                   FileConstants.Type.LIBRARY_MODULE_SIGNER,
 630                   new File(root, FILE));
 631             this.signers = signers;
 632         }
 633 
 634         protected void storeRest(DataOutputStream out)
 635             throws IOException
 636         {
 637             out.writeInt(signers.size());
 638             for (CodeSigner signer : signers) {
 639                 try {
 640                     CertPath signerCertPath = signer.getSignerCertPath();
 641                     out.write(signerCertPath.getEncoded("PkiPath"));
 642                     Timestamp ts = signer.getTimestamp();
 643                     out.writeByte((ts != null) ? 1 : 0);
 644                     if (ts != null) {
 645                         out.writeLong(ts.getTimestamp().getTime());
 646                         out.write(ts.getSignerCertPath().getEncoded("PkiPath"));
 647                     }
 648                 } catch (CertificateEncodingException cee) {
 649                     throw new IOException(cee);
 650                 }
 651             }
 652         }
 653 
 654         protected void loadRest(DataInputStream in)
 655             throws IOException
 656         {
 657             int size = in.readInt();
 658             for (int i = 0; i < size; i++) {
 659                 try {
 660                     if (cf == null)
 661                         cf = CertificateFactory.getInstance("X.509");
 662                     CertPath signerCertPath = cf.generateCertPath(in, "PkiPath");
 663                     int b = in.readByte();
 664                     if (b != 0) {
 665                         Date timestamp = new Date(in.readLong());
 666                         CertPath tsaCertPath = cf.generateCertPath(in, "PkiPath");
 667                         Timestamp ts = new Timestamp(timestamp, tsaCertPath);
 668                         signers.add(new CodeSigner(signerCertPath, ts));
 669                     } else {
 670                         signers.add(new CodeSigner(signerCertPath, null));
 671                     }
 672                 } catch (CertificateException ce) {
 673                     throw new IOException(ce);
 674                 }
 675             }
 676         }
 677 
 678         private static Signers load(File f)
 679             throws IOException
 680         {
 681             Signers signers = new Signers(f, new HashSet<CodeSigner>());
 682             signers.load();
 683             return signers;
 684         }
 685     }
 686 
 687     private void gatherLocalModuleIds(File mnd, Set<ModuleId> mids)
 688         throws IOException
 689     {
 690         if (!mnd.isDirectory())
 691             throw new IOException(mnd + ": Not a directory");
 692         if (!mnd.canRead())
 693             throw new IOException(mnd + ": Not readable");
 694         for (String v : mnd.list()) {
 695             mids.add(jms.parseModuleId(mnd.getName(), v));
 696         }
 697     }
 698 
 699     private void gatherLocalModuleIds(Set<ModuleId> mids)
 700         throws IOException
 701     {
 702         File[] mnds = root.listFiles();
 703         for (File mnd : mnds) {
 704             if (mnd.getName().startsWith(FileConstants.META_PREFIX))
 705                 continue;
 706             gatherLocalModuleIds(mnd, mids);
 707         }
 708     }
 709 
 710     protected void gatherLocalModuleIds(String moduleName,
 711                                         Set<ModuleId> mids)
 712         throws IOException
 713     {
 714         if (moduleName == null) {
 715             gatherLocalModuleIds(mids);
 716             return;
 717         }
 718         File mnd = new File(root, moduleName);
 719         if (mnd.exists())
 720             gatherLocalModuleIds(mnd, mids);
 721     }
 722 
 723     private void checkModuleId(ModuleId mid) {
 724         Version v = mid.version();
 725         if (v == null)
 726             return;
 727         if (!(v instanceof JigsawVersion))
 728             throw new IllegalArgumentException(mid + ": Not a Jigsaw module id");
 729     }
 730         
 731     private File moduleDir(File root, ModuleId mid) {
 732         Version v = mid.version();
 733         String vs = (v != null) ? v.toString() : "default";
 734         return new File(new File(root, mid.name()), vs);
 735     }
 736 
 737     private void checkModuleDir(File md)
 738         throws IOException
 739     {
 740         if (!md.isDirectory())
 741             throw new IOException(md + ": Not a directory");
 742         if (!md.canRead())
 743             throw new IOException(md + ": Not readable");
 744     }
 745 
 746     private File findModuleDir(ModuleId mid)
 747         throws IOException
 748     {
 749         checkModuleId(mid);
 750         File md = moduleDir(root, mid);
 751         if (!md.exists())
 752             return null;
 753         checkModuleDir(md);
 754                 
 755         // mid may be a view or alias of a module
 756         byte[] mib = Files.load(new File(md, "info"));
 757         ModuleInfo mi = jms.parseModuleInfo(mib);
 758         if (!mid.equals(mi.id())) {
 759             md = moduleDir(root, mi.id());
 760             if (!md.exists())
 761                 throw new IOException(mid + ": " + md + " does not exist");
 762             checkModuleDir(md);
 763         }
 764         return md;
 765     }
 766 
 767     private File makeModuleDir(File root, ModuleInfo mi)
 768         throws ConfigurationException, IOException
 769     {
 770         // view name is unique
 771         for (ModuleView mv : mi.views()) {
 772             File md = moduleDir(root, mv.id());
 773             if (md.exists()) {
 774                 throw new ConfigurationException("module view " +
 775                     mv.id() + " already installed");
 776             }
 777             if (!md.mkdirs()) {
 778                 throw new IOException(md + ": Cannot create");
 779             }
 780         }
 781 
 782         return moduleDir(root, mi.id());
 783     }
 784     
 785     private void deleteModuleDir(File root, ModuleInfo mi)
 786         throws IOException
 787     {
 788         // delete the default view and the module content
 789         ModuleId mid = mi.defaultView().id();
 790         File md = moduleDir(root, mid);
 791         if (md.exists())
 792             ModuleFile.Reader.remove(md);
 793         // delete all views
 794         for (ModuleView mv : mi.views()) {
 795             md = moduleDir(root, mv.id());
 796             if (md.exists()) {
 797                 Files.deleteTree(md);
 798             }
 799         }
 800     }
 801 
 802     private void deleteModuleDir(ModuleId mid)
 803         throws IOException
 804     {
 805         checkModuleId(mid);
 806         File md = moduleDir(root, mid);
 807         if (!md.exists())
 808             return;
 809         checkModuleDir(md);
 810 
 811         // mid may be a view or alias of a module
 812         byte[] mib = Files.load(new File(md, "info"));
 813         ModuleInfo mi = jms.parseModuleInfo(mib);
 814         if (!mid.equals(mi.id())) {
 815             throw new IOException(mi.id() + " found in the module directory for " + mid);
 816         }
 817         deleteModuleDir(root, mi);
 818     }
 819 
 820     private void copyModuleInfo(File root, ModuleInfo mi, byte[] mib)
 821         throws IOException
 822     {
 823         for (ModuleView mv : mi.views()) {
 824             if (mv.id().equals(mi.id())) {
 825                 continue;
 826             }
 827 
 828             File mvd = moduleDir(root, mv.id());
 829             Files.store(mib, new File(mvd, "info"));
 830         }
 831     }
 832     public byte[] readLocalModuleInfoBytes(ModuleId mid)
 833         throws IOException
 834     {
 835         File md = findModuleDir(mid);
 836         if (md == null)
 837             return null;
 838         return Files.load(new File(md, "info"));
 839     }
 840 
 841     public CodeSigner[] readLocalCodeSigners(ModuleId mid)
 842         throws IOException
 843     {
 844         File md = findModuleDir(mid);
 845         if (md == null)
 846             return null;
 847         // Only one signer is currently supported
 848         File f = new File(md, "signer");
 849         // ## concurrency issues : what is the expected behavior if file is
 850         // ## removed by another thread/process here?
 851         if (!f.exists())
 852             return null;
 853         return Signers.load(md).signers().toArray(new CodeSigner[0]);
 854     }
 855 
 856     // ## Close all zip files when we close this library
 857     private Map<ModuleId, Object> contentForModule = new HashMap<>();
 858     private Object NONE = new Object();
 859 
 860     private Object findContent(ModuleId mid)
 861         throws IOException
 862     {
 863         Object o = contentForModule.get(mid);
 864         if (o != null)
 865             return o;
 866         if (o == NONE)
 867             return null;
 868         File md = findModuleDir(mid);
 869         if (md == null) {
 870             contentForModule.put(mid, NONE);
 871             return null;
 872         }
 873         File cf = new File(md, "classes");
 874         if (cf.isFile()) {
 875             ZipFile zf = new ZipFile(cf);
 876             contentForModule.put(mid, zf);
 877             return zf;
 878         }
 879         if (cf.isDirectory()) {
 880             contentForModule.put(mid, cf);
 881             return cf;
 882         }
 883         contentForModule.put(mid, NONE);
 884         return null;
 885     }
 886 
 887     private byte[] loadContent(ZipFile zf, String path)
 888         throws IOException
 889     {
 890         ZipEntry ze = zf.getEntry(path);
 891         if (ze == null)
 892             return null;
 893         return Files.load(zf.getInputStream(ze), (int)ze.getSize());
 894     }
 895 
 896     private byte[] loadContent(ModuleId mid, String path)
 897         throws IOException
 898     {
 899         Object o = findContent(mid);
 900         if (o == null)
 901             return null;
 902         if (o instanceof ZipFile) {
 903             ZipFile zf = (ZipFile)o;
 904             ZipEntry ze = zf.getEntry(path);
 905             if (ze == null)
 906                 return null;
 907             return Files.load(zf.getInputStream(ze), (int)ze.getSize());
 908         }
 909         if (o instanceof File) {
 910             File f = new File((File)o, path);
 911             if (!f.exists())
 912                 return null;
 913             return Files.load(f);
 914         }
 915         assert false;
 916         return null;
 917     }
 918 
 919     private URI locateContent(ModuleId mid, String path)
 920         throws IOException
 921     {
 922         Object o = findContent(mid);
 923         if (o == null)
 924             return null;
 925         if (o instanceof ZipFile) {
 926             ZipFile zf = (ZipFile)o;
 927             ZipEntry ze = zf.getEntry(path);
 928             if (ze == null)
 929                 return null;
 930             return URI.create("jar:"
 931                               + new File(zf.getName()).toURI().toString()
 932                               + "!/" + path);
 933         }
 934         if (o instanceof File) {
 935             File f = new File((File)o, path);
 936             if (!f.exists())
 937                 return null;
 938             return f.toURI();
 939         }
 940         assert false;
 941         return null;
 942     }
 943 
 944     public byte[] readLocalClass(ModuleId mid, String className)
 945         throws IOException
 946     {
 947         return loadContent(mid, className.replace('.', '/') + ".class");
 948     }
 949 
 950     public List<String> listLocalClasses(ModuleId mid, boolean all)
 951         throws IOException
 952     {
 953         File md = findModuleDir(mid);
 954         if (md == null)
 955             return null;
 956         Index ix = Index.load(md);
 957         int os = all ? ix.otherClasses().size() : 0;
 958         ArrayList<String> cns
 959             = new ArrayList<String>(ix.publicClasses().size() + os);
 960         cns.addAll(ix.publicClasses());
 961         if (all)
 962             cns.addAll(ix.otherClasses());
 963         return cns;
 964     }
 965 
 966     public Configuration<Context> readConfiguration(ModuleId mid)
 967         throws IOException
 968     {
 969         File md = findModuleDir(mid);
 970         if (md == null) {
 971             if (parent != null)
 972                 return parent.readConfiguration(mid);
 973             return null;
 974         }
 975         StoredConfiguration scf = StoredConfiguration.load(md);
 976         return scf.cf;
 977     }
 978 
 979     private boolean addToIndex(ClassInfo ci, Index ix)
 980         throws IOException
 981     {
 982         if (ci.isModuleInfo())
 983             return false;
 984         if (ci.moduleName() != null) {
 985             // ## From early Jigsaw development; can probably delete now
 986             throw new IOException("Old-style class file with"
 987                                   + " module attribute");
 988         }
 989         if (ci.isPublic())
 990             ix.publicClasses().add(ci.name());
 991         else
 992             ix.otherClasses().add(ci.name());
 993         return true;
 994     }
 995 
 996     private void reIndex(ModuleId mid)
 997         throws IOException
 998     {
 999 
1000         File md = findModuleDir(mid);
1001         if (md == null)
1002             throw new IllegalArgumentException(mid + ": No such module");
1003         File cd = new File(md, "classes");
1004         final Index ix = new Index(md);
1005 
1006         if (cd.isDirectory()) {
1007             Files.walkTree(cd, new Files.Visitor<File>() {
1008                 public void accept(File f) throws IOException {
1009                     if (f.getPath().endsWith(".class"))
1010                         addToIndex(ClassInfo.read(f), ix);
1011                 }
1012             });
1013         } else if (cd.isFile()) {
1014             FileInputStream fis = new FileInputStream(cd);
1015             ZipInputStream zis = new ZipInputStream(fis);
1016             ZipEntry ze;
1017             while ((ze = zis.getNextEntry()) != null) {
1018                 if (!ze.getName().endsWith(".class"))
1019                     continue;
1020                 addToIndex(ClassInfo.read(Files.nonClosingStream(zis),
1021                                           ze.getSize(),
1022                                           mid + ":" + ze.getName()),
1023                            ix);
1024             }
1025         }
1026 
1027         ix.store();
1028     }
1029 
1030     /**
1031      * Strip the debug attributes from the classes in a given module
1032      * directory.
1033      */
1034     private void strip(File md) throws IOException {
1035         File classes = new File(md, "classes");
1036         if (classes.isFile()) {
1037             File pf = new File(md, "classes.pack");
1038             try (JarFile jf = new JarFile(classes);
1039                 FileOutputStream out = new FileOutputStream(pf))
1040             {
1041                 Pack200.Packer packer = Pack200.newPacker();
1042                 Map<String,String> p = packer.properties();
1043                 p.put("com.sun.java.util.jar.pack.strip.debug", Pack200.Packer.TRUE);
1044                 packer.pack(jf, out);
1045             }
1046 
1047             try (OutputStream out = new FileOutputStream(classes);
1048                  JarOutputStream jos = new JarOutputStream(out))
1049             {
1050                 Pack200.Unpacker unpacker = Pack200.newUnpacker();
1051                 unpacker.unpack(pf, jos);
1052             } finally {
1053                 pf.delete();
1054            }
1055         }
1056     }
1057 
1058     private void install(Manifest mf, File dst, boolean strip)
1059         throws IOException
1060     {
1061         if (mf.classes().size() > 1)
1062             throw new IllegalArgumentException("Multiple module-class"
1063                                                + " directories"
1064                                                + " not yet supported");
1065         if (mf.classes().size() < 1)
1066             throw new IllegalArgumentException("At least one module-class"
1067                                                + " directory required");
1068         File classes = mf.classes().get(0);
1069         final String mn = mf.module();
1070 
1071         File mif = new File(classes, "module-info.class");
1072         File src = null;
1073         if (mif.exists()) {
1074             src = classes;
1075         } else {
1076             src = new File(classes, mn);
1077             mif = new File(src, "module-info.class");
1078         }
1079         byte[] bs =  Files.load(mif);
1080         ModuleInfo mi = jms.parseModuleInfo(bs);
1081         if (!mi.id().name().equals(mn)) {
1082             // ## Need a more appropriate throwable here
1083             throw new Error(mif + " is for module " + mi.id().name()
1084                             + ", not " + mn);
1085         }
1086         String m = mi.id().name();
1087         JigsawVersion v = (JigsawVersion)mi.id().version();
1088         String vs = (v == null) ? "default" : v.toString();
1089         deleteModuleDir(dst, mi);
1090 
1091          // view name is unique
1092         for (ModuleView mv : mi.views()) {
1093             File md = moduleDir(dst, mv.id());
1094             if (!md.mkdirs()) {
1095                 throw new IOException(md + ": Cannot create");
1096             }
1097         }
1098 
1099         File mdst = moduleDir(dst, mi.id());
1100         Files.store(bs, new File(mdst, "info"));
1101         File cldst = new File(mdst, "classes");
1102 
1103         // Delete the config file, if one exists
1104         StoredConfiguration.delete(mdst);
1105 
1106         if (false) {
1107 
1108             // ## Retained for now in case we later want to add an option
1109             // ## to install into a tree rather than a zip file
1110 
1111             // Copy class files and build index
1112             final Index ix = new Index(mdst);
1113             Files.copyTree(src, cldst, new Files.Filter<File>() {
1114                     public boolean accept(File f) throws IOException {
1115                         if (f.isDirectory())
1116                             return true;
1117                         if (f.getName().endsWith(".class")) {
1118                             return addToIndex(ClassInfo.read(f), ix);
1119                         } else {
1120                             return true;
1121                         }
1122                     }});
1123             ix.store();
1124         } else {
1125             try (FileOutputStream fos = new FileOutputStream(new File(mdst, "classes"));
1126                  JarOutputStream jos = new JarOutputStream(new BufferedOutputStream(fos)))
1127             {
1128                 // Copy class files and build index
1129                 final Index ix = new Index(mdst);
1130                 Files.storeTree(src, jos, isDeflated(), new Files.Filter<File>() {
1131                         public boolean accept(File f) throws IOException {
1132                             if (f.isDirectory())
1133                                 return true;
1134                             if (f.getName().endsWith(".class")) {
1135                                 return addToIndex(ClassInfo.read(f), ix);
1136                             } else {
1137                                 return true;
1138                             }
1139                         }});
1140                 ix.store();
1141                 copyModuleInfo(dst, mi, bs);
1142             }
1143             if (strip)
1144                 strip(mdst);
1145         }
1146 
1147     }
1148 
1149     private void install(Collection<Manifest> mfs, File dst, boolean strip)
1150         throws IOException
1151     {
1152         for (Manifest mf : mfs)
1153             install(mf, dst, strip);
1154     }
1155 
1156     public void installFromManifests(Collection<Manifest> mfs, boolean strip)
1157         throws ConfigurationException, IOException
1158     {
1159         install(mfs, root, strip);
1160         configure(null);
1161     }
1162 
1163     @Override
1164     public void installFromManifests(Collection<Manifest> mfs)
1165         throws ConfigurationException, IOException
1166     {
1167         installFromManifests(mfs, false);
1168     }
1169 
1170     private ModuleFileVerifier.Parameters mfvParams;
1171     private ModuleId install(InputStream is, boolean verifySignature, boolean strip)
1172         throws ConfigurationException, IOException, SignatureException
1173     {
1174         BufferedInputStream bin = new BufferedInputStream(is);
1175         DataInputStream in = new DataInputStream(bin);
1176         ModuleInfo mi = null;
1177         try (ModuleFile.Reader mr = new ModuleFile.Reader(in)) {
1178             byte[] mib = mr.readStart();
1179             mi = jms.parseModuleInfo(mib);        
1180             File md = makeModuleDir(root, mi);
1181             if (verifySignature && mr.hasSignature()) {
1182                 ModuleFileVerifier mfv = new SignedModule.PKCS7Verifier(mr);
1183                 if (mfvParams == null) {
1184                     mfvParams = new SignedModule.VerifierParameters();
1185                 }
1186                 // Verify the module signature and validate the signer's
1187                 // certificate chain
1188                 Set<CodeSigner> signers = mfv.verifySignature(mfvParams);
1189 
1190                 // Verify the module header hash and the module info hash
1191                 mfv.verifyHashesStart(mfvParams);
1192 
1193                 // ## Check policy - is signer trusted and what permissions
1194                 // ## should be granted?
1195 
1196                 // Store signer info
1197                 new Signers(md, signers).store();
1198 
1199                 // Read and verify the rest of the hashes
1200                 mr.readRest(md, isDeflated(), natlibs(), natcmds(), configs());
1201                 mfv.verifyHashesRest(mfvParams);
1202             } else {
1203                 mr.readRest(md, isDeflated(), natlibs(), natcmds(), configs());
1204             }
1205 
1206             if (strip)
1207                 strip(md);
1208             reIndex(mi.id());         // ## Could do this while reading module file
1209 
1210             // copy module-info.class to each view
1211             copyModuleInfo(root, mi, mib);
1212             return mi.id();
1213 
1214         } catch (IOException | SignatureException x) {
1215             if (mi != null) {
1216                 try {
1217                     deleteModuleDir(root, mi);
1218                 } catch (IOException y) {
1219                     y.initCause(x);
1220                     throw y;
1221                 }
1222             }
1223             throw x;
1224         }
1225     }
1226 
1227     private ModuleId installFromJarFile(File mf, boolean verifySignature, boolean strip)
1228         throws ConfigurationException, IOException, SignatureException
1229     {
1230         ModuleInfo mi = null;
1231         try (JarFile jf = new JarFile(mf, verifySignature)) {
1232             mi = jf.getModuleInfo();
1233             if (mi == null)
1234                 throw new ConfigurationException(mf + ": not a modular JAR file");
1235 
1236             File md = makeModuleDir(root, mi);
1237             ModuleId mid = mi.id();
1238 
1239             boolean signed = false;
1240 
1241             // copy the jar file to the module library
1242             File classesDir = new File(md, "classes");
1243             try (FileOutputStream fos = new FileOutputStream(classesDir);
1244                  BufferedOutputStream bos = new BufferedOutputStream(fos);
1245                  JarOutputStream jos = new JarOutputStream(bos)) {
1246                 jos.setLevel(0);
1247 
1248                 Enumeration<JarEntry> entries = jf.entries();
1249                 while (entries.hasMoreElements()) {
1250                     JarEntry je = entries.nextElement();
1251                     try (InputStream is = jf.getInputStream(je)) {
1252                         if (je.getName().equals(JarFile.MODULEINFO_NAME)) {                            
1253                             java.nio.file.Files.copy(is, md.toPath().resolve("info"));
1254                         } else {
1255                             writeJarEntry(is, je, jos);
1256                         }
1257                     }
1258                     if (!signed) {
1259                         String name = je.getName().toUpperCase(Locale.ENGLISH);
1260                         signed = name.startsWith("META-INF/")
1261                                  && name.endsWith(".SF");
1262                     }
1263                 }
1264             }
1265 
1266             try {
1267                 if (verifySignature && signed) {
1268                     // validate the code signers
1269                     Set<CodeSigner> signers = getSigners(jf);
1270                     SignedModule.validateSigners(signers);
1271                     // store the signers
1272                     new Signers(md, signers).store();
1273                 }
1274             } catch (CertificateException ce) {
1275                 throw new SignatureException(ce);
1276             }
1277 
1278             if (strip)
1279                 strip(md);
1280             reIndex(mid);
1281             
1282             // copy module-info.class to each view
1283             byte[] mib = java.nio.file.Files.readAllBytes(md.toPath().resolve("info"));
1284             copyModuleInfo(root, mi, mib);
1285             return mid;
1286         } catch (IOException | SignatureException x) {
1287             if (mi != null) {
1288                 try {
1289                     deleteModuleDir(root, mi);
1290                 } catch (IOException y) {
1291                     y.initCause(x);
1292                     throw y;
1293                 }
1294             }
1295             throw x;
1296         }
1297     }
1298 
1299     /**
1300      * Returns the set of signers of the specified jar file. Each signer
1301      * must have signed all relevant entries.
1302      */
1303     private static Set<CodeSigner> getSigners(JarFile jf)
1304         throws SignatureException
1305     {
1306         Set<CodeSigner> signers = new HashSet<>();
1307         Enumeration<JarEntry> entries = jf.entries();
1308         while (entries.hasMoreElements()) {
1309             JarEntry je = entries.nextElement();
1310             String name = je.getName().toUpperCase(Locale.ENGLISH);
1311             if (name.endsWith("/") || isSigningRelated(name))
1312                 continue;
1313 
1314             // A signed modular jar can be signed by multiple signers.
1315             // However, all entries must be signed by each of these signers.
1316             // Signers that only sign a subset of entries are ignored.
1317             CodeSigner[] jeSigners = je.getCodeSigners();
1318             if (jeSigners == null || jeSigners.length == 0)
1319                 throw new SignatureException("Found unsigned entry in "
1320                                              + "signed modular JAR");
1321 
1322             Set<CodeSigner> jeSignerSet =
1323                 new HashSet<>(Arrays.asList(jeSigners));
1324             if (signers.isEmpty())
1325                 signers.addAll(jeSignerSet);
1326             else {
1327                 if (signers.retainAll(jeSignerSet) && signers.isEmpty())
1328                     throw new SignatureException("No signers in common in "
1329                                                  + "signed modular JAR");
1330             }
1331         }
1332         return signers;
1333     }
1334 
1335     // true if file is part of the signature mechanism itself
1336     private static boolean isSigningRelated(String name) {
1337         if (!name.startsWith("META-INF/")) {
1338             return false;
1339         }
1340         name = name.substring(9);
1341         if (name.indexOf('/') != -1) {
1342             return false;
1343         }
1344         if (name.endsWith(".DSA") ||
1345             name.endsWith(".RSA") ||
1346             name.endsWith(".SF")  ||
1347             name.endsWith(".EC")  ||
1348             name.startsWith("SIG-") ||
1349             name.equals("MANIFEST.MF")) {
1350             return true;
1351         }
1352         return false;
1353     }
1354 
1355     private void writeJarEntry(InputStream is, JarEntry je, JarOutputStream jos)
1356         throws IOException, SignatureException
1357     {
1358         JarEntry entry = new JarEntry(je.getName());
1359         entry.setMethod(isDeflated() ? ZipEntry.DEFLATED : ZipEntry.STORED);
1360         entry.setTime(je.getTime());
1361         try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
1362             int size = 0;
1363             byte[] bs = new byte[1024];
1364             int cc = 0;
1365             // This will throw a SecurityException if a signature is invalid.
1366             while ((cc = is.read(bs)) > 0) {
1367                 baos.write(bs, 0, cc);
1368                 size += cc;
1369             }
1370             if (!isDeflated()) {
1371                 entry.setSize(size);
1372                 entry.setCrc(je.getCrc());
1373                 entry.setCompressedSize(size);
1374             }
1375             jos.putNextEntry(entry);
1376             if (baos.size() > 0)
1377                 baos.writeTo(jos);
1378             jos.closeEntry();
1379         } catch (SecurityException se) {
1380             throw new SignatureException(se);
1381         }
1382     }
1383 
1384     private ModuleId install(File mf, boolean verifySignature, boolean strip)
1385         throws ConfigurationException, IOException, SignatureException
1386     {
1387         if (mf.getName().endsWith(".jar"))
1388             return installFromJarFile(mf, verifySignature, strip);
1389         else {
1390             // Assume jmod file
1391             try (FileInputStream in = new FileInputStream(mf)) {
1392                 return install(in, verifySignature, strip);
1393             }
1394         }
1395     }
1396 
1397     public void install(Collection<File> mfs, boolean verifySignature, boolean strip)
1398         throws ConfigurationException, IOException, SignatureException
1399     {
1400         List<ModuleId> mids = new ArrayList<>();
1401         boolean complete = false;
1402         Throwable ox = null;
1403         try {
1404             for (File mf : mfs)
1405                 mids.add(install(mf, verifySignature, strip));
1406             configure(mids);
1407             complete = true;
1408         } catch (IOException|ConfigurationException x) {
1409             ox = x;
1410             throw x;
1411         } finally {
1412             if (!complete) {
1413                 try {
1414                     for (ModuleId mid : mids)
1415                         deleteModuleDir(mid);
1416                 } catch (IOException x) {
1417                     if (ox != null)
1418                         x.initCause(ox);
1419                     throw x;
1420                 }
1421             }
1422         }
1423     }
1424 
1425     @Override
1426     public void install(Collection<File> mfs, boolean verifySignature)
1427         throws ConfigurationException, IOException, SignatureException
1428     {
1429         install(mfs, verifySignature, false);
1430     }
1431 
1432     // Public entry point, since the Resolver itself is package-private
1433     //
1434     public Resolution resolve(Collection<ModuleIdQuery> midqs)
1435         throws ConfigurationException, IOException
1436     {
1437         return Resolver.run(this, midqs);
1438     }
1439 
1440     public void install(Resolution res, boolean verifySignature, boolean strip)
1441         throws ConfigurationException, IOException, SignatureException
1442     {
1443         // ## Handle case of installing multiple root modules
1444         assert res.rootQueries.size() == 1;
1445         ModuleIdQuery midq = res.rootQueries.iterator().next();
1446         ModuleInfo root = null;
1447         for (String mn : res.moduleViewForName.keySet()) {
1448             ModuleView mv = res.moduleViewForName.get(mn);
1449             if (midq.matches(mv.id())) {
1450                 root = mv.moduleInfo();
1451                 break;
1452             }
1453         }
1454         assert root != null;
1455 
1456         // Download
1457         //
1458         for (ModuleId mid : res.modulesNeeded()) {
1459             URI u = res.locationForName.get(mid.name());
1460             assert u != null;
1461             RemoteRepository rr = repositoryList().firstRepository();
1462             assert rr != null;
1463             install(rr.fetch(mid), verifySignature, strip);
1464             res.locationForName.put(mid.name(), location());
1465             // ## If something goes wrong, delete all our modules
1466         }
1467 
1468         // Configure
1469         //
1470         Configuration<Context> cf
1471             = Configurator.configure(this, res);
1472         new StoredConfiguration(findModuleDir(root.id()), cf).store();
1473     }
1474 
1475     @Override
1476     public void install(Resolution res, boolean verifySignature)
1477         throws ConfigurationException, IOException, SignatureException
1478     {
1479         install(res, verifySignature, false);
1480     }
1481 
1482     /**
1483      * <p> Pre-install one or more modules to an arbitrary destination
1484      * directory. </p>
1485      *
1486      * <p> A pre-installed module has the same format as within the library
1487      * itself, except that there is never a configuration file. </p>
1488      *
1489      * <p> This method is provided for use by the module-packaging tool. </p>
1490      *
1491      * @param   mfs
1492      *          The manifest describing the contents of the modules to be
1493      *          pre-installed
1494      *
1495      * @param   dst
1496      *          The destination directory, with one subdirectory per module
1497      *          name, each of which contains one subdirectory per version
1498      */
1499     public void preInstall(Collection<Manifest> mfs, File dst)
1500         throws IOException
1501     {
1502         Files.mkdirs(dst, "module destination");
1503         install(mfs, dst, false);
1504     }
1505 
1506     public void preInstall(Manifest mf, File dst)
1507         throws IOException
1508     {
1509         preInstall(Collections.singleton(mf), dst);
1510     }
1511 
1512     /**
1513      * <p> Update the configurations of any root modules affected by the
1514      * copying of the named modules, in pre-installed format, into this
1515      * library. </p>
1516      *
1517      * @param   mids
1518      *          The module ids of the new or updated modules, or
1519      *          {@code null} if the configuration of every root module
1520      *          should be (re)computed
1521      */
1522     public void configure(List<ModuleId> mids)
1523         throws ConfigurationException, IOException
1524     {
1525         // ## mids not used yet
1526         List<ModuleId> roots = new ArrayList<>();
1527         for (ModuleView mv : listLocalRootModuleViews()) {
1528             // each module can have multiple entry points
1529             // only configure once for each module.
1530             if (!roots.contains(mv.moduleInfo().id()))
1531                 roots.add(mv.moduleInfo().id());
1532         }
1533 
1534         for (ModuleId mid : roots) {
1535             // ## We could be a lot more clever about this!
1536             Configuration<Context> cf
1537                 = Configurator.configure(this, mid.toQuery());
1538             new StoredConfiguration(findModuleDir(mid), cf).store();
1539         }
1540     }
1541 
1542     public URI findLocalResource(ModuleId mid, String name)
1543         throws IOException
1544     {
1545         return locateContent(mid, name);
1546     }
1547 
1548     public File findLocalNativeLibrary(ModuleId mid, String name)
1549         throws IOException
1550     {
1551         File f = natlibs();
1552         if (f == null) {
1553             f = findModuleDir(mid);
1554             if (f == null)
1555                 return null;
1556             f = new File(f, "lib");
1557         }
1558         f = new File(f, name);
1559         if (!f.exists())
1560             return null;
1561         return f;
1562     }
1563 
1564     public File classPath(ModuleId mid)
1565         throws IOException
1566     {
1567         File md = findModuleDir(mid);
1568         if (md == null) {
1569             if (parent != null)
1570                 return parent.classPath(mid);
1571             return null;
1572         }
1573         // ## Check for other formats here
1574         return new File(md, "classes");
1575     }
1576 
1577     /**
1578      * <p> Re-index the classes of the named previously-installed modules, and
1579      * then update the configurations of any affected root modules. </p>
1580      *
1581      * <p> This method is intended for use during development, when a build
1582      * process may update a previously-installed module in place, adding or
1583      * removing classes. </p>
1584      *
1585      * @param   mids
1586      *          The module ids of the new or updated modules, or
1587      *          {@code null} if the configuration of every root module
1588      *          should be (re)computed
1589      */
1590     public void reIndex(List<ModuleId> mids)
1591         throws ConfigurationException, IOException
1592     {
1593         for (ModuleId mid : mids)
1594             reIndex(mid);
1595         configure(mids);
1596     }
1597 
1598 
1599     // -- Repositories --
1600 
1601     private static class RepoList
1602         implements RemoteRepositoryList
1603     {
1604 
1605         private static final int MINOR_VERSION = 0;
1606         private static final int MAJOR_VERSION = 0;
1607 
1608         private final File root;
1609         private final File listFile;
1610 
1611         private RepoList(File r) {
1612             root = new File(r, FileConstants.META_PREFIX + "repos");
1613             listFile = new File(root, FileConstants.META_PREFIX + "list");
1614         }
1615 
1616         private static FileHeader fileHeader() {
1617             return (new FileHeader()
1618                     .type(FileConstants.Type.REMOTE_REPO_LIST)
1619                     .majorVersion(MAJOR_VERSION)
1620                     .minorVersion(MINOR_VERSION));
1621         }
1622 
1623         private List<RemoteRepository> repos = null;
1624         private long nextRepoId = 0;
1625 
1626         private File repoDir(long id) {
1627             return new File(root, Long.toHexString(id));
1628         }
1629 
1630         private void load() throws IOException {
1631 
1632             repos = new ArrayList<>();
1633             if (!root.exists() || !listFile.exists())
1634                 return;
1635             FileInputStream fin = new FileInputStream(listFile);
1636             DataInputStream in
1637                 = new DataInputStream(new BufferedInputStream(fin));
1638             try {
1639 
1640                 FileHeader fh = fileHeader();
1641                 fh.read(in);
1642                 nextRepoId = in.readLong();
1643                 int n = in.readInt();
1644                 long[] ids = new long[n];
1645                 for (int i = 0; i < n; i++)
1646                     ids[i] = in.readLong();
1647                 RemoteRepository parent = null;
1648 
1649                 // Load in reverse order so that parents are correct
1650                 for (int i = n - 1; i >= 0; i--) {
1651                     long id = ids[i];
1652                     RemoteRepository rr
1653                         = RemoteRepository.open(repoDir(id), id, parent);
1654                     repos.add(rr);
1655                     parent = rr;
1656                 }
1657                 Collections.reverse(repos);
1658 
1659             } finally {
1660                 in.close();
1661             }
1662 
1663         }
1664 
1665         private List<RemoteRepository> roRepos = null;
1666 
1667         // Unmodifiable
1668         public List<RemoteRepository> repositories() throws IOException {
1669             if (repos == null) {
1670                 load();
1671                 roRepos = Collections.unmodifiableList(repos);
1672             }
1673             return roRepos;
1674         }
1675 
1676         public RemoteRepository firstRepository() throws IOException {
1677             repositories();
1678             return repos.isEmpty() ? null : repos.get(0);
1679         }
1680 
1681         private void store() throws IOException {
1682             File newfn = new File(root, "list.new");
1683             FileOutputStream fout = new FileOutputStream(newfn);
1684             DataOutputStream out
1685                 = new DataOutputStream(new BufferedOutputStream(fout));
1686             try {
1687                 try {
1688                     fileHeader().write(out);
1689                     out.writeLong(nextRepoId);
1690                     out.writeInt(repos.size());
1691                     for (RemoteRepository rr : repos)
1692                         out.writeLong(rr.id());
1693                 } finally {
1694                     out.close();
1695                 }
1696             } catch (IOException x) {
1697                 newfn.delete();
1698                 throw x;
1699             }
1700             java.nio.file.Files.move(newfn.toPath(), listFile.toPath(), ATOMIC_MOVE);
1701         }
1702 
1703         public RemoteRepository add(URI u, int position)
1704             throws IOException
1705         {
1706 
1707             if (repos == null)
1708                 load();
1709             for (RemoteRepository rr : repos) {
1710                 if (rr.location().equals(u)) // ## u not canonical
1711                     throw new IllegalStateException(u + ": Already in"
1712                                                     + " repository list");
1713             }
1714             if (!root.exists()) {
1715                 if (!root.mkdir())
1716                     throw new IOException(root + ": Cannot create directory");
1717             }
1718 
1719             if (repos.size() == Integer.MAX_VALUE)
1720                 throw new IllegalStateException("Too many repositories");
1721             if (position < 0)
1722                 throw new IllegalArgumentException("Invalid index");
1723 
1724             long id = nextRepoId++;
1725             RemoteRepository rr = RemoteRepository.create(repoDir(id), u, id);
1726             try {
1727                 rr.updateCatalog(true);
1728             } catch (IOException x) {
1729                 repoDir(id).delete();
1730                 throw x;
1731             }
1732 
1733             if (position >= repos.size()) {
1734                 repos.add(rr);
1735             } else if (position >= 0) {
1736                 List<RemoteRepository> prefix
1737                     = new ArrayList<>(repos.subList(0, position));
1738                 List<RemoteRepository> suffix
1739                     = new ArrayList<>(repos.subList(position, repos.size()));
1740                 repos.clear();
1741                 repos.addAll(prefix);
1742                 repos.add(rr);
1743                 repos.addAll(suffix);
1744             }
1745             store();
1746 
1747             return rr;
1748 
1749         }
1750 
1751         public boolean remove(RemoteRepository rr)
1752             throws IOException
1753         {
1754             if (!repos.remove(rr))
1755                 return false;
1756             store();
1757             File rd = repoDir(rr.id());
1758             for (File f : rd.listFiles()) {
1759                 if (!f.delete())
1760                     throw new IOException(f + ": Cannot delete");
1761             }
1762             if (!rd.delete())
1763                 throw new IOException(rd + ": Cannot delete");
1764             return true;
1765         }
1766 
1767         public boolean areCatalogsStale() throws IOException {
1768             for (RemoteRepository rr : repos) {
1769                 if (rr.isCatalogStale())
1770                     return true;
1771             }
1772             return false;
1773         }
1774 
1775         public boolean updateCatalogs(boolean force) throws IOException {
1776             boolean updated = false;
1777             for (RemoteRepository rr : repos) {
1778                 if (rr.updateCatalog(force))
1779                     updated = true;
1780             }
1781             return updated;
1782         }
1783 
1784     }
1785 
1786     private RemoteRepositoryList repoList = null;
1787 
1788     public RemoteRepositoryList repositoryList()
1789         throws IOException
1790     {
1791         if (repoList == null)
1792             repoList = new RepoList(root);
1793         return repoList;
1794     }
1795 
1796 }