1 /*
   2  * Copyright (c) 2009, 2012, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package org.openjdk.jigsaw;
  27 
  28 import java.lang.module.*;
  29 import java.io.*;
  30 import java.net.URI;
  31 import java.nio.channels.FileChannel;
  32 import java.nio.file.*;
  33 import java.nio.file.attribute.BasicFileAttributes;
  34 import java.security.*;
  35 import java.security.cert.*;
  36 import java.util.*;
  37 import java.util.jar.*;
  38 import java.util.zip.*;
  39 
  40 import static java.nio.file.StandardCopyOption.*;
  41 import static java.nio.file.StandardOpenOption.*;
  42 import org.openjdk.jigsaw.Repository.ModuleType;
  43 
  44 /**
  45  * A simple module library which stores data directly in the filesystem
  46  *
  47  * @see Library
  48  */
  49 
  50 // ## TODO: Move remaining parent-searching logic upward into Library class
  51 
  52 // On-disk library layout
  53 //
  54 //   $LIB/%jigsaw-library
  55 //        com.foo.bar/1.2.3/info (= module-info.class)
  56 //                          index (list of defined classes)
  57 //                          config (resolved configuration, if a root)
  58 //                          classes/com/foo/bar/...
  59 //                          resources/com/foo/bar/...
  60 //                          lib/libbar.so
  61 //                          bin/bar
  62 //                          signer (signer's certchain & timestamp)
  63 //
  64 // ## Issue: Concurrent access to the module library
  65 // ## e.g. a module is being removed while a running application
  66 // ## is depending on it
  67 
  68 public final class SimpleLibrary
  69     extends Library
  70 {
  71 
  72     private static abstract class MetaData {
  73 
  74         protected final int maxMajorVersion;
  75         protected final int maxMinorVersion;
  76         protected int majorVersion;
  77         protected int minorVersion;
  78         private final FileConstants.Type type;
  79         private final File file;
  80 
  81         protected MetaData(int maxMajor, int maxMinor,
  82                            FileConstants.Type t, File f)
  83         {
  84             maxMajorVersion = majorVersion = maxMajor;
  85             maxMinorVersion = minorVersion = maxMinor;
  86             type = t;
  87             file = f;
  88         }
  89 
  90         protected abstract void storeRest(DataOutputStream out)
  91             throws IOException;
  92 
  93         void store() throws IOException {
  94             try (OutputStream fos = new FileOutputStream(file);
  95                  BufferedOutputStream bos = new BufferedOutputStream(fos);
  96                  DataOutputStream out = new DataOutputStream(bos)) {
  97                 out.writeInt(FileConstants.MAGIC);
  98                 out.writeShort(type.value());
  99                 out.writeShort(majorVersion);
 100                 out.writeShort(minorVersion);
 101                 storeRest(out);
 102             }
 103         }
 104 
 105         protected abstract void loadRest(DataInputStream in)
 106             throws IOException;
 107 
 108         protected void load() throws IOException {
 109             try (InputStream fis = new FileInputStream(file);
 110                  BufferedInputStream bis = new BufferedInputStream(fis);
 111                  DataInputStream in = new DataInputStream(bis)) {
 112                 if (in.readInt() != FileConstants.MAGIC)
 113                     throw new IOException(file + ": Invalid magic number");
 114                 if (in.readShort() != type.value())
 115                     throw new IOException(file + ": Invalid file type");
 116                 int maj = in.readShort();
 117                 int min = in.readShort();
 118                 if (   maj > maxMajorVersion
 119                     || (maj == maxMajorVersion && min > maxMinorVersion)) {
 120                     throw new IOException(file
 121                                           + ": Futuristic version number");
 122                 }
 123                 majorVersion = maj;
 124                 minorVersion = min;
 125                 loadRest(in);
 126             } catch (EOFException x) {
 127                 throw new IOException(file + ": Invalid library metadata", x);
 128             }
 129         }
 130     }
 131 
 132     /**
 133      * <p> Storage options supported by the {@link SimpleLibrary}
 134      */
 135     public static enum StorageOption {
 136         DEFLATED
 137     }
 138 
 139     private static final class Header
 140         extends MetaData
 141     {
 142         private static final String FILE
 143             = FileConstants.META_PREFIX + "jigsaw-library";
 144 
 145         private static final int MAJOR_VERSION = 0;
 146         private static final int MINOR_VERSION = 1;
 147 
 148         private static final int DEFLATED = 1 << 0;
 149 
 150         private File parent;
 151         // location of native libs for this library (may be outside the library)
 152         // null:default, to use a per-module 'lib' directory
 153         private File natlibs;
 154         // location of native cmds for this library (may be outside the library)
 155         // null:default, to use a per-module 'bin' directory
 156         private File natcmds;
 157         // location of config files for this library (may be outside the library)
 158         // null:default, to use a per-module 'etc' directory
 159         private File configs;
 160         private Set<StorageOption> opts;




 161 
 162         public File parent()  { return parent;  }
 163         public File natlibs() { return natlibs; }
 164         public File natcmds() { return natcmds; }
 165         public File configs() { return configs; }


 166         public boolean isDeflated() {
 167             return opts.contains(StorageOption.DEFLATED);
 168         }
 169 
 170         private Header(File root) {
 171             super(MAJOR_VERSION, MINOR_VERSION,
 172                   FileConstants.Type.LIBRARY_HEADER,
 173                   new File(root, FILE));
 174         }
 175 
 176         private Header(File root, File parent, File natlibs, File natcmds,
 177                        File configs, Set<StorageOption> opts) {

 178             this(root);
 179             this.parent = parent;
 180             this.natlibs = natlibs;
 181             this.natcmds = natcmds;
 182             this.configs = configs;


 183             this.opts = new HashSet<>(opts);
 184         }
 185 
 186         private void storePath(File p, DataOutputStream out) throws IOException {
 187             if (p != null) {
 188                 out.writeByte(1);
 189                 out.writeUTF(Files.convertSeparator(p.toString()));
 190             } else {
 191                 out.write(0);
 192             }
 193         }
 194 










 195         protected void storeRest(DataOutputStream out) throws IOException {
 196             int flags = 0;
 197             if (isDeflated())
 198                 flags |= DEFLATED;
 199             out.writeShort(flags);
 200 
 201             storePath(parent, out);
 202             storePath(natlibs, out);
 203             storePath(natcmds, out);
 204             storePath(configs, out);


 205         }
 206 
 207         private File loadPath(DataInputStream in) throws IOException {
 208             if (in.readByte() != 0)
 209                 return new File(Files.platformSeparator(in.readUTF()));
 210             return null;
 211         }
 212 







 213         protected void loadRest(DataInputStream in) throws IOException {
 214             opts = new HashSet<StorageOption>();
 215             int flags = in.readShort();
 216             if ((flags & DEFLATED) == DEFLATED)
 217                 opts.add(StorageOption.DEFLATED);
 218             parent = loadPath(in);
 219             natlibs = loadPath(in);
 220             natcmds = loadPath(in);
 221             configs = loadPath(in);


 222         }
 223 
 224         private static Header load(File f) throws IOException {
 225             Header h = new Header(f);
 226             h.load();
 227             return h;
 228         }
 229     }
 230 
 231     private final File root;
 232     private final File canonicalRoot;
 233     private final File parentPath;
 234     private final File natlibs;
 235     private final File natcmds;
 236     private final File configs;

 237     private final SimpleLibrary parent;
 238     private final Header hd;
 239     private final ModuleDictionary moduleDictionary;
 240     private final File lockf;
 241     private final File trash;
 242 
 243     public String name() { return root.toString(); }
 244     public File root() { return canonicalRoot; }
 245     public int majorVersion() { return hd.majorVersion; }
 246     public int minorVersion() { return hd.minorVersion; }
 247     public SimpleLibrary parent() { return parent; }
 248     public File natlibs() { return natlibs; }
 249     public File natcmds() { return natcmds; }
 250     public File configs() { return configs; }

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

 302 
 303         lockf = new File(root, FileConstants.META_PREFIX + "lock");
 304         trash = new File(root, TRASH);
 305         moduleDictionary = new ModuleDictionary(root);
 306     }
 307 
 308     // Creates a new library
 309     private SimpleLibrary(File path, File parentPath, File natlibs, File natcmds,
 310                           File configs, Set<StorageOption> opts)

 311         throws IOException
 312     {
 313         root = path;
 314         canonicalRoot = root.getCanonicalFile();
 315         if (root.exists()) {
 316             Files.ensureIsDirectory(root);
 317             if (root.list().length != 0)
 318                 throw new IOException(root + ": Already Exists");
 319             Files.ensureWriteable(root);
 320         } else
 321             Files.mkdirs(root, root.toString());
 322 
 323         this.parent = parentPath != null ? open(parentPath) : null;
 324         this.parentPath = parentPath != null ? this.parent.root() : null;
 325 
 326         this.natlibs = resolveAndEnsurePath(natlibs);
 327         this.natcmds = resolveAndEnsurePath(natcmds);
 328         this.configs = resolveAndEnsurePath(configs);

 329 
 330         hd = new Header(canonicalRoot, this.parentPath, relativize(this.natlibs),
 331                         relativize(this.natcmds), relativize(this.configs), opts);

 332         hd.store();
 333 
 334         lockf = new File(root, FileConstants.META_PREFIX + "lock");
 335         lockf.createNewFile();
 336         trash = new File(root, TRASH);
 337         Files.mkdirs(trash, "module library trash");
 338         moduleDictionary = new ModuleDictionary(canonicalRoot);
 339         moduleDictionary.store();
 340     }
 341 
 342     public static SimpleLibrary create(File path, File parent, File natlibs,
 343                                        File natcmds, File configs,
 344                                        Set<StorageOption> opts)

 345         throws IOException
 346     {
 347         return new SimpleLibrary(path, parent, natlibs, natcmds, configs, opts);

 348     }
 349 
 350     public static SimpleLibrary create(File path, File parent, Set<StorageOption> opts)
 351         throws IOException
 352     {
 353         return new SimpleLibrary(path, parent, null, null, null, opts);

 354     }
 355 
 356     public static SimpleLibrary create(File path, File parent)
 357         throws IOException
 358     {
 359         return SimpleLibrary.create(path, parent, Collections.<StorageOption>emptySet());
 360     }
 361 
 362     public static SimpleLibrary create(File path, Set<StorageOption> opts)
 363         throws IOException
 364     {
 365         // ## Should default parent to $JAVA_HOME/lib/modules
 366         return SimpleLibrary.create(path, null, opts);
 367     }
 368 
 369     public static SimpleLibrary open(File path)
 370         throws IOException
 371     {
 372         return new SimpleLibrary(path);
 373     }
 374 
 375     private static final JigsawModuleSystem jms
 376         = JigsawModuleSystem.instance();
 377 
 378     private static final class Index
 379         extends MetaData
 380     {
 381 
 382         private static String FILE = "index";
 383 
 384         private static int MAJOR_VERSION = 0;
 385         private static int MINOR_VERSION = 1;
 386 
 387         private Set<String> publicClasses;
 388         public Set<String> publicClasses() { return publicClasses; }
 389 
 390         private Set<String> otherClasses;
 391         public Set<String> otherClasses() { return otherClasses; }
 392 
 393         private Index(File root) {
 394             super(MAJOR_VERSION, MINOR_VERSION,
 395                   FileConstants.Type.LIBRARY_MODULE_INDEX,
 396                   new File(root, FILE));
 397             // Unsorted on input, because we don't need it sorted
 398             publicClasses = new HashSet<String>();
 399             otherClasses = new HashSet<String>();
 400         }
 401 
 402         private void storeSet(Set<String> cnset, DataOutputStream out)
 403             throws IOException
 404         {
 405             // Sorted on output, because we can afford it
 406             List<String> cns = new ArrayList<String>(cnset);
 407             Collections.sort(cns);
 408             out.writeInt(cns.size());
 409             for (String cn : cns)
 410                 out.writeUTF(cn);
 411         }
 412 
 413         protected void storeRest(DataOutputStream out)
 414             throws IOException
 415         {
 416             storeSet(publicClasses, out);
 417             storeSet(otherClasses, out);
 418         }
 419 
 420         private void loadSet(DataInputStream in, Set<String> cnset)
 421             throws IOException
 422         {
 423             int n = in.readInt();
 424             for (int i = 0; i < n; i++)
 425                 cnset.add(in.readUTF());
 426         }
 427 
 428         protected void loadRest(DataInputStream in)
 429             throws IOException
 430         {
 431             loadSet(in, publicClasses);
 432             loadSet(in, otherClasses);
 433         }
 434 
 435         private static Index load(File f)
 436             throws IOException
 437         {
 438             Index ix = new Index(f);
 439             ix.load();
 440             return ix;
 441         }
 442 
 443     }
 444 
 445     private static final class StoredConfiguration
 446         extends MetaData
 447     {
 448 
 449         private static String FILE = "config";
 450 
 451         private static int MAJOR_VERSION = 0;
 452         private static int MINOR_VERSION = 1;
 453 
 454         private Configuration<Context> cf;
 455 
 456         private static void delete(File root) {
 457             new File(root, FILE).delete();
 458         }
 459 
 460         private StoredConfiguration(File root, Configuration<Context> conf)
 461         {
 462             super(MAJOR_VERSION, MINOR_VERSION,
 463                   FileConstants.Type.LIBRARY_MODULE_CONFIG,
 464                   new File(root, FILE));
 465             cf = conf;
 466         }
 467 
 468         protected void storeRest(DataOutputStream out)
 469             throws IOException
 470         {
 471             // Roots
 472             out.writeInt(cf.roots().size());
 473             for (ModuleId mid : cf.roots()) {
 474                 out.writeUTF(mid.toString());
 475             }
 476             
 477             // Context names and package names
 478             // Store these strings only once and the subsequent sections will
 479             // reference these names by its index.
 480             List<String> cxns = new ArrayList<>();
 481             Set<String> pkgs = new HashSet<>();
 482             for (Context cx : cf.contexts()) {
 483                 String cxn = cx.name();
 484                 cxns.add(cxn);
 485                 pkgs.addAll(cx.remotePackages());
 486                 for (String cn : cx.localClasses()) {
 487                     int i = cn.lastIndexOf('.');
 488                     if (i >= 0)
 489                         pkgs.add(cn.substring(0, i));
 490                 }
 491             }
 492             List<String> packages = Arrays.asList(pkgs.toArray(new String[0]));
 493             Collections.sort(packages);
 494             out.writeInt(cf.contexts().size());
 495             for (String cxn : cxns) {
 496                 out.writeUTF(cxn);
 497             }
 498             out.writeInt(packages.size());
 499             for (String pn : packages) {
 500                 out.writeUTF(pn);
 501             }
 502             
 503             // Contexts
 504             for (Context cx : cf.contexts()) {
 505                 // Module ids, and their libraries
 506                 out.writeInt(cx.modules().size());
 507                 List<ModuleId> mids = new ArrayList<>(cx.modules());
 508                 for (ModuleId mid : mids) {
 509                     out.writeUTF(mid.toString());
 510                     File lp = cx.findLibraryPathForModule(mid);
 511                     if (lp == null)
 512                         out.writeUTF("");
 513                     else
 514                         out.writeUTF(lp.toString());
 515 
 516                     // Module views
 517                     out.writeInt(cx.views(mid).size());
 518                     for (ModuleId id : cx.views(mid)) {
 519                         out.writeUTF(id.toString());
 520                     }
 521                 }
 522 
 523                 // Local class map
 524                 out.writeInt(cx.localClasses().size());
 525                 for (Map.Entry<String,ModuleId> me
 526                          : cx.moduleForLocalClassMap().entrySet()) {
 527                     String cn = me.getKey();
 528                     int i = cn.lastIndexOf('.');
 529                     if (i == -1) {
 530                         out.writeInt(-1);
 531                         out.writeUTF(cn);
 532                     } else {
 533                         String pn = cn.substring(0, i);
 534                         assert packages.contains(pn);
 535                         out.writeInt(packages.indexOf(pn));
 536                         out.writeUTF(cn.substring(i+1, cn.length()));
 537                     }
 538                     assert mids.contains(me.getValue());
 539                     out.writeInt(mids.indexOf(me.getValue()));
 540                 }
 541 
 542                 // Remote package map
 543                 out.writeInt(cx.contextForRemotePackageMap().size());
 544                 for (Map.Entry<String,String> me
 545                          : cx.contextForRemotePackageMap().entrySet()) {
 546                     assert packages.contains(me.getKey()) && cxns.contains(me.getValue());
 547                     out.writeInt(packages.indexOf(me.getKey()));
 548                     out.writeInt(cxns.indexOf(me.getValue()));
 549                 }
 550 
 551                 // Suppliers
 552                 out.writeInt(cx.remoteContexts().size());
 553                 for (String cxn : cx.remoteContexts()) {
 554                     assert cxns.contains(cxn);
 555                     out.writeInt(cxns.indexOf(cxn));
 556                 }
 557 
 558                 // Local service implementations
 559                 Map<String,Set<String>> services = cx.services();
 560                 out.writeInt(services.size());
 561                 for (Map.Entry<String,Set<String>> me: services.entrySet()) {
 562                     out.writeUTF(me.getKey());
 563                     Set<String> values = me.getValue();
 564                     out.writeInt(values.size());
 565                     for (String value: values) {
 566                         out.writeUTF(value);
 567                     }
 568                 }
 569             }
 570         }
 571         
 572         // NOTE: jigsaw.c load_config is the native implementation of this method.
 573         // Any change to the format of StoredConfiguration should be reflectd in
 574         // both native and Java implementation
 575         protected void loadRest(DataInputStream in)
 576             throws IOException
 577         {
 578             // Roots
 579             int nRoots = in.readInt();
 580             List<ModuleId> roots = new ArrayList<>();
 581             for (int i = 0; i < nRoots; i++) {
 582                 String root = in.readUTF();
 583                 ModuleId rmid = jms.parseModuleId(root);
 584                 roots.add(rmid);
 585             }
 586             cf = new Configuration<>(roots);
 587             
 588             // Context names
 589             int nContexts = in.readInt();
 590             List<String> contexts = new ArrayList<>(nContexts);
 591             for (int i = 0; i < nContexts; i++) {
 592                 contexts.add(in.readUTF());
 593             }
 594             
 595             // Package names
 596             int nPkgs = in.readInt();
 597             List<String> packages = new ArrayList<>(nPkgs);
 598             for (int i = 0; i < nPkgs; i++) {
 599                 packages.add(in.readUTF());
 600             }
 601             
 602             // Contexts
 603             for (String cxn : contexts) {
 604                 Context cx = new Context();
 605                 // Module ids
 606                 int nModules = in.readInt();
 607                 List<ModuleId> mids = new ArrayList<>(nModules);
 608                 for (int j = 0; j < nModules; j++) {
 609                     ModuleId mid = jms.parseModuleId(in.readUTF());
 610                     mids.add(mid);
 611                     String lps = in.readUTF();
 612                     if (lps.length() > 0)
 613                         cx.putLibraryPathForModule(mid, new File(lps));
 614                     // Module Views
 615                     int nViews = in.readInt();
 616                     Set<ModuleId> views = new HashSet<>();
 617                     for (int k = 0; k < nViews; k++) {
 618                         ModuleId id = jms.parseModuleId(in.readUTF());
 619                         views.add(id);
 620                         cf.put(id.name(), cx);
 621                     }
 622                     cx.add(mid, views);
 623                 }
 624                 cx.freeze();
 625                 assert cx.name().equals(cxn);
 626                 cf.add(cx);
 627                 
 628                 // Local class map
 629                 int nClasses = in.readInt();
 630                 for (int j = 0; j < nClasses; j++) {
 631                     int idx = in.readInt();
 632                     String name = in.readUTF();
 633                     String cn = (idx == -1) ? name : packages.get(idx) + "." + name;
 634                     ModuleId mid = mids.get(in.readInt());
 635                     cx.putModuleForLocalClass(cn, mid);
 636                 } 
 637                 // Remote package map
 638                 int nPackages = in.readInt();
 639                 for (int j = 0; j < nPackages; j++) {
 640                     String pn = packages.get(in.readInt());
 641                     String rcxn = contexts.get(in.readInt());
 642                     cx.putContextForRemotePackage(pn, rcxn);
 643                 }
 644                 // Suppliers
 645                 int nSuppliers = in.readInt();
 646                 for (int j = 0; j < nSuppliers; j++) {
 647                     String rcxn = contexts.get(in.readInt());
 648                     cx.addSupplier(rcxn);
 649                 }
 650                 // Local service implementations
 651                 int nServices = in.readInt();
 652                 for (int j = 0; j < nServices; j++) {
 653                     String sn = in.readUTF();
 654                     int nImpl = in.readInt();
 655                     for (int k = 0; k < nImpl; k++) {
 656                         String cn = in.readUTF();
 657                         cx.putService(sn, cn);
 658                     }
 659                 }
 660             }
 661 
 662         }
 663 
 664         private static StoredConfiguration load(File f)
 665             throws IOException
 666         {
 667             StoredConfiguration sp = new StoredConfiguration(f, null);
 668             sp.load();
 669             return sp;
 670         }
 671 
 672     }
 673 
 674     private static final class Signers
 675         extends MetaData {
 676 
 677         private static final String FILE = "signer";
 678         private static final int MAJOR_VERSION = 0;
 679         private static final int MINOR_VERSION = 1;
 680         private static final String ENCODING = "PkiPath";
 681 
 682         private CertificateFactory cf;
 683         private Set<CodeSigner> signers;
 684         private Set<CodeSigner> signers() { return signers; }
 685 
 686         private Signers(File root, Set<CodeSigner> signers) {
 687             super(MAJOR_VERSION, MINOR_VERSION,
 688                   FileConstants.Type.LIBRARY_MODULE_SIGNER,
 689                   new File(root, FILE));
 690             this.signers = signers;
 691         }
 692 
 693         @Override
 694         protected void storeRest(DataOutputStream out)
 695             throws IOException
 696         {
 697             out.writeInt(signers.size());
 698             for (CodeSigner signer : signers) {
 699                 try {
 700                     CertPath signerCertPath = signer.getSignerCertPath();
 701                     out.write(signerCertPath.getEncoded(ENCODING));
 702                     Timestamp ts = signer.getTimestamp();
 703                     if (ts != null) {
 704                         out.writeByte(1);
 705                         out.writeLong(ts.getTimestamp().getTime());
 706                         out.write(ts.getSignerCertPath().getEncoded(ENCODING));
 707                     } else {
 708                         out.writeByte(0);
 709                     }
 710                 } catch (CertificateEncodingException cee) {
 711                     throw new IOException(cee);
 712                 }
 713             }
 714         }
 715 
 716         @Override
 717         protected void loadRest(DataInputStream in)
 718             throws IOException
 719         {
 720             int size = in.readInt();
 721             for (int i = 0; i < size; i++) {
 722                 try {
 723                     if (cf == null)
 724                         cf = CertificateFactory.getInstance("X.509");
 725                     CertPath signerCertPath = cf.generateCertPath(in, ENCODING);
 726                     int b = in.readByte();
 727                     if (b != 0) {
 728                         Date timestamp = new Date(in.readLong());
 729                         CertPath tsaCertPath = cf.generateCertPath(in, ENCODING);
 730                         Timestamp ts = new Timestamp(timestamp, tsaCertPath);
 731                         signers.add(new CodeSigner(signerCertPath, ts));
 732                     } else {
 733                         signers.add(new CodeSigner(signerCertPath, null));
 734                     }
 735                 } catch (CertificateException ce) {
 736                     throw new IOException(ce);
 737                 }
 738             }
 739         }
 740 
 741         private static Signers load(File f)
 742             throws IOException
 743         {
 744             Signers signers = new Signers(f, new HashSet<CodeSigner>());
 745             signers.load();
 746             return signers;
 747         }
 748     }
 749 
 750     protected void gatherLocalModuleIds(String moduleName,
 751                                         Set<ModuleId> mids)
 752         throws IOException
 753     {
 754         moduleDictionary.gatherLocalModuleIds(moduleName, mids);
 755     }
 756 
 757     protected void gatherLocalDeclaringModuleIds(Set<ModuleId> mids)
 758         throws IOException
 759     {
 760         mids.addAll(moduleDictionary.modules());
 761     }
 762 
 763     private void checkModuleId(ModuleId mid) {
 764         Version v = mid.version();
 765         if (v == null)
 766             return;
 767         if (!(v instanceof JigsawVersion))
 768             throw new IllegalArgumentException(mid + ": Not a Jigsaw module id");
 769     }
 770 
 771     private static File moduleDir(File root, ModuleId mid) {
 772         Version v = mid.version();
 773         String vs = (v != null) ? v.toString() : "default";
 774         return new File(new File(root, mid.name()), vs);
 775     }
 776 
 777     private static void checkModuleDir(File md)
 778         throws IOException
 779     {
 780         if (!md.isDirectory())
 781             throw new IOException(md + ": Not a directory");
 782         if (!md.canRead())
 783             throw new IOException(md + ": Not readable");
 784     }
 785 
 786     private File preinstallModuleDir(File dst, ModuleInfo mi) throws IOException {
 787         File md = moduleDir(dst, mi.id());
 788         if (md.exists()) {
 789             Files.deleteTree(md);
 790         }
 791         if (!md.mkdirs()) {
 792             throw new IOException(md + ": Cannot create");
 793         }
 794         return md;
 795     }
 796 
 797     public byte[] readLocalModuleInfoBytes(ModuleId mid)
 798         throws IOException
 799     {
 800         File md = moduleDictionary.findDeclaringModuleDir(mid);
 801         if (md == null)
 802             return null;
 803         return Files.load(new File(md, "info"));
 804     }
 805 
 806     @Override
 807     public CodeSigner[] readLocalCodeSigners(ModuleId mid)
 808         throws IOException
 809     {
 810         File md = moduleDictionary.findDeclaringModuleDir(mid);
 811         if (md == null)
 812             return null;
 813 
 814         File f = new File(md, "signer");
 815         // ## concurrency issues : what is the expected behavior if file is
 816         // ## removed by another thread/process here?
 817         if (!f.exists())
 818             return null;
 819         return Signers.load(md).signers().toArray(new CodeSigner[0]);
 820     }
 821 
 822     // ## Close all zip files when we close this library
 823     private Map<ModuleId, Object> contentForModule = new HashMap<>();
 824     private Object NONE = new Object();
 825 
 826     private Object findContent(ModuleId mid)
 827         throws IOException
 828     {
 829         ModuleId dmid = moduleDictionary.getDeclaringModule(mid);
 830         Object o = contentForModule.get(dmid);
 831         if (o == NONE)
 832             return null;
 833         if (o != null)
 834             return o;
 835         File md = moduleDictionary.findDeclaringModuleDir(dmid);
 836         if (md == null) {
 837             contentForModule.put(mid, NONE);
 838             return null;
 839         }
 840         File cf = new File(md, "classes");
 841         if (cf.isFile()) {
 842             ZipFile zf = new ZipFile(cf);
 843             contentForModule.put(mid, zf);
 844             return zf;
 845         }
 846         if (cf.isDirectory()) {
 847             contentForModule.put(mid, cf);
 848             return cf;
 849         }
 850         contentForModule.put(mid, NONE);
 851         return null;
 852     }
 853 
 854     private byte[] loadContent(ZipFile zf, String path)
 855         throws IOException
 856     {
 857         ZipEntry ze = zf.getEntry(path);
 858         if (ze == null)
 859             return null;
 860         return Files.load(zf.getInputStream(ze), (int)ze.getSize());
 861     }
 862 
 863     private byte[] loadContent(ModuleId mid, String path)
 864         throws IOException
 865     {
 866         Object o = findContent(mid);
 867         if (o == null)
 868             return null;
 869         if (o instanceof ZipFile) {
 870             ZipFile zf = (ZipFile)o;
 871             ZipEntry ze = zf.getEntry(path);
 872             if (ze == null)
 873                 return null;
 874             return Files.load(zf.getInputStream(ze), (int)ze.getSize());
 875         }
 876         if (o instanceof File) {
 877             File f = new File((File)o, path);
 878             if (!f.exists())
 879                 return null;
 880             return Files.load(f);
 881         }
 882         assert false;
 883         return null;
 884     }
 885 
 886     private URI locateContent(ModuleId mid, String path)
 887         throws IOException
 888     {
 889         Object o = findContent(mid);
 890         if (o == null)
 891             return null;
 892         if (o instanceof ZipFile) {
 893             ZipFile zf = (ZipFile)o;
 894             ZipEntry ze = zf.getEntry(path);
 895             if (ze == null)
 896                 return null;
 897             return URI.create("jar:"
 898                               + new File(zf.getName()).toURI().toString()
 899                               + "!/" + path);
 900         }
 901         if (o instanceof File) {
 902             File f = new File((File)o, path);
 903             if (!f.exists())
 904                 return null;
 905             return f.toURI();
 906         }
 907         return null;
 908     }
 909 
 910     public byte[] readLocalClass(ModuleId mid, String className)
 911         throws IOException
 912     {
 913         return loadContent(mid, className.replace('.', '/') + ".class");
 914     }
 915 
 916     public List<String> listLocalClasses(ModuleId mid, boolean all)
 917         throws IOException
 918     {
 919         File md = moduleDictionary.findDeclaringModuleDir(mid);
 920         if (md == null)
 921             return null;
 922         Index ix = Index.load(md);
 923         int os = all ? ix.otherClasses().size() : 0;
 924         ArrayList<String> cns
 925             = new ArrayList<String>(ix.publicClasses().size() + os);
 926         cns.addAll(ix.publicClasses());
 927         if (all)
 928             cns.addAll(ix.otherClasses());
 929         return cns;
 930     }
 931 
 932     public Configuration<Context> readConfiguration(ModuleId mid)
 933         throws IOException
 934     {
 935         File md = moduleDictionary.findDeclaringModuleDir(mid);
 936         if (md == null) {
 937             if (parent != null) {
 938                 return parent.readConfiguration(mid);
 939             }
 940             return null;
 941         }
 942         StoredConfiguration scf = StoredConfiguration.load(md);
 943         return scf.cf;
 944     }
 945 
 946     private boolean addToIndex(ClassInfo ci, Index ix)
 947         throws IOException
 948     {
 949         if (ci.isModuleInfo())
 950             return false;
 951         if (ci.moduleName() != null) {
 952             // ## From early Jigsaw development; can probably delete now
 953             throw new IOException("Old-style class file with"
 954                                   + " module attribute");
 955         }
 956         if (ci.isPublic())
 957             ix.publicClasses().add(ci.name());
 958         else
 959             ix.otherClasses().add(ci.name());
 960         return true;
 961     }
 962 
 963     private void reIndex(ModuleId mid)
 964         throws IOException
 965     {
 966 
 967         File md = moduleDictionary.findDeclaringModuleDir(mid);
 968         if (md == null)
 969             throw new IllegalArgumentException(mid + ": No such module");
 970         File cd = new File(md, "classes");
 971         final Index ix = new Index(md);
 972 
 973         if (cd.isDirectory()) {
 974             Files.walkTree(cd, new Files.Visitor<File>() {
 975                 public void accept(File f) throws IOException {
 976                     if (f.getPath().endsWith(".class"))
 977                         addToIndex(ClassInfo.read(f), ix);
 978                 }
 979             });
 980         } else if (cd.isFile()) {
 981             try (FileInputStream fis = new FileInputStream(cd);
 982                  ZipInputStream zis = new ZipInputStream(fis))
 983             {
 984                 ZipEntry ze;
 985                 while ((ze = zis.getNextEntry()) != null) {
 986                     if (!ze.getName().endsWith(".class"))
 987                         continue;
 988                     addToIndex(ClassInfo.read(Files.nonClosingStream(zis),
 989                                               ze.getSize(),
 990                                               mid + ":" + ze.getName()),
 991                                ix);
 992                 }
 993             }
 994         }
 995 
 996         ix.store();
 997     }
 998 
 999     /**
1000      * Strip the debug attributes from the classes in a given module
1001      * directory.
1002      */
1003     private void strip(File md) throws IOException {
1004         File classes = new File(md, "classes");
1005         if (classes.isFile()) {
1006             File pf = new File(md, "classes.pack");
1007             try (JarFile jf = new JarFile(classes);
1008                 FileOutputStream out = new FileOutputStream(pf))
1009             {
1010                 Pack200.Packer packer = Pack200.newPacker();
1011                 Map<String,String> p = packer.properties();
1012                 p.put("com.sun.java.util.jar.pack.strip.debug", Pack200.Packer.TRUE);
1013                 packer.pack(jf, out);
1014             }
1015 
1016             try (OutputStream out = new FileOutputStream(classes);
1017                  JarOutputStream jos = new JarOutputStream(out))
1018             {
1019                 Pack200.Unpacker unpacker = Pack200.newUnpacker();
1020                 unpacker.unpack(pf, jos);
1021             } finally {
1022                 pf.delete();
1023            }
1024         }
1025     }
1026 
1027     private List<Path> listFiles(Path dir) throws IOException {
1028         final List<Path> files = new ArrayList<>();
1029         java.nio.file.Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
1030             @Override
1031             public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
1032                 throws IOException
1033             {
1034                 if (!file.endsWith("module-info.class"))
1035                     files.add(file);
1036 
1037                 return FileVisitResult.CONTINUE;
1038             }
1039         });
1040         return files;
1041     }
1042 
1043     private ModuleId installWhileLocked(Manifest mf, File dst, boolean strip)
1044         throws IOException
1045     {
1046         if (mf.classes().size() > 1)
1047             throw new IllegalArgumentException("Multiple module-class"
1048                                                + " directories"
1049                                                + " not yet supported");
1050         if (mf.classes().size() < 1)
1051             throw new IllegalArgumentException("At least one module-class"
1052                                                + " directory required");
1053         File classes = mf.classes().get(0);
1054         final String mn = mf.module();
1055 
1056         File mif = new File(classes, "module-info.class");
1057         File src = null;
1058         if (mif.exists()) {
1059             src = classes;
1060         } else {
1061             src = new File(classes, mn);
1062             mif = new File(src, "module-info.class");
1063         }
1064         byte[] bs =  Files.load(mif);
1065         ModuleInfo mi = jms.parseModuleInfo(bs);
1066         if (!mi.id().name().equals(mn)) {
1067             // ## Need a more appropriate throwable here
1068             throw new Error(mif + " is for module " + mi.id().name()
1069                             + ", not " + mn);
1070         }
1071         String m = mi.id().name();
1072         JigsawVersion v = (JigsawVersion)mi.id().version();
1073         String vs = (v == null) ? "default" : v.toString();
1074 
1075         try {
1076             File mdst;
1077             if (dst.equals(root)) {
1078                 mdst = moduleDictionary.add(mi);
1079             } else {
1080                 mdst = preinstallModuleDir(dst, mi);
1081             }
1082             Files.store(bs, new File(mdst, "info"));
1083             File cldst = new File(mdst, "classes");
1084 
1085             // Delete the config file, if one exists
1086             StoredConfiguration.delete(mdst);
1087 
1088             if (false) {
1089 
1090                 // ## Retained for now in case we later want to add an option
1091                 // ## to install into a tree rather than a zip file
1092 
1093                 // Copy class files and build index
1094                 final Index ix = new Index(mdst);
1095                 Files.copyTree(src, cldst, new Files.Filter<File>() {
1096 
1097                     public boolean accept(File f) throws IOException {
1098                         if (f.isDirectory())
1099                             return true;
1100                         if (f.getName().endsWith(".class")) {
1101                             return addToIndex(ClassInfo.read(f), ix);
1102                         } else {
1103                             return true;
1104                         }
1105                     }
1106                 });
1107                 ix.store();
1108             } else {
1109                 // Copy class/resource files and build index
1110                 Index ix = new Index(mdst);
1111                 Path srcPath = src.toPath();
1112                 List<Path> files = listFiles(srcPath);
1113 
1114                 if (!files.isEmpty()) {
1115                     try (FileOutputStream fos = new FileOutputStream(new File(mdst, "classes"));
1116                          JarOutputStream jos = new JarOutputStream(new BufferedOutputStream(fos)))
1117                     {
1118                         boolean deflate = isDeflated();
1119                         for (Path path : files) {
1120                             File file = path.toFile();
1121                             String jp = Files.convertSeparator(srcPath.relativize(path).toString());
1122                             try (OutputStream out = Files.newOutputStream(jos, deflate, jp)) {
1123                                 java.nio.file.Files.copy(path, out);
1124                             }
1125                             if (file.getName().endsWith(".class"))
1126                                 addToIndex(ClassInfo.read(file), ix);
1127                         }
1128                     }
1129                 }
1130                 ix.store();
1131                 if (strip) {
1132                     strip(mdst);
1133                 }
1134             }
1135         } catch (ConfigurationException x) {
1136             // module already exists
1137             throw new IOException(x);
1138         } catch (IOException x) {
1139             try {
1140                 moduleDictionary.remove(mi);
1141             } catch (IOException y) {
1142                 x.addSuppressed(y);
1143             }
1144             throw x;
1145         }
1146         return mi.id();
1147     }
1148 
1149     public void installFromManifests(Collection<Manifest> mfs, boolean strip)
1150         throws ConfigurationException, IOException
1151     {
1152         boolean complete = false;
1153         List<ModuleId> mids = new ArrayList<>();
1154         FileChannel fc = FileChannel.open(lockf.toPath(), WRITE);
1155         try {
1156             fc.lock();
1157             moduleDictionary.load();
1158             for (Manifest mf : mfs) {
1159                 mids.add(installWhileLocked(mf, root, strip));
1160             }
1161             configureWhileModuleDirectoryLocked(null);
1162             complete = true;
1163         } catch (ConfigurationException | IOException x) {
1164             try {
1165                 for (ModuleId mid : mids) {
1166                     ModuleInfo mi = readLocalModuleInfo(mid);
1167                     if (mi != null) {
1168                         moduleDictionary.remove(mi);
1169                     }
1170                 }
1171             } catch (IOException y) {
1172                 x.addSuppressed(y);
1173             }
1174             throw x;
1175         } finally {
1176             if (complete) {
1177                 moduleDictionary.store();
1178             }
1179             fc.close();
1180         }
1181     }
1182 
1183     @Override
1184     public void installFromManifests(Collection<Manifest> mfs)
1185         throws ConfigurationException, IOException
1186     {
1187         installFromManifests(mfs, false);
1188     }
1189 
1190     private ModuleId installWhileLocked(ModuleType type, InputStream is, boolean verifySignature,
1191                                         boolean strip) 
1192         throws ConfigurationException, IOException, SignatureException
1193     {
1194         switch (type) {
1195             case JAR:
1196                 Path jf = java.nio.file.Files.createTempFile(null, null);
1197                 try {
1198                     java.nio.file.Files.copy(is, jf, StandardCopyOption.REPLACE_EXISTING);
1199                     return installFromJarFile(jf.toFile(), verifySignature, strip);
1200                 } finally {
1201                     java.nio.file.Files.delete(jf);
1202                 }
1203             case JMOD:
1204             default:
1205                 return installWhileLocked(is, verifySignature, strip);
1206         }
1207     }
1208     
1209     private ModuleId installWhileLocked(InputStream is, boolean verifySignature,
1210                                         boolean strip)
1211         throws ConfigurationException, IOException, SignatureException
1212     {
1213         BufferedInputStream bin = new BufferedInputStream(is);
1214         DataInputStream in = new DataInputStream(bin);
1215         ModuleInfo mi = null;
1216         try (ModuleFile.Reader mr = new ModuleFile.Reader(in)) {
1217             ModuleInfo moduleInfo = jms.parseModuleInfo(mr.getModuleInfoBytes());







1218             File md = moduleDictionary.add(moduleInfo);
1219             mi = moduleInfo;
1220             if (verifySignature && mr.hasSignature()) {
1221                 // Verify the module signature
1222                 SignedModule sm = new SignedModule(mr);
1223                 Set<CodeSigner> signers = sm.verifySignature();
1224 
1225                 // Validate the signers
1226                 try {
1227                     SignedModule.validateSigners(signers);
1228                 } catch (CertificateException x) {
1229                     throw new SignatureException(x);
1230                 }
1231 
1232                 // ## TODO: Check policy and determine if signer is trusted
1233                 // ## and what permissions should be granted.
1234                 // ## If there is no policy entry, show signers and prompt
1235                 // ## user to accept before proceeding.
1236 
1237                 // Verify the module header hash and the module info hash
1238                 sm.verifyHashesStart();
1239 
1240                 // Extract remainder of the module file, and calculate hashes
1241                 mr.extractTo(md, isDeflated(), natlibs(), natcmds(), configs());
1242 
1243                 // Verify the rest of the hashes
1244                 sm.verifyHashesRest();
1245 
1246                 // Store signer info
1247                 new Signers(md, signers).store();
1248             } else {
1249                 mr.extractTo(md, isDeflated(), natlibs(), natcmds(), configs());
1250             }
1251 
1252             if (strip)
1253                 strip(md);
1254             reIndex(mi.id());         // ## Could do this while reading module file
1255 
1256             return mi.id();
1257 
1258         } catch (ConfigurationException | IOException | SignatureException |
1259                  ModuleFileParserException x) { // ## should we catch Throwable
1260             if (mi != null) {
1261                 try {
1262                     moduleDictionary.remove(mi);
1263                 } catch (IOException y) {
1264                     x.addSuppressed(y);
1265                 }
1266             }
1267             throw x;
1268         }
1269     }
1270 
1271     private ModuleId installFromJarFile(File mf, boolean verifySignature, boolean strip)
1272         throws ConfigurationException, IOException, SignatureException
1273     {
1274         ModuleInfo mi = null;
1275         try (JarFile jf = new JarFile(mf, verifySignature)) {
1276             ModuleInfo moduleInfo = jf.getModuleInfo();
1277             if (moduleInfo == null)
1278                 throw new ConfigurationException(mf + ": not a modular JAR file");
1279 
1280             File md = moduleDictionary.add(moduleInfo);
1281             mi = moduleInfo;
1282             ModuleId mid = mi.id();
1283 
1284             boolean signed = false;
1285 
1286             // copy the jar file to the module library
1287             File classesDir = new File(md, "classes");
1288             try (FileOutputStream fos = new FileOutputStream(classesDir);
1289                  BufferedOutputStream bos = new BufferedOutputStream(fos);
1290                  JarOutputStream jos = new JarOutputStream(bos)) {
1291                 jos.setLevel(0);
1292 
1293                 Enumeration<JarEntry> entries = jf.entries();
1294                 while (entries.hasMoreElements()) {
1295                     JarEntry je = entries.nextElement();
1296                     try (InputStream is = jf.getInputStream(je)) {
1297                         if (je.getName().equals(JarFile.MODULEINFO_NAME)) {
1298                             java.nio.file.Files.copy(is, md.toPath().resolve("info"));
1299                         } else {
1300                             writeJarEntry(is, je, jos);
1301                         }
1302                     }
1303                     if (!signed) {
1304                         String name = je.getName().toUpperCase(Locale.ENGLISH);
1305                         signed = name.startsWith("META-INF/")
1306                                  && name.endsWith(".SF");
1307                     }
1308                 }
1309             }
1310 
1311             try {
1312                 if (verifySignature && signed) {
1313                     // validate the code signers
1314                     Set<CodeSigner> signers = getSigners(jf);
1315                     SignedModule.validateSigners(signers);
1316                     // store the signers
1317                     new Signers(md, signers).store();
1318                 }
1319             } catch (CertificateException ce) {
1320                 throw new SignatureException(ce);
1321             }
1322 
1323             if (strip)
1324                 strip(md);
1325             reIndex(mid);
1326 
1327             return mid;
1328         } catch (ConfigurationException | IOException | SignatureException x) {
1329             if (mi != null) {
1330                 try {
1331                     moduleDictionary.remove(mi);
1332                 } catch (IOException y) {
1333                     x.addSuppressed(y);
1334                 }
1335             }
1336             throw x;
1337         }
1338     }
1339 
1340     /**
1341      * Returns the set of signers of the specified jar file. Each signer
1342      * must have signed all relevant entries.
1343      */
1344     private static Set<CodeSigner> getSigners(JarFile jf)
1345         throws SignatureException
1346     {
1347         Set<CodeSigner> signers = new HashSet<>();
1348         Enumeration<JarEntry> entries = jf.entries();
1349         while (entries.hasMoreElements()) {
1350             JarEntry je = entries.nextElement();
1351             String name = je.getName().toUpperCase(Locale.ENGLISH);
1352             if (name.endsWith("/") || isSigningRelated(name))
1353                 continue;
1354 
1355             // A signed modular jar can be signed by multiple signers.
1356             // However, all entries must be signed by each of these signers.
1357             // Signers that only sign a subset of entries are ignored.
1358             CodeSigner[] jeSigners = je.getCodeSigners();
1359             if (jeSigners == null || jeSigners.length == 0)
1360                 throw new SignatureException("Found unsigned entry in "
1361                                              + "signed modular JAR");
1362 
1363             Set<CodeSigner> jeSignerSet =
1364                 new HashSet<>(Arrays.asList(jeSigners));
1365             if (signers.isEmpty())
1366                 signers.addAll(jeSignerSet);
1367             else if (signers.retainAll(jeSignerSet) && signers.isEmpty())
1368                 throw new SignatureException("No signers in common in "
1369                                              + "signed modular JAR");
1370         }
1371         return signers;
1372     }
1373 
1374     // true if file is part of the signature mechanism itself
1375     private static boolean isSigningRelated(String name) {
1376         if (!name.startsWith("META-INF/")) {
1377             return false;
1378         }
1379         name = name.substring(9);
1380         if (name.indexOf('/') != -1) {
1381             return false;
1382         }
1383         if (name.endsWith(".DSA") ||
1384             name.endsWith(".RSA") ||
1385             name.endsWith(".SF")  ||
1386             name.endsWith(".EC")  ||
1387             name.startsWith("SIG-") ||
1388             name.equals("MANIFEST.MF")) {
1389             return true;
1390         }
1391         return false;
1392     }
1393 
1394     private void writeJarEntry(InputStream is, JarEntry je, JarOutputStream jos)
1395         throws IOException, SignatureException
1396     {
1397         JarEntry entry = new JarEntry(je.getName());
1398         entry.setMethod(isDeflated() ? ZipEntry.DEFLATED : ZipEntry.STORED);
1399         entry.setTime(je.getTime());
1400         try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
1401             int size = 0;
1402             byte[] bs = new byte[1024];
1403             int cc = 0;
1404             // This will throw a SecurityException if a signature is invalid.
1405             while ((cc = is.read(bs)) > 0) {
1406                 baos.write(bs, 0, cc);
1407                 size += cc;
1408             }
1409             if (!isDeflated()) {
1410                 entry.setSize(size);
1411                 entry.setCrc(je.getCrc());
1412                 entry.setCompressedSize(size);
1413             }
1414             jos.putNextEntry(entry);
1415             if (baos.size() > 0)
1416                 baos.writeTo(jos);
1417             jos.closeEntry();
1418         } catch (SecurityException se) {
1419             throw new SignatureException(se);
1420         }
1421     }
1422 
1423     private ModuleId installWhileLocked(File mf, boolean verifySignature, boolean strip)
1424         throws ConfigurationException, IOException, SignatureException
1425     {
1426         if (mf.getName().endsWith(".jar"))
1427             return installFromJarFile(mf, verifySignature, strip);
1428         else {
1429             // Assume jmod file
1430             try (FileInputStream in = new FileInputStream(mf)) {
1431                 return installWhileLocked(in, verifySignature, strip);
1432             }
1433         }
1434     }
1435 
1436     public void install(Collection<File> mfs, boolean verifySignature, boolean strip)
1437         throws ConfigurationException, IOException, SignatureException
1438     {
1439         List<ModuleId> mids = new ArrayList<>();
1440         boolean complete = false;
1441         FileChannel fc = FileChannel.open(lockf.toPath(), WRITE);
1442         try {
1443             fc.lock();
1444             moduleDictionary.load();
1445             for (File mf : mfs)
1446                 mids.add(installWhileLocked(mf, verifySignature, strip));
1447             configureWhileModuleDirectoryLocked(mids);
1448             complete = true;
1449         } catch (ConfigurationException | IOException | SignatureException |
1450                  ModuleFileParserException x) {  // ## catch throwable??
1451             try {
1452                 for (ModuleId mid : mids) {
1453                     ModuleInfo mi = readLocalModuleInfo(mid);
1454                     if (mi != null) {
1455                         moduleDictionary.remove(mi);
1456                     }
1457                 }
1458             } catch (IOException y) {
1459                 x.addSuppressed(y);
1460             }
1461             throw x;
1462         } finally {
1463             if (complete) {
1464                 moduleDictionary.store();
1465             }
1466             fc.close();
1467         }
1468     }
1469 
1470     @Override
1471     public void install(Collection<File> mfs, boolean verifySignature)
1472         throws ConfigurationException, IOException, SignatureException
1473     {
1474         install(mfs, verifySignature, false);
1475     }
1476 
1477     // Public entry point, since the Resolver itself is package-private
1478     //
1479     @Override
1480     public Resolution resolve(Collection<ModuleIdQuery> midqs)
1481         throws ConfigurationException, IOException
1482     {
1483         try (FileChannel fc = FileChannel.open(lockf.toPath(), WRITE)) {
1484             fc.lock();
1485             return Resolver.run(this, midqs);
1486         }
1487     }
1488 
1489     public void install(Resolution res, boolean verifySignature, boolean strip)
1490         throws ConfigurationException, IOException, SignatureException
1491     {
1492         boolean complete = false;
1493         FileChannel fc = FileChannel.open(lockf.toPath(), WRITE);
1494         try {
1495             fc.lock();
1496             moduleDictionary.load();
1497 
1498             // ## Handle case of installing multiple root modules
1499             assert res.rootQueries.size() == 1;
1500             ModuleIdQuery midq = res.rootQueries.iterator().next();
1501             ModuleInfo root = null;
1502             for (String mn : res.moduleViewForName.keySet()) {
1503                 ModuleView mv = res.moduleViewForName.get(mn);
1504                 if (midq.matches(mv.id())) {
1505                     root = mv.moduleInfo();
1506                     break;
1507                 }
1508             }
1509             assert root != null;
1510 
1511             // Download
1512             //
1513             for (ModuleId mid : res.modulesNeeded()) {
1514                 URI u = res.locationForName.get(mid.name());
1515                 assert u != null;
1516                 RemoteRepository rr = repositoryList().firstRepository();
1517                 assert rr != null;
1518                 installWhileLocked(rr.fetchMetaData(mid).getType(), 






1519                                    rr.fetch(mid), 
1520                                    verifySignature, 
1521                                    strip);
1522                 res.locationForName.put(mid.name(), location());
1523                 // ## If something goes wrong, delete all our modules
1524             }
1525 
1526             // Configure
1527             //
1528             configureWhileModuleDirectoryLocked(res.modulesNeeded());
1529             complete = true;
1530         } catch (ConfigurationException | IOException | SignatureException |
1531                  ModuleFileParserException x) {  // ## catch throwable??
1532             try {
1533                 for (ModuleId mid : res.modulesNeeded()) {
1534                     ModuleInfo mi = readLocalModuleInfo(mid);
1535                     if (mi != null) {
1536                         moduleDictionary.remove(mi);
1537                     }
1538                 }
1539             } catch (IOException y) {
1540                 x.addSuppressed(y);
1541             }
1542             throw x;
1543         } finally {
1544             if (complete) {
1545                 moduleDictionary.store();
1546             }
1547             fc.close();
1548         }
1549     }
1550 
1551     @Override
1552     public void install(Resolution res, boolean verifySignature)
1553         throws ConfigurationException, IOException, SignatureException
1554     {
1555         install(res, verifySignature, false);
1556     }
1557 
1558     @Override
1559     public void removeForcibly(List<ModuleId> mids)
1560         throws IOException
1561     {
1562         try {
1563             remove(mids, true, false);
1564         } catch (ConfigurationException x) {
1565             throw new Error("should not be thrown when forcibly removing", x);
1566         }
1567     }
1568 
1569     @Override
1570     public void remove(List<ModuleId> mids, boolean dry)
1571         throws ConfigurationException, IOException
1572     {
1573         remove(mids, false,  dry);
1574     }
1575 
1576     private void remove(List<ModuleId> mids, boolean force, boolean dry)
1577         throws ConfigurationException, IOException
1578     {
1579         IOException ioe = null;
1580 
1581         try (FileChannel fc = FileChannel.open(lockf.toPath(), WRITE)) {
1582             fc.lock();
1583             for (ModuleId mid : mids) {
1584                 // ## Should we support alias and/or non-default view names??
1585                 if (moduleDictionary.findDeclaringModuleDir(mid) == null)
1586                     throw new IllegalArgumentException(mid + ": No such module");
1587             }
1588             if (!force)
1589                 ensureNotInConfiguration(mids);
1590             if (dry)
1591                 return;
1592 
1593             // The library may be altered after this point, so the modules
1594             // dictionary needs to be refreshed
1595             List<IOException> excs = removeWhileLocked(mids);
1596             try {
1597                 moduleDictionary.refresh();
1598                 moduleDictionary.store();
1599             } catch (IOException x) {
1600                 excs.add(x);
1601             }
1602             if (!excs.isEmpty()) {
1603                 ioe = excs.remove(0);
1604                 for (IOException x : excs)
1605                     ioe.addSuppressed(x);
1606             }
1607         } finally {
1608             if (ioe != null)
1609                 throw ioe;
1610         }
1611     }
1612 
1613     private void ensureNotInConfiguration(List<ModuleId> mids)
1614         throws ConfigurationException, IOException
1615     {
1616         // ## We do not know if a root module in a child library depends on one
1617         // ## of the 'to be removed' modules. We would break it's configuration.
1618 
1619         // check each root configuration for reference to a module in mids
1620         for (ModuleId rootid : libraryRoots()) {
1621             // skip any root modules being removed
1622             if (mids.contains(rootid))
1623                 continue;
1624 
1625             Configuration<Context> cf = readConfiguration(rootid);
1626             for (Context cx : cf.contexts()) {
1627                 for (ModuleId mid : cx.modules()) {
1628                     if (mids.contains(mid))
1629                         throw new ConfigurationException(mid +
1630                                 ": being used by " + rootid);
1631                 }
1632             }
1633         }
1634     }
1635 
1636     private static final String TRASH = ".trash";
1637     // lazy initialization of Random
1638     private static class LazyInitialization {
1639         static final Random random = new Random();
1640     }
1641     private static Path moduleTrashDir(File trash, ModuleId mid)
1642         throws IOException
1643     {
1644         String mn = mid.name();
1645         Version version = mid.version();
1646         String v = (version != null) ? version.toString() : "default";
1647         for (;;) {
1648             long n = LazyInitialization.random.nextLong();
1649             n = (n == Long.MIN_VALUE) ? 0 : Math.abs(n);
1650             String modTrashName = mn + '_' + v + '_' + Long.toString(n);
1651             File mtd = new File(trash, modTrashName);
1652             if (!mtd.exists())
1653                 return mtd.toPath();
1654         }
1655     }
1656 
1657     private List<IOException> removeWhileLocked(List<ModuleId> mids) {
1658         List<IOException> excs = new ArrayList<>();
1659         // First move the modules to the .trash dir
1660         for (ModuleId mid : mids) {
1661             try {
1662                 File md = moduleDir(root, mid);
1663                 java.nio.file.Files.move(md.toPath(),
1664                                          moduleTrashDir(trash, mid),
1665                                          ATOMIC_MOVE);
1666                 File p = md.getParentFile();
1667                 if (p.list().length == 0)
1668                     java.nio.file.Files.delete(p.toPath());
1669             } catch (IOException x) {
1670                 excs.add(x);
1671             }
1672         }
1673         for (String tm : trash.list())
1674             excs.addAll(ModuleFile.Reader.remove(new File(trash, tm)));
1675 
1676         return excs;
1677     }
1678 
1679     /**
1680      * <p> Pre-install one or more modules to an arbitrary destination
1681      * directory. </p>
1682      *
1683      * <p> A pre-installed module has the same format as within the library
1684      * itself, except that there is never a configuration file. </p>
1685      *
1686      * <p> This method is provided for use by the module-packaging tool. </p>
1687      *
1688      * @param   mfs
1689      *          The manifest describing the contents of the modules to be
1690      *          pre-installed
1691      *
1692      * @param   dst
1693      *          The destination directory, with one subdirectory per module
1694      *          name, each of which contains one subdirectory per version
1695      */
1696     public void preInstall(Collection<Manifest> mfs, File dst)
1697         throws IOException
1698     {
1699         Files.mkdirs(dst, "module destination");
1700         try (FileChannel fc = FileChannel.open(lockf.toPath(), WRITE)) {
1701             fc.lock();
1702             for (Manifest mf : mfs) {
1703                 installWhileLocked(mf, dst, false);
1704             }
1705             // no update to the module directory
1706         }
1707     }
1708 
1709     public void preInstall(Manifest mf, File dst)
1710         throws IOException
1711     {
1712         preInstall(Collections.singleton(mf), dst);
1713     }
1714 
1715     /**
1716      * Refresh the module library.
1717      */
1718     public void refresh() throws IOException {
1719         try (FileChannel fc = FileChannel.open(lockf.toPath(), WRITE)) {
1720             fc.lock();
1721             moduleDictionary.refresh();
1722             moduleDictionary.store();
1723         }
1724     }
1725 
1726     /**
1727      * <p> Update the configurations of any root modules affected by the
1728      * copying of the named modules, in pre-installed format, into this
1729      * library. </p>
1730      *
1731      * @param   mids
1732      *          The module ids of the new or updated modules, or
1733      *          {@code null} if the configuration of every root module
1734      *          should be (re)computed
1735      */
1736     public void configure(Collection<ModuleId> mids)
1737         throws ConfigurationException, IOException
1738     {
1739         try (FileChannel fc = FileChannel.open(lockf.toPath(), WRITE)) {
1740             fc.lock();
1741             configureWhileModuleDirectoryLocked(mids);
1742         }
1743     }
1744 
1745     private void configureWhileModuleDirectoryLocked(Collection<ModuleId> mids)
1746         throws ConfigurationException, IOException
1747     {
1748         // ## mids not used yet
1749         for (ModuleId mid : libraryRoots()) {
1750             // ## We could be a lot more clever about this!
1751             Configuration<Context> cf
1752                 = Configurator.configure(this, mid.toQuery());
1753             File md = moduleDictionary.findDeclaringModuleDir(mid);
1754             new StoredConfiguration(md, cf).store();
1755         }
1756     }
1757 
1758     private List<ModuleId> libraryRoots()
1759         throws IOException
1760     {
1761         List<ModuleId> roots = new ArrayList<>();
1762         for (ModuleId mid : listLocalDeclaringModuleIds()) {
1763             // each module can have multiple entry points, but
1764             // only one configuration for each module.
1765             ModuleInfo mi = readModuleInfo(mid);
1766             for (ModuleView mv : mi.views()) {
1767                 if (mv.mainClass() != null) {
1768                     roots.add(mid);
1769                     break;
1770                 }
1771             }
1772         }
1773         return roots;
1774     }
1775 
1776     public URI findLocalResource(ModuleId mid, String name)
1777         throws IOException
1778     {
1779         return locateContent(mid, name);
1780     }
1781 
1782     public File findLocalNativeLibrary(ModuleId mid, String name)
1783         throws IOException
1784     {
1785         File f = natlibs();
1786         if (f == null) {
1787             f = moduleDictionary.findDeclaringModuleDir(mid);
1788             if (f == null)
1789                 return null;
1790             f = new File(f, "lib");
1791         }
1792         f = new File(f, name);
1793         if (!f.exists())
1794             return null;
1795         return f;
1796     }
1797 
1798     public File classPath(ModuleId mid)
1799         throws IOException
1800     {
1801         File md = moduleDictionary.findDeclaringModuleDir(mid);
1802         if (md == null) {
1803             if (parent != null)
1804                 return parent.classPath(mid);
1805             return null;
1806         }
1807         // ## Check for other formats here
1808         return new File(md, "classes");
1809     }
1810 
1811     /**
1812      * <p> Re-index the classes of the named previously-installed modules, and
1813      * then update the configurations of any affected root modules. </p>
1814      *
1815      * <p> This method is intended for use during development, when a build
1816      * process may update a previously-installed module in place, adding or
1817      * removing classes. </p>
1818      *
1819      * @param   mids
1820      *          The module ids of the new or updated modules, or
1821      *          {@code null} if the configuration of every root module
1822      *          should be (re)computed
1823      */
1824     public void reIndex(List<ModuleId> mids)
1825         throws ConfigurationException, IOException
1826     {
1827         for (ModuleId mid : mids)
1828             reIndex(mid);
1829         configure(mids);
1830     }
1831 
1832     private static final class ModuleDictionary
1833     {
1834         private static final String FILE
1835             = FileConstants.META_PREFIX + "mids";
1836 
1837         private final File root;
1838         private final File file;
1839         private Map<String,Set<ModuleId>> moduleIdsForName;
1840         private Map<ModuleId,ModuleId> providingModuleIds;
1841         private Set<ModuleId> modules;
1842         private long lastUpdated;
1843 
1844         ModuleDictionary(File root) {
1845             this.root = root;
1846             this.file = new File(root, FILE);
1847             this.providingModuleIds = new LinkedHashMap<>();
1848             this.moduleIdsForName = new LinkedHashMap<>();
1849             this.modules = new HashSet<>();
1850             this.lastUpdated = -1;
1851         }
1852 
1853         private static FileHeader fileHeader() {
1854             return (new FileHeader()
1855                     .type(FileConstants.Type.LIBRARY_MODULE_IDS)
1856                     .majorVersion(Header.MAJOR_VERSION)
1857                     .minorVersion(Header.MINOR_VERSION));
1858         }
1859 
1860         void load() throws IOException {
1861             if (lastUpdated == file.lastModified())
1862                 return;
1863 
1864             providingModuleIds = new LinkedHashMap<>();
1865             moduleIdsForName = new LinkedHashMap<>();
1866             modules = new HashSet<>();
1867             lastUpdated = file.lastModified();
1868 
1869             try (FileInputStream fin = new FileInputStream(file);
1870                  DataInputStream in = new DataInputStream(new BufferedInputStream(fin)))
1871             {
1872                 FileHeader fh = fileHeader();
1873                 fh.read(in);
1874                 int nMids = in.readInt();
1875                 for (int j = 0; j < nMids; j++) {
1876                     ModuleId mid = jms.parseModuleId(in.readUTF());
1877                     ModuleId pmid = jms.parseModuleId(in.readUTF());
1878                     providingModuleIds.put(mid, pmid);
1879                     addModuleId(mid);
1880                     addModuleId(pmid);
1881                     if (mid.equals(pmid))
1882                         modules.add(mid);
1883                 }
1884             }
1885         }
1886 
1887         void store() throws IOException {
1888             File newfn = new File(root, "mids.new");
1889             FileOutputStream fout = new FileOutputStream(newfn);
1890             DataOutputStream out = new DataOutputStream(new BufferedOutputStream(fout));
1891             try {
1892                 try {
1893                     fileHeader().write(out);
1894                     out.writeInt(providingModuleIds.size());
1895                     for (Map.Entry<ModuleId, ModuleId> e : providingModuleIds.entrySet()) {
1896                         out.writeUTF(e.getKey().toString());
1897                         out.writeUTF(e.getValue().toString());
1898                     }
1899                 } finally {
1900                     out.close();
1901                 }
1902             } catch (IOException x) {
1903                 newfn.delete();
1904                 throw x;
1905             }
1906             java.nio.file.Files.move(newfn.toPath(), file.toPath(), ATOMIC_MOVE);
1907         }
1908 
1909         void gatherLocalModuleIds(String moduleName, Set<ModuleId> mids)
1910                 throws IOException
1911         {
1912             if (lastUpdated != file.lastModified())
1913                 load();
1914 
1915             if (moduleName == null) {
1916                 mids.addAll(providingModuleIds.keySet());
1917             } else {
1918                 Set<ModuleId> res = moduleIdsForName.get(moduleName);
1919                 if (res != null)
1920                     mids.addAll(res);
1921             }
1922         }
1923 
1924         ModuleId getDeclaringModule(ModuleId mid) throws IOException {
1925             if (lastUpdated != file.lastModified())
1926                 load();
1927 
1928             ModuleId pmid = providingModuleIds.get(mid);
1929             if (pmid != null && !pmid.equals(providingModuleIds.get(pmid))) {
1930                 // mid is an alias
1931                 pmid = providingModuleIds.get(pmid);
1932             }
1933             return pmid;
1934         }
1935 
1936         File findDeclaringModuleDir(ModuleId mid)
1937                 throws IOException
1938         {
1939             ModuleId dmid = getDeclaringModule(mid);
1940             if (dmid == null)
1941                 return null;
1942 
1943             File md = moduleDir(root, dmid);
1944             assert md.exists();
1945             checkModuleDir(md);
1946             return md;
1947         }
1948 
1949         Set<ModuleId> modules() throws IOException {
1950             if (lastUpdated != file.lastModified())
1951                 load();
1952             return modules;
1953         }
1954 
1955         void addModuleId(ModuleId mid) {
1956             Set<ModuleId> mids = moduleIdsForName.get(mid.name());
1957             if (mids == null) {
1958                 mids = new HashSet<>();
1959                 moduleIdsForName.put(mid.name(), mids);
1960             }
1961             mids.add(mid);
1962         }
1963 
1964         File add(ModuleInfo mi)
1965                 throws ConfigurationException, IOException
1966         {
1967             File md = ensureNewModule(mi);
1968             addToDirectory(mi);
1969             return md;
1970         }
1971 
1972         private void addToDirectory(ModuleInfo mi) {
1973             modules.add(mi.id());
1974             for (ModuleView view : mi.views()) {
1975                 providingModuleIds.put(view.id(), mi.id());
1976                 addModuleId(view.id());
1977                 for (ModuleId alias : view.aliases()) {
1978                     providingModuleIds.put(alias, view.id());
1979                     addModuleId(alias);
1980                 }
1981             }
1982         }
1983 
1984         void remove(ModuleInfo mi) throws IOException {
1985             modules.remove(mi.id());
1986             for (ModuleView view : mi.views()) {
1987                 providingModuleIds.remove(view.id());
1988                 Set<ModuleId> mids = moduleIdsForName.get(view.id().name());
1989                 if (mids != null)
1990                     mids.remove(view.id());
1991                 for (ModuleId alias : view.aliases()) {
1992                     providingModuleIds.remove(alias);
1993                     mids = moduleIdsForName.get(alias.name());
1994                     if (mids != null)
1995                         mids.remove(view.id());
1996                 }
1997             }
1998             File md = moduleDir(root, mi.id());
1999             delete(md);
2000         }
2001 
2002         private void delete(File md) throws IOException {
2003             if (!md.exists())
2004                 return;
2005 
2006             checkModuleDir(md);
2007             ModuleFile.Reader.remove(md);
2008             File parent = md.getParentFile();
2009             if (parent.list().length == 0)
2010                 parent.delete();
2011         }
2012 
2013         void refresh() throws IOException {
2014             providingModuleIds = new LinkedHashMap<>();
2015             moduleIdsForName = new LinkedHashMap<>();
2016             modules = new HashSet<>();
2017 
2018             try (DirectoryStream<Path> ds = java.nio.file.Files.newDirectoryStream(root.toPath())) {
2019                 for (Path mnp : ds) {
2020                     String mn = mnp.toFile().getName();
2021                     if (mn.startsWith(FileConstants.META_PREFIX) ||
2022                         TRASH.equals(mn)) {
2023                         continue;
2024                     }
2025 
2026                     try (DirectoryStream<Path> mds = java.nio.file.Files.newDirectoryStream(mnp)) {
2027                         for (Path versionp : mds) {
2028                             File v = versionp.toFile();
2029                             if (!v.isDirectory()) {
2030                                 throw new IOException(versionp + ": Not a directory");
2031                             }
2032                             modules.add(jms.parseModuleId(mn, v.getName()));
2033                         }
2034                     }
2035                 }
2036             }
2037             for (ModuleId mid : modules) {
2038                 byte[] bs = Files.load(new File(moduleDir(root, mid), "info"));
2039                 ModuleInfo mi = jms.parseModuleInfo(bs);
2040                 addToDirectory(mi);
2041             }
2042         }
2043 
2044         private File ensureNewModule(ModuleInfo mi)
2045                 throws ConfigurationException, IOException
2046         {
2047             for (ModuleView view : mi.views()) {
2048                 if (providingModuleIds.containsKey(view.id())) {
2049                     throw new ConfigurationException("module view " + view.id()
2050                             + " already installed");
2051                 }
2052                 for (ModuleId alias : view.aliases()) {
2053                     ModuleId mid = alias;
2054                     if (providingModuleIds.containsKey(mid)) {
2055                         throw new ConfigurationException("alias " + alias
2056                                 + " already installed");
2057                     }
2058                 }
2059             }
2060             File md = moduleDir(root, mi.id());
2061             if (md.exists()) {
2062                 throw new ConfigurationException("module " + mi.id()
2063                         + " already installed");
2064             }
2065             if (!md.mkdirs()) {
2066                 throw new IOException(md + ": Cannot create");
2067             }
2068             return md;
2069         }
2070     }
2071 
2072     // -- Repositories --
2073 
2074     private static class RepoList
2075         implements RemoteRepositoryList
2076     {
2077 
2078         private static final int MINOR_VERSION = 0;
2079         private static final int MAJOR_VERSION = 0;
2080 
2081         private final File root;
2082         private final File listFile;
2083 
2084         private RepoList(File r) {
2085             root = new File(r, FileConstants.META_PREFIX + "repos");
2086             listFile = new File(root, FileConstants.META_PREFIX + "list");
2087         }
2088 
2089         private static FileHeader fileHeader() {
2090             return (new FileHeader()
2091                     .type(FileConstants.Type.REMOTE_REPO_LIST)
2092                     .majorVersion(MAJOR_VERSION)
2093                     .minorVersion(MINOR_VERSION));
2094         }
2095 
2096         private List<RemoteRepository> repos = null;
2097         private long nextRepoId = 0;
2098 
2099         private File repoDir(long id) {
2100             return new File(root, Long.toHexString(id));
2101         }
2102 
2103         private void load() throws IOException {
2104 
2105             repos = new ArrayList<>();
2106             if (!root.exists() || !listFile.exists())
2107                 return;
2108             FileInputStream fin = new FileInputStream(listFile);
2109             DataInputStream in
2110                 = new DataInputStream(new BufferedInputStream(fin));
2111             try {
2112 
2113                 FileHeader fh = fileHeader();
2114                 fh.read(in);
2115                 nextRepoId = in.readLong();
2116                 int n = in.readInt();
2117                 long[] ids = new long[n];
2118                 for (int i = 0; i < n; i++)
2119                     ids[i] = in.readLong();
2120                 RemoteRepository parent = null;
2121 
2122                 // Load in reverse order so that parents are correct
2123                 for (int i = n - 1; i >= 0; i--) {
2124                     long id = ids[i];
2125                     RemoteRepository rr
2126                         = RemoteRepository.open(repoDir(id), id, parent);
2127                     repos.add(rr);
2128                     parent = rr;
2129                 }
2130                 Collections.reverse(repos);
2131 
2132             } finally {
2133                 in.close();
2134             }
2135 
2136         }
2137 
2138         private List<RemoteRepository> roRepos = null;
2139 
2140         // Unmodifiable
2141         public List<RemoteRepository> repositories() throws IOException {
2142             if (repos == null) {
2143                 load();
2144                 roRepos = Collections.unmodifiableList(repos);
2145             }
2146             return roRepos;
2147         }
2148 
2149         public RemoteRepository firstRepository() throws IOException {
2150             repositories();
2151             return repos.isEmpty() ? null : repos.get(0);
2152         }
2153 
2154         private void store() throws IOException {
2155             File newfn = new File(root, "list.new");
2156             FileOutputStream fout = new FileOutputStream(newfn);
2157             DataOutputStream out
2158                 = new DataOutputStream(new BufferedOutputStream(fout));
2159             try {
2160                 try {
2161                     fileHeader().write(out);
2162                     out.writeLong(nextRepoId);
2163                     out.writeInt(repos.size());
2164                     for (RemoteRepository rr : repos)
2165                         out.writeLong(rr.id());
2166                 } finally {
2167                     out.close();
2168                 }
2169             } catch (IOException x) {
2170                 newfn.delete();
2171                 throw x;
2172             }
2173             java.nio.file.Files.move(newfn.toPath(), listFile.toPath(), ATOMIC_MOVE);
2174         }
2175 
2176         public RemoteRepository add(URI u, int position)
2177             throws IOException
2178         {
2179 
2180             if (repos == null)
2181                 load();
2182             for (RemoteRepository rr : repos) {
2183                 if (rr.location().equals(u)) // ## u not canonical
2184                     throw new IllegalStateException(u + ": Already in"
2185                                                     + " repository list");
2186             }
2187             if (!root.exists()) {
2188                 if (!root.mkdir())
2189                     throw new IOException(root + ": Cannot create directory");
2190             }
2191 
2192             if (repos.size() == Integer.MAX_VALUE)
2193                 throw new IllegalStateException("Too many repositories");
2194             if (position < 0)
2195                 throw new IllegalArgumentException("Invalid index");
2196 
2197             long id = nextRepoId++;
2198             RemoteRepository rr = RemoteRepository.create(repoDir(id), u, id);
2199             try {
2200                 rr.updateCatalog(true);
2201             } catch (IOException x) {
2202                 rr.delete();
2203                 nextRepoId--;
2204                 throw x;
2205             }
2206 
2207             if (position >= repos.size()) {
2208                 repos.add(rr);
2209             } else if (position >= 0) {
2210                 List<RemoteRepository> prefix
2211                     = new ArrayList<>(repos.subList(0, position));
2212                 List<RemoteRepository> suffix
2213                     = new ArrayList<>(repos.subList(position, repos.size()));
2214                 repos.clear();
2215                 repos.addAll(prefix);
2216                 repos.add(rr);
2217                 repos.addAll(suffix);
2218             }
2219             store();
2220 
2221             return rr;
2222 
2223         }
2224 
2225         public boolean remove(RemoteRepository rr)
2226             throws IOException
2227         {
2228             if (!repos.remove(rr))
2229                 return false;
2230             store();
2231             File rd = repoDir(rr.id());
2232             for (File f : rd.listFiles()) {
2233                 if (!f.delete())
2234                     throw new IOException(f + ": Cannot delete");
2235             }
2236             if (!rd.delete())
2237                 throw new IOException(rd + ": Cannot delete");
2238             return true;
2239         }
2240 
2241         public boolean areCatalogsStale() throws IOException {
2242             for (RemoteRepository rr : repos) {
2243                 if (rr.isCatalogStale())
2244                     return true;
2245             }
2246             return false;
2247         }
2248 
2249         public boolean updateCatalogs(boolean force) throws IOException {
2250             boolean updated = false;
2251             for (RemoteRepository rr : repos) {
2252                 if (rr.updateCatalog(force))
2253                     updated = true;
2254             }
2255             return updated;
2256         }
2257 
2258     }
2259 
2260     private RemoteRepositoryList repoList = null;
2261 
2262     public RemoteRepositoryList repositoryList()
2263         throws IOException
2264     {
2265         if (repoList == null)
2266             repoList = new RepoList(root);
2267         return repoList;
2268     }
2269 
2270 }
--- EOF ---