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.ModuleFileType;
  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         // target Operating System of this library
 162         private String os;
 163         // target Architecture of this library
 164         private String arch;
 165 
 166         public File parent()  { return parent;  }
 167         public File natlibs() { return natlibs; }
 168         public File natcmds() { return natcmds; }
 169         public File configs() { return configs; }
 170         public String os()    { return os; }
 171         public String arch()  { return arch; }
 172         public boolean isDeflated() {
 173             return opts.contains(StorageOption.DEFLATED);
 174         }
 175 
 176         private Header(File root) {
 177             super(MAJOR_VERSION, MINOR_VERSION,
 178                   FileConstants.Type.LIBRARY_HEADER,
 179                   new File(root, FILE));
 180         }
 181 
 182         private Header(File root, File parent, File natlibs, File natcmds,
 183                        File configs, Set<StorageOption> opts, String os,
 184                        String arch) {
 185             this(root);
 186             this.parent = parent;
 187             this.natlibs = natlibs;
 188             this.natcmds = natcmds;
 189             this.configs = configs;
 190             this.os = os;
 191             this.arch = arch;
 192             this.opts = new HashSet<>(opts);
 193         }
 194 
 195         private void storePath(File p, DataOutputStream out) throws IOException {
 196             if (p != null) {
 197                 out.writeByte(1);
 198                 out.writeUTF(Files.convertSeparator(p.toString()));
 199             } else {
 200                 out.write(0);
 201             }
 202         }
 203 
 204         private void storeUTF(String s, DataOutputStream out) throws IOException {
 205             if (s != null) {
 206                 out.writeByte(1);
 207                 out.writeUTF(s);
 208             } else {
 209                 out.write(0);
 210             }
 211         }
 212 
 213         @Override
 214         protected void storeRest(DataOutputStream out) throws IOException {
 215             int flags = 0;
 216             if (isDeflated())
 217                 flags |= DEFLATED;
 218             out.writeShort(flags);
 219 
 220             storePath(parent, out);
 221             storePath(natlibs, out);
 222             storePath(natcmds, out);
 223             storePath(configs, out);
 224             storeUTF(os, out);
 225             storeUTF(arch, out);
 226         }
 227 
 228         private File loadPath(DataInputStream in) throws IOException {
 229             if (in.readByte() != 0)
 230                 return new File(Files.platformSeparator(in.readUTF()));
 231             return null;
 232         }
 233 
 234         private String loadUTF(DataInputStream in) throws IOException {
 235             if (in.readByte() != 0)
 236                 return in.readUTF();
 237             return null;
 238         }
 239 
 240         @Override
 241         protected void loadRest(DataInputStream in) throws IOException {
 242             opts = new HashSet<StorageOption>();
 243             int flags = in.readShort();
 244             if ((flags & DEFLATED) == DEFLATED)
 245                 opts.add(StorageOption.DEFLATED);
 246             parent = loadPath(in);
 247             natlibs = loadPath(in);
 248             natcmds = loadPath(in);
 249             configs = loadPath(in);
 250             os = loadUTF(in);
 251             arch = loadUTF(in);
 252         }
 253 
 254         private static Header load(File f) throws IOException {
 255             Header h = new Header(f);
 256             h.load();
 257             return h;
 258         }
 259     }
 260 
 261     private final File root;
 262     private final File canonicalRoot;
 263     private final File parentPath;
 264     private final File natlibs;
 265     private final File natcmds;
 266     private final File configs;
 267     private final ModuleArchitecture modArch;
 268     private final SimpleLibrary parent;
 269     private final Header hd;
 270     private final ModuleDictionary moduleDictionary;
 271     private final File lockf;
 272     private final File trash;
 273 
 274     public String name() { return root.toString(); }
 275     public File root() { return canonicalRoot; }
 276     public int majorVersion() { return hd.majorVersion; }
 277     public int minorVersion() { return hd.minorVersion; }
 278     public SimpleLibrary parent() { return parent; }
 279     public File natlibs() { return natlibs; }
 280     public File natcmds() { return natcmds; }
 281     public File configs() { return configs; }
 282     public ModuleArchitecture architecture() { return modArch; }
 283     public boolean isDeflated() { return hd.isDeflated(); }
 284 
 285     private URI location = null;
 286     public URI location() {
 287         if (location == null)
 288             location = root().toURI();
 289         return location;
 290     }
 291 
 292     @Override
 293     public String toString() {
 294         return (this.getClass().getName()
 295                 + "[" + canonicalRoot
 296                 + ", v" + hd.majorVersion + "." + hd.minorVersion + "]");
 297     }
 298 
 299     private static File resolveAndEnsurePath(File path) throws IOException {
 300         if (path == null) { return null; }
 301 
 302         File p = path.getCanonicalFile();
 303         if (!p.exists()) {
 304             Files.mkdirs(p, p.toString());
 305         } else {
 306             Files.ensureIsDirectory(p);
 307             Files.ensureWriteable(p);
 308         }
 309         return p;
 310     }
 311 
 312     private File relativize(File path) throws IOException {
 313         if (path == null) { return null; }
 314         // Return the path relative to the canonical root
 315         return (canonicalRoot.toPath().relativize(path.toPath().toRealPath())).toFile();
 316     }
 317 
 318     // Opens an existing library
 319     private SimpleLibrary(File path) throws IOException {
 320         root = path;
 321         canonicalRoot = root.getCanonicalFile();
 322         Files.ensureIsDirectory(root);
 323         hd = Header.load(root);
 324 
 325         parentPath = hd.parent();
 326         parent = parentPath != null ? open(parentPath) : null;
 327 
 328         natlibs = hd.natlibs() == null ? null :
 329             new File(canonicalRoot, hd.natlibs().toString()).getCanonicalFile();
 330         natcmds = hd.natcmds() == null ? null :
 331             new File(canonicalRoot, hd.natcmds().toString()).getCanonicalFile();
 332         configs = hd.configs() == null ? null :
 333             new File(canonicalRoot, hd.configs().toString()).getCanonicalFile();
 334         modArch = ModuleArchitecture.create(hd.os(), hd.arch());
 335 
 336         lockf = new File(root, FileConstants.META_PREFIX + "lock");
 337         trash = new File(root, TRASH);
 338         moduleDictionary = new ModuleDictionary(root);
 339     }
 340 
 341     // Creates a new library
 342     private SimpleLibrary(File path, File parentPath, File natlibs, File natcmds,
 343                           File configs, Set<StorageOption> opts,
 344                           ModuleArchitecture modArch)
 345         throws IOException
 346     {
 347         root = path;
 348         canonicalRoot = root.getCanonicalFile();
 349         if (root.exists()) {
 350             Files.ensureIsDirectory(root);
 351             if (root.list().length != 0)
 352                 throw new IOException(root + ": Already Exists");
 353             Files.ensureWriteable(root);
 354         } else
 355             Files.mkdirs(root, root.toString());
 356 
 357         this.parent = parentPath != null ? open(parentPath) : null;
 358         this.parentPath = parentPath != null ? this.parent.root() : null;
 359 
 360         this.natlibs = resolveAndEnsurePath(natlibs);
 361         this.natcmds = resolveAndEnsurePath(natcmds);
 362         this.configs = resolveAndEnsurePath(configs);
 363         this.modArch = modArch;
 364 
 365         hd = new Header(canonicalRoot, this.parentPath, relativize(this.natlibs),
 366                         relativize(this.natcmds), relativize(this.configs),
 367                         opts, modArch.os(), modArch.arch());
 368         hd.store();
 369 
 370         lockf = new File(root, FileConstants.META_PREFIX + "lock");
 371         lockf.createNewFile();
 372         trash = new File(root, TRASH);
 373         Files.mkdirs(trash, "module library trash");
 374         moduleDictionary = new ModuleDictionary(canonicalRoot);
 375         moduleDictionary.store();
 376     }
 377 
 378     public static SimpleLibrary create(File path, File parent, File natlibs,
 379                                        File natcmds, File configs,
 380                                        Set<StorageOption> opts,
 381                                        ModuleArchitecture modArch)
 382         throws IOException
 383     {
 384         return new SimpleLibrary(path, parent, natlibs, natcmds, configs,
 385                                  opts, modArch);
 386     }
 387 
 388     public static SimpleLibrary create(File path, File parent, Set<StorageOption> opts)
 389         throws IOException
 390     {
 391         return new SimpleLibrary(path, parent, null, null, null, opts,
 392                                  ModuleArchitecture.ANY);
 393     }
 394 
 395     public static SimpleLibrary create(File path, File parent)
 396         throws IOException
 397     {
 398         return SimpleLibrary.create(path, parent, Collections.<StorageOption>emptySet());
 399     }
 400 
 401     public static SimpleLibrary create(File path, Set<StorageOption> opts)
 402         throws IOException
 403     {
 404         // ## Should default parent to $JAVA_HOME/lib/modules
 405         return SimpleLibrary.create(path, null, opts);
 406     }
 407 
 408     public static SimpleLibrary open(File path)
 409         throws IOException
 410     {
 411         return new SimpleLibrary(path);
 412     }
 413 
 414     private static final JigsawModuleSystem jms
 415         = JigsawModuleSystem.instance();
 416 
 417     private static final class Index
 418         extends MetaData
 419     {
 420 
 421         private static String FILE = "index";
 422 
 423         private static int MAJOR_VERSION = 0;
 424         private static int MINOR_VERSION = 1;
 425 
 426         private Set<String> publicClasses;
 427         public Set<String> publicClasses() { return publicClasses; }
 428 
 429         private Set<String> otherClasses;
 430         public Set<String> otherClasses() { return otherClasses; }
 431 
 432         private Index(File root) {
 433             super(MAJOR_VERSION, MINOR_VERSION,
 434                   FileConstants.Type.LIBRARY_MODULE_INDEX,
 435                   new File(root, FILE));
 436             // Unsorted on input, because we don't need it sorted
 437             publicClasses = new HashSet<String>();
 438             otherClasses = new HashSet<String>();
 439         }
 440 
 441         private void storeSet(Set<String> cnset, DataOutputStream out)
 442             throws IOException
 443         {
 444             // Sorted on output, because we can afford it
 445             List<String> cns = new ArrayList<String>(cnset);
 446             Collections.sort(cns);
 447             out.writeInt(cns.size());
 448             for (String cn : cns)
 449                 out.writeUTF(cn);
 450         }
 451 
 452         protected void storeRest(DataOutputStream out)
 453             throws IOException
 454         {
 455             storeSet(publicClasses, out);
 456             storeSet(otherClasses, out);
 457         }
 458 
 459         private void loadSet(DataInputStream in, Set<String> cnset)
 460             throws IOException
 461         {
 462             int n = in.readInt();
 463             for (int i = 0; i < n; i++)
 464                 cnset.add(in.readUTF());
 465         }
 466 
 467         protected void loadRest(DataInputStream in)
 468             throws IOException
 469         {
 470             loadSet(in, publicClasses);
 471             loadSet(in, otherClasses);
 472         }
 473 
 474         private static Index load(File f)
 475             throws IOException
 476         {
 477             Index ix = new Index(f);
 478             ix.load();
 479             return ix;
 480         }
 481 
 482     }
 483 
 484     private static final class StoredConfiguration
 485         extends MetaData
 486     {
 487 
 488         private static String FILE = "config";
 489 
 490         private static int MAJOR_VERSION = 0;
 491         private static int MINOR_VERSION = 1;
 492 
 493         private Configuration<Context> cf;
 494 
 495         private static void delete(File root) {
 496             new File(root, FILE).delete();
 497         }
 498 
 499         private StoredConfiguration(File root, Configuration<Context> conf)
 500         {
 501             super(MAJOR_VERSION, MINOR_VERSION,
 502                   FileConstants.Type.LIBRARY_MODULE_CONFIG,
 503                   new File(root, FILE));
 504             cf = conf;
 505         }
 506 
 507         protected void storeRest(DataOutputStream out)
 508             throws IOException
 509         {
 510             // Roots
 511             out.writeInt(cf.roots().size());
 512             for (ModuleId mid : cf.roots()) {
 513                 out.writeUTF(mid.toString());
 514             }
 515 
 516             // Context names and package names
 517             // Store these strings only once and the subsequent sections will
 518             // reference these names by its index.
 519             List<String> cxns = new ArrayList<>();
 520             Set<String> pkgs = new HashSet<>();
 521             for (Context cx : cf.contexts()) {
 522                 String cxn = cx.name();
 523                 cxns.add(cxn);
 524                 pkgs.addAll(cx.remotePackages());
 525                 for (String cn : cx.localClasses()) {
 526                     int i = cn.lastIndexOf('.');
 527                     if (i >= 0)
 528                         pkgs.add(cn.substring(0, i));
 529                 }
 530             }
 531             List<String> packages = Arrays.asList(pkgs.toArray(new String[0]));
 532             Collections.sort(packages);
 533             out.writeInt(cf.contexts().size());
 534             for (String cxn : cxns) {
 535                 out.writeUTF(cxn);
 536             }
 537             out.writeInt(packages.size());
 538             for (String pn : packages) {
 539                 out.writeUTF(pn);
 540             }
 541 
 542             // Contexts
 543             for (Context cx : cf.contexts()) {
 544                 // Module ids, and their libraries
 545                 out.writeInt(cx.modules().size());
 546                 List<ModuleId> mids = new ArrayList<>(cx.modules());
 547                 for (ModuleId mid : mids) {
 548                     out.writeUTF(mid.toString());
 549                     File lp = cx.findLibraryPathForModule(mid);
 550                     if (lp == null)
 551                         out.writeUTF("");
 552                     else
 553                         out.writeUTF(lp.toString());
 554 
 555                     // Module views
 556                     out.writeInt(cx.views(mid).size());
 557                     for (ModuleId id : cx.views(mid)) {
 558                         out.writeUTF(id.toString());
 559                     }
 560                 }
 561 
 562                 // Local class map
 563                 out.writeInt(cx.localClasses().size());
 564                 for (Map.Entry<String,ModuleId> me
 565                          : cx.moduleForLocalClassMap().entrySet()) {
 566                     String cn = me.getKey();
 567                     int i = cn.lastIndexOf('.');
 568                     if (i == -1) {
 569                         out.writeInt(-1);
 570                         out.writeUTF(cn);
 571                     } else {
 572                         String pn = cn.substring(0, i);
 573                         assert packages.contains(pn);
 574                         out.writeInt(packages.indexOf(pn));
 575                         out.writeUTF(cn.substring(i+1, cn.length()));
 576                     }
 577                     assert mids.contains(me.getValue());
 578                     out.writeInt(mids.indexOf(me.getValue()));
 579                 }
 580 
 581                 // Remote package map
 582                 out.writeInt(cx.contextForRemotePackageMap().size());
 583                 for (Map.Entry<String,String> me
 584                          : cx.contextForRemotePackageMap().entrySet()) {
 585                     assert packages.contains(me.getKey()) && cxns.contains(me.getValue());
 586                     out.writeInt(packages.indexOf(me.getKey()));
 587                     out.writeInt(cxns.indexOf(me.getValue()));
 588                 }
 589 
 590                 // Suppliers
 591                 out.writeInt(cx.remoteContexts().size());
 592                 for (String cxn : cx.remoteContexts()) {
 593                     assert cxns.contains(cxn);
 594                     out.writeInt(cxns.indexOf(cxn));
 595                 }
 596 
 597                 // Local service implementations
 598                 Map<String,Set<String>> services = cx.services();
 599                 out.writeInt(services.size());
 600                 for (Map.Entry<String,Set<String>> me: services.entrySet()) {
 601                     out.writeUTF(me.getKey());
 602                     Set<String> values = me.getValue();
 603                     out.writeInt(values.size());
 604                     for (String value: values) {
 605                         out.writeUTF(value);
 606                     }
 607                 }
 608             }
 609         }
 610 
 611         // NOTE: jigsaw.c load_config is the native implementation of this method.
 612         // Any change to the format of StoredConfiguration should be reflectd in
 613         // both native and Java implementation
 614         protected void loadRest(DataInputStream in)
 615             throws IOException
 616         {
 617             // Roots
 618             int nRoots = in.readInt();
 619             List<ModuleId> roots = new ArrayList<>();
 620             for (int i = 0; i < nRoots; i++) {
 621                 String root = in.readUTF();
 622                 ModuleId rmid = jms.parseModuleId(root);
 623                 roots.add(rmid);
 624             }
 625             cf = new Configuration<>(roots);
 626 
 627             // Context names
 628             int nContexts = in.readInt();
 629             List<String> contexts = new ArrayList<>(nContexts);
 630             for (int i = 0; i < nContexts; i++) {
 631                 contexts.add(in.readUTF());
 632             }
 633 
 634             // Package names
 635             int nPkgs = in.readInt();
 636             List<String> packages = new ArrayList<>(nPkgs);
 637             for (int i = 0; i < nPkgs; i++) {
 638                 packages.add(in.readUTF());
 639             }
 640 
 641             // Contexts
 642             for (String cxn : contexts) {
 643                 Context cx = new Context();
 644                 // Module ids
 645                 int nModules = in.readInt();
 646                 List<ModuleId> mids = new ArrayList<>(nModules);
 647                 for (int j = 0; j < nModules; j++) {
 648                     ModuleId mid = jms.parseModuleId(in.readUTF());
 649                     mids.add(mid);
 650                     String lps = in.readUTF();
 651                     if (lps.length() > 0)
 652                         cx.putLibraryPathForModule(mid, new File(lps));
 653                     // Module Views
 654                     int nViews = in.readInt();
 655                     Set<ModuleId> views = new HashSet<>();
 656                     for (int k = 0; k < nViews; k++) {
 657                         ModuleId id = jms.parseModuleId(in.readUTF());
 658                         views.add(id);
 659                         cf.put(id.name(), cx);
 660                     }
 661                     cx.add(mid, views);
 662                 }
 663                 cx.freeze();
 664                 assert cx.name().equals(cxn);
 665                 cf.add(cx);
 666 
 667                 // Local class map
 668                 int nClasses = in.readInt();
 669                 for (int j = 0; j < nClasses; j++) {
 670                     int idx = in.readInt();
 671                     String name = in.readUTF();
 672                     String cn = (idx == -1) ? name : packages.get(idx) + "." + name;
 673                     ModuleId mid = mids.get(in.readInt());
 674                     cx.putModuleForLocalClass(cn, mid);
 675                 }
 676                 // Remote package map
 677                 int nPackages = in.readInt();
 678                 for (int j = 0; j < nPackages; j++) {
 679                     String pn = packages.get(in.readInt());
 680                     String rcxn = contexts.get(in.readInt());
 681                     cx.putContextForRemotePackage(pn, rcxn);
 682                 }
 683                 // Suppliers
 684                 int nSuppliers = in.readInt();
 685                 for (int j = 0; j < nSuppliers; j++) {
 686                     String rcxn = contexts.get(in.readInt());
 687                     cx.addSupplier(rcxn);
 688                 }
 689                 // Local service implementations
 690                 int nServices = in.readInt();
 691                 for (int j = 0; j < nServices; j++) {
 692                     String sn = in.readUTF();
 693                     int nImpl = in.readInt();
 694                     for (int k = 0; k < nImpl; k++) {
 695                         String cn = in.readUTF();
 696                         cx.putService(sn, cn);
 697                     }
 698                 }
 699             }
 700 
 701         }
 702 
 703         private static StoredConfiguration load(File f)
 704             throws IOException
 705         {
 706             StoredConfiguration sp = new StoredConfiguration(f, null);
 707             sp.load();
 708             return sp;
 709         }
 710 
 711     }
 712 
 713     private static final class Signers
 714         extends MetaData {
 715 
 716         private static final String FILE = "signer";
 717         private static final int MAJOR_VERSION = 0;
 718         private static final int MINOR_VERSION = 1;
 719         private static final String ENCODING = "PkiPath";
 720 
 721         private CertificateFactory cf;
 722         private Set<CodeSigner> signers;
 723         private Set<CodeSigner> signers() { return signers; }
 724 
 725         private Signers(File root, Set<CodeSigner> signers) {
 726             super(MAJOR_VERSION, MINOR_VERSION,
 727                   FileConstants.Type.LIBRARY_MODULE_SIGNER,
 728                   new File(root, FILE));
 729             this.signers = signers;
 730         }
 731 
 732         @Override
 733         protected void storeRest(DataOutputStream out)
 734             throws IOException
 735         {
 736             out.writeInt(signers.size());
 737             for (CodeSigner signer : signers) {
 738                 try {
 739                     CertPath signerCertPath = signer.getSignerCertPath();
 740                     out.write(signerCertPath.getEncoded(ENCODING));
 741                     Timestamp ts = signer.getTimestamp();
 742                     if (ts != null) {
 743                         out.writeByte(1);
 744                         out.writeLong(ts.getTimestamp().getTime());
 745                         out.write(ts.getSignerCertPath().getEncoded(ENCODING));
 746                     } else {
 747                         out.writeByte(0);
 748                     }
 749                 } catch (CertificateEncodingException cee) {
 750                     throw new IOException(cee);
 751                 }
 752             }
 753         }
 754 
 755         @Override
 756         protected void loadRest(DataInputStream in)
 757             throws IOException
 758         {
 759             int size = in.readInt();
 760             for (int i = 0; i < size; i++) {
 761                 try {
 762                     if (cf == null)
 763                         cf = CertificateFactory.getInstance("X.509");
 764                     CertPath signerCertPath = cf.generateCertPath(in, ENCODING);
 765                     int b = in.readByte();
 766                     if (b != 0) {
 767                         Date timestamp = new Date(in.readLong());
 768                         CertPath tsaCertPath = cf.generateCertPath(in, ENCODING);
 769                         Timestamp ts = new Timestamp(timestamp, tsaCertPath);
 770                         signers.add(new CodeSigner(signerCertPath, ts));
 771                     } else {
 772                         signers.add(new CodeSigner(signerCertPath, null));
 773                     }
 774                 } catch (CertificateException ce) {
 775                     throw new IOException(ce);
 776                 }
 777             }
 778         }
 779 
 780         private static Signers load(File f)
 781             throws IOException
 782         {
 783             Signers signers = new Signers(f, new HashSet<CodeSigner>());
 784             signers.load();
 785             return signers;
 786         }
 787     }
 788 
 789     protected void gatherLocalModuleIds(String moduleName,
 790                                         Set<ModuleId> mids)
 791         throws IOException
 792     {
 793         moduleDictionary.gatherLocalModuleIds(moduleName, mids);
 794     }
 795 
 796     protected void gatherLocalDeclaringModuleIds(Set<ModuleId> mids)
 797         throws IOException
 798     {
 799         mids.addAll(moduleDictionary.modules());
 800     }
 801 
 802     private void checkModuleId(ModuleId mid) {
 803         Version v = mid.version();
 804         if (v == null)
 805             return;
 806         if (!(v instanceof JigsawVersion))
 807             throw new IllegalArgumentException(mid + ": Not a Jigsaw module id");
 808     }
 809 
 810     private static File moduleDir(File root, ModuleId mid) {
 811         Version v = mid.version();
 812         String vs = (v != null) ? v.toString() : "default";
 813         return new File(new File(root, mid.name()), vs);
 814     }
 815 
 816     private static void checkModuleDir(File md)
 817         throws IOException
 818     {
 819         if (!md.isDirectory())
 820             throw new IOException(md + ": Not a directory");
 821         if (!md.canRead())
 822             throw new IOException(md + ": Not readable");
 823     }
 824 
 825     private File preinstallModuleDir(File dst, ModuleInfo mi) throws IOException {
 826         File md = moduleDir(dst, mi.id());
 827         if (md.exists()) {
 828             Files.deleteTree(md);
 829         }
 830         if (!md.mkdirs()) {
 831             throw new IOException(md + ": Cannot create");
 832         }
 833         return md;
 834     }
 835 
 836     public byte[] readLocalModuleInfoBytes(ModuleId mid)
 837         throws IOException
 838     {
 839         File md = moduleDictionary.findDeclaringModuleDir(mid);
 840         if (md == null)
 841             return null;
 842         return Files.load(new File(md, "info"));
 843     }
 844 
 845     @Override
 846     public CodeSigner[] readLocalCodeSigners(ModuleId mid)
 847         throws IOException
 848     {
 849         File md = moduleDictionary.findDeclaringModuleDir(mid);
 850         if (md == null)
 851             return null;
 852 
 853         File f = new File(md, "signer");
 854         // ## concurrency issues : what is the expected behavior if file is
 855         // ## removed by another thread/process here?
 856         if (!f.exists())
 857             return null;
 858         return Signers.load(md).signers().toArray(new CodeSigner[0]);
 859     }
 860 
 861     // ## Close all zip files when we close this library
 862     private Map<ModuleId, Object> contentForModule = new HashMap<>();
 863     private Object NONE = new Object();
 864 
 865     private Object findContent(ModuleId mid)
 866         throws IOException
 867     {
 868         ModuleId dmid = moduleDictionary.getDeclaringModule(mid);
 869         Object o = contentForModule.get(dmid);
 870         if (o == NONE)
 871             return null;
 872         if (o != null)
 873             return o;
 874         File md = moduleDictionary.findDeclaringModuleDir(dmid);
 875         if (md == null) {
 876             contentForModule.put(mid, NONE);
 877             return null;
 878         }
 879         File cf = new File(md, "classes");
 880         if (cf.isFile()) {
 881             ZipFile zf = new ZipFile(cf);
 882             contentForModule.put(mid, zf);
 883             return zf;
 884         }
 885         if (cf.isDirectory()) {
 886             contentForModule.put(mid, cf);
 887             return cf;
 888         }
 889         contentForModule.put(mid, NONE);
 890         return null;
 891     }
 892 
 893     private byte[] loadContent(ZipFile zf, String path)
 894         throws IOException
 895     {
 896         ZipEntry ze = zf.getEntry(path);
 897         if (ze == null)
 898             return null;
 899         return Files.load(zf.getInputStream(ze), (int)ze.getSize());
 900     }
 901 
 902     private byte[] loadContent(ModuleId mid, String path)
 903         throws IOException
 904     {
 905         Object o = findContent(mid);
 906         if (o == null)
 907             return null;
 908         if (o instanceof ZipFile) {
 909             ZipFile zf = (ZipFile)o;
 910             ZipEntry ze = zf.getEntry(path);
 911             if (ze == null)
 912                 return null;
 913             return Files.load(zf.getInputStream(ze), (int)ze.getSize());
 914         }
 915         if (o instanceof File) {
 916             File f = new File((File)o, path);
 917             if (!f.exists())
 918                 return null;
 919             return Files.load(f);
 920         }
 921         assert false;
 922         return null;
 923     }
 924 
 925     private URI locateContent(ModuleId mid, String path)
 926         throws IOException
 927     {
 928         Object o = findContent(mid);
 929         if (o == null)
 930             return null;
 931         if (o instanceof ZipFile) {
 932             ZipFile zf = (ZipFile)o;
 933             ZipEntry ze = zf.getEntry(path);
 934             if (ze == null)
 935                 return null;
 936             return URI.create("jar:"
 937                               + new File(zf.getName()).toURI().toString()
 938                               + "!/" + path);
 939         }
 940         if (o instanceof File) {
 941             File f = new File((File)o, path);
 942             if (!f.exists())
 943                 return null;
 944             return f.toURI();
 945         }
 946         return null;
 947     }
 948 
 949     public byte[] readLocalClass(ModuleId mid, String className)
 950         throws IOException
 951     {
 952         return loadContent(mid, className.replace('.', '/') + ".class");
 953     }
 954 
 955     public List<String> listLocalClasses(ModuleId mid, boolean all)
 956         throws IOException
 957     {
 958         File md = moduleDictionary.findDeclaringModuleDir(mid);
 959         if (md == null)
 960             return null;
 961         Index ix = Index.load(md);
 962         int os = all ? ix.otherClasses().size() : 0;
 963         ArrayList<String> cns
 964             = new ArrayList<String>(ix.publicClasses().size() + os);
 965         cns.addAll(ix.publicClasses());
 966         if (all)
 967             cns.addAll(ix.otherClasses());
 968         return cns;
 969     }
 970 
 971     public Configuration<Context> readConfiguration(ModuleId mid)
 972         throws IOException
 973     {
 974         File md = moduleDictionary.findDeclaringModuleDir(mid);
 975         if (md == null) {
 976             if (parent != null) {
 977                 return parent.readConfiguration(mid);
 978             }
 979             return null;
 980         }
 981         StoredConfiguration scf = StoredConfiguration.load(md);
 982         return scf.cf;
 983     }
 984 
 985     private boolean addToIndex(ClassInfo ci, Index ix)
 986         throws IOException
 987     {
 988         if (ci.isModuleInfo())
 989             return false;
 990         if (ci.moduleName() != null) {
 991             // ## From early Jigsaw development; can probably delete now
 992             throw new IOException("Old-style class file with"
 993                                   + " module attribute");
 994         }
 995         if (ci.isPublic())
 996             ix.publicClasses().add(ci.name());
 997         else
 998             ix.otherClasses().add(ci.name());
 999         return true;
1000     }
1001 
1002     private void reIndex(ModuleId mid)
1003         throws IOException
1004     {
1005 
1006         File md = moduleDictionary.findDeclaringModuleDir(mid);
1007         if (md == null)
1008             throw new IllegalArgumentException(mid + ": No such module");
1009         File cd = new File(md, "classes");
1010         final Index ix = new Index(md);
1011 
1012         if (cd.isDirectory()) {
1013             Files.walkTree(cd, new Files.Visitor<File>() {
1014                 public void accept(File f) throws IOException {
1015                     if (f.getPath().endsWith(".class"))
1016                         addToIndex(ClassInfo.read(f), ix);
1017                 }
1018             });
1019         } else if (cd.isFile()) {
1020             try (FileInputStream fis = new FileInputStream(cd);
1021                  ZipInputStream zis = new ZipInputStream(fis))
1022             {
1023                 ZipEntry ze;
1024                 while ((ze = zis.getNextEntry()) != null) {
1025                     if (!ze.getName().endsWith(".class"))
1026                         continue;
1027                     addToIndex(ClassInfo.read(Files.nonClosingStream(zis),
1028                                               ze.getSize(),
1029                                               mid + ":" + ze.getName()),
1030                                ix);
1031                 }
1032             }
1033         }
1034 
1035         ix.store();
1036     }
1037 
1038     /**
1039      * Strip the debug attributes from the classes in a given module
1040      * directory.
1041      */
1042     private void strip(File md) throws IOException {
1043         File classes = new File(md, "classes");
1044         if (classes.isFile()) {
1045             File pf = new File(md, "classes.pack");
1046             try (JarFile jf = new JarFile(classes);
1047                 FileOutputStream out = new FileOutputStream(pf))
1048             {
1049                 Pack200.Packer packer = Pack200.newPacker();
1050                 Map<String,String> p = packer.properties();
1051                 p.put("com.sun.java.util.jar.pack.strip.debug", Pack200.Packer.TRUE);
1052                 packer.pack(jf, out);
1053             }
1054 
1055             try (OutputStream out = new FileOutputStream(classes);
1056                  JarOutputStream jos = new JarOutputStream(out))
1057             {
1058                 Pack200.Unpacker unpacker = Pack200.newUnpacker();
1059                 unpacker.unpack(pf, jos);
1060             } finally {
1061                 pf.delete();
1062            }
1063         }
1064     }
1065 
1066     private List<Path> listFiles(Path dir) throws IOException {
1067         final List<Path> files = new ArrayList<>();
1068         java.nio.file.Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
1069             @Override
1070             public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
1071                 throws IOException
1072             {
1073                 if (!file.endsWith("module-info.class"))
1074                     files.add(file);
1075 
1076                 return FileVisitResult.CONTINUE;
1077             }
1078         });
1079         return files;
1080     }
1081 
1082     private ModuleId installWhileLocked(Manifest mf, File dst, boolean strip)
1083         throws IOException
1084     {
1085         if (mf.classes().size() > 1)
1086             throw new IllegalArgumentException("Multiple module-class"
1087                                                + " directories"
1088                                                + " not yet supported");
1089         if (mf.classes().size() < 1)
1090             throw new IllegalArgumentException("At least one module-class"
1091                                                + " directory required");
1092         File classes = mf.classes().get(0);
1093         final String mn = mf.module();
1094 
1095         File mif = new File(classes, "module-info.class");
1096         File src = null;
1097         if (mif.exists()) {
1098             src = classes;
1099         } else {
1100             src = new File(classes, mn);
1101             mif = new File(src, "module-info.class");
1102         }
1103         byte[] bs =  Files.load(mif);
1104         ModuleInfo mi = jms.parseModuleInfo(bs);
1105         if (!mi.id().name().equals(mn)) {
1106             // ## Need a more appropriate throwable here
1107             throw new Error(mif + " is for module " + mi.id().name()
1108                             + ", not " + mn);
1109         }
1110         String m = mi.id().name();
1111         JigsawVersion v = (JigsawVersion)mi.id().version();
1112         String vs = (v == null) ? "default" : v.toString();
1113 
1114         try {
1115             File mdst;
1116             if (dst.equals(root)) {
1117                 mdst = moduleDictionary.add(mi);
1118             } else {
1119                 mdst = preinstallModuleDir(dst, mi);
1120             }
1121             Files.store(bs, new File(mdst, "info"));
1122             File cldst = new File(mdst, "classes");
1123 
1124             // Delete the config file, if one exists
1125             StoredConfiguration.delete(mdst);
1126 
1127             if (false) {
1128 
1129                 // ## Retained for now in case we later want to add an option
1130                 // ## to install into a tree rather than a zip file
1131 
1132                 // Copy class files and build index
1133                 final Index ix = new Index(mdst);
1134                 Files.copyTree(src, cldst, new Files.Filter<File>() {
1135 
1136                     public boolean accept(File f) throws IOException {
1137                         if (f.isDirectory())
1138                             return true;
1139                         if (f.getName().endsWith(".class")) {
1140                             return addToIndex(ClassInfo.read(f), ix);
1141                         } else {
1142                             return true;
1143                         }
1144                     }
1145                 });
1146                 ix.store();
1147             } else {
1148                 // Copy class/resource files and build index
1149                 Index ix = new Index(mdst);
1150                 Path srcPath = src.toPath();
1151                 List<Path> files = listFiles(srcPath);
1152 
1153                 if (!files.isEmpty()) {
1154                     try (FileOutputStream fos = new FileOutputStream(new File(mdst, "classes"));
1155                          JarOutputStream jos = new JarOutputStream(new BufferedOutputStream(fos)))
1156                     {
1157                         boolean deflate = isDeflated();
1158                         for (Path path : files) {
1159                             File file = path.toFile();
1160                             String jp = Files.convertSeparator(srcPath.relativize(path).toString());
1161                             try (OutputStream out = Files.newOutputStream(jos, deflate, jp)) {
1162                                 java.nio.file.Files.copy(path, out);
1163                             }
1164                             if (file.getName().endsWith(".class"))
1165                                 addToIndex(ClassInfo.read(file), ix);
1166                         }
1167                     }
1168                 }
1169                 ix.store();
1170                 if (strip) {
1171                     strip(mdst);
1172                 }
1173             }
1174         } catch (ConfigurationException x) {
1175             // module already exists
1176             throw new IOException(x);
1177         } catch (IOException x) {
1178             try {
1179                 moduleDictionary.remove(mi);
1180             } catch (IOException y) {
1181                 x.addSuppressed(y);
1182             }
1183             throw x;
1184         }
1185         return mi.id();
1186     }
1187 
1188     public void installFromManifests(Collection<Manifest> mfs, boolean strip)
1189         throws ConfigurationException, IOException
1190     {
1191         boolean complete = false;
1192         List<ModuleId> mids = new ArrayList<>();
1193         FileChannel fc = FileChannel.open(lockf.toPath(), WRITE);
1194         try {
1195             fc.lock();
1196             moduleDictionary.load();
1197             for (Manifest mf : mfs) {
1198                 mids.add(installWhileLocked(mf, root, strip));
1199             }
1200             configureWhileModuleDirectoryLocked(null);
1201             complete = true;
1202         } catch (ConfigurationException | IOException x) {
1203             try {
1204                 for (ModuleId mid : mids) {
1205                     ModuleInfo mi = readLocalModuleInfo(mid);
1206                     if (mi != null) {
1207                         moduleDictionary.remove(mi);
1208                     }
1209                 }
1210             } catch (IOException y) {
1211                 x.addSuppressed(y);
1212             }
1213             throw x;
1214         } finally {
1215             if (complete) {
1216                 moduleDictionary.store();
1217             }
1218             fc.close();
1219         }
1220     }
1221 
1222     @Override
1223     public void installFromManifests(Collection<Manifest> mfs)
1224         throws ConfigurationException, IOException
1225     {
1226         installFromManifests(mfs, false);
1227     }
1228 
1229     private ModuleId installWhileLocked(ModuleFileType type, InputStream is,
1230                                         boolean verifySignature, boolean strip)
1231         throws ConfigurationException, IOException, SignatureException
1232     {
1233         switch (type) {
1234             case JAR:
1235                 Path jf = java.nio.file.Files.createTempFile(null, null);
1236                 try {
1237                     java.nio.file.Files.copy(is, jf, StandardCopyOption.REPLACE_EXISTING);
1238                     return installFromJarFile(jf.toFile(), verifySignature, strip);
1239                 } finally {
1240                     java.nio.file.Files.delete(jf);
1241                 }
1242             case JMOD:
1243             default:
1244                 return installWhileLocked(is, verifySignature, strip);
1245         }
1246     }
1247 
1248     private ModuleId installWhileLocked(InputStream is, boolean verifySignature,
1249                                         boolean strip)
1250         throws ConfigurationException, IOException, SignatureException
1251     {
1252         BufferedInputStream bin = new BufferedInputStream(is);
1253         DataInputStream in = new DataInputStream(bin);
1254         ModuleInfo mi = null;
1255         try (ModuleFile.Reader mr = new ModuleFile.Reader(in)) {
1256             ModuleInfo moduleInfo = jms.parseModuleInfo(mr.getModuleInfoBytes());
1257 
1258             // Installee must have the same architecture as the library
1259             if (!mr.getArchitecture().matches(architecture()))
1260                 throw new IOException("Module architecture [" + mr.getArchitecture()
1261                                       + "] does not match library ["
1262                                       + architecture() + "]");  // ## exception type??
1263 
1264             File md = moduleDictionary.add(moduleInfo);
1265             mi = moduleInfo;
1266             if (verifySignature && mr.hasSignature()) {
1267                 // Verify the module signature
1268                 SignedModule sm = new SignedModule(mr);
1269                 Set<CodeSigner> signers = sm.verifySignature();
1270 
1271                 // Validate the signers
1272                 try {
1273                     SignedModule.validateSigners(signers);
1274                 } catch (CertificateException x) {
1275                     throw new SignatureException(x);
1276                 }
1277 
1278                 // ## TODO: Check policy and determine if signer is trusted
1279                 // ## and what permissions should be granted.
1280                 // ## If there is no policy entry, show signers and prompt
1281                 // ## user to accept before proceeding.
1282 
1283                 // Verify the module header hash and the module info hash
1284                 sm.verifyHashesStart();
1285 
1286                 // Extract remainder of the module file, and calculate hashes
1287                 mr.extractTo(md, isDeflated(), natlibs(), natcmds(), configs());
1288 
1289                 // Verify the rest of the hashes
1290                 sm.verifyHashesRest();
1291 
1292                 // Store signer info
1293                 new Signers(md, signers).store();
1294             } else {
1295                 mr.extractTo(md, isDeflated(), natlibs(), natcmds(), configs());
1296             }
1297 
1298             if (strip)
1299                 strip(md);
1300             reIndex(mi.id());         // ## Could do this while reading module file
1301 
1302             return mi.id();
1303 
1304         } catch (ConfigurationException | IOException | SignatureException |
1305                  ModuleFileParserException x) { // ## should we catch Throwable
1306             if (mi != null) {
1307                 try {
1308                     moduleDictionary.remove(mi);
1309                 } catch (IOException y) {
1310                     x.addSuppressed(y);
1311                 }
1312             }
1313             throw x;
1314         }
1315     }
1316 
1317     private ModuleId installFromJarFile(File mf, boolean verifySignature, boolean strip)
1318         throws ConfigurationException, IOException, SignatureException
1319     {
1320         ModuleInfo mi = null;
1321         try (JarFile jf = new JarFile(mf, verifySignature)) {
1322             ModuleInfo moduleInfo = jf.getModuleInfo();
1323             if (moduleInfo == null)
1324                 throw new ConfigurationException(mf + ": not a modular JAR file");
1325 
1326             File md = moduleDictionary.add(moduleInfo);
1327             mi = moduleInfo;
1328             ModuleId mid = mi.id();
1329 
1330             boolean signed = false;
1331 
1332             // copy the jar file to the module library
1333             File classesDir = new File(md, "classes");
1334             try (FileOutputStream fos = new FileOutputStream(classesDir);
1335                  BufferedOutputStream bos = new BufferedOutputStream(fos);
1336                  JarOutputStream jos = new JarOutputStream(bos)) {
1337                 jos.setLevel(0);
1338 
1339                 Enumeration<JarEntry> entries = jf.entries();
1340                 while (entries.hasMoreElements()) {
1341                     JarEntry je = entries.nextElement();
1342                     try (InputStream is = jf.getInputStream(je)) {
1343                         if (je.getName().equals(JarFile.MODULEINFO_NAME)) {
1344                             java.nio.file.Files.copy(is, md.toPath().resolve("info"));
1345                         } else {
1346                             writeJarEntry(is, je, jos);
1347                         }
1348                     }
1349                     if (!signed) {
1350                         String name = je.getName().toUpperCase(Locale.ENGLISH);
1351                         signed = name.startsWith("META-INF/")
1352                                  && name.endsWith(".SF");
1353                     }
1354                 }
1355             }
1356 
1357             try {
1358                 if (verifySignature && signed) {
1359                     // validate the code signers
1360                     Set<CodeSigner> signers = getSigners(jf);
1361                     SignedModule.validateSigners(signers);
1362                     // store the signers
1363                     new Signers(md, signers).store();
1364                 }
1365             } catch (CertificateException ce) {
1366                 throw new SignatureException(ce);
1367             }
1368 
1369             if (strip)
1370                 strip(md);
1371             reIndex(mid);
1372 
1373             return mid;
1374         } catch (ConfigurationException | IOException | SignatureException x) {
1375             if (mi != null) {
1376                 try {
1377                     moduleDictionary.remove(mi);
1378                 } catch (IOException y) {
1379                     x.addSuppressed(y);
1380                 }
1381             }
1382             throw x;
1383         }
1384     }
1385 
1386     /**
1387      * Returns the set of signers of the specified jar file. Each signer
1388      * must have signed all relevant entries.
1389      */
1390     private static Set<CodeSigner> getSigners(JarFile jf)
1391         throws SignatureException
1392     {
1393         Set<CodeSigner> signers = new HashSet<>();
1394         Enumeration<JarEntry> entries = jf.entries();
1395         while (entries.hasMoreElements()) {
1396             JarEntry je = entries.nextElement();
1397             String name = je.getName().toUpperCase(Locale.ENGLISH);
1398             if (name.endsWith("/") || isSigningRelated(name))
1399                 continue;
1400 
1401             // A signed modular jar can be signed by multiple signers.
1402             // However, all entries must be signed by each of these signers.
1403             // Signers that only sign a subset of entries are ignored.
1404             CodeSigner[] jeSigners = je.getCodeSigners();
1405             if (jeSigners == null || jeSigners.length == 0)
1406                 throw new SignatureException("Found unsigned entry in "
1407                                              + "signed modular JAR");
1408 
1409             Set<CodeSigner> jeSignerSet =
1410                 new HashSet<>(Arrays.asList(jeSigners));
1411             if (signers.isEmpty())
1412                 signers.addAll(jeSignerSet);
1413             else if (signers.retainAll(jeSignerSet) && signers.isEmpty())
1414                 throw new SignatureException("No signers in common in "
1415                                              + "signed modular JAR");
1416         }
1417         return signers;
1418     }
1419 
1420     // true if file is part of the signature mechanism itself
1421     private static boolean isSigningRelated(String name) {
1422         if (!name.startsWith("META-INF/")) {
1423             return false;
1424         }
1425         name = name.substring(9);
1426         if (name.indexOf('/') != -1) {
1427             return false;
1428         }
1429         if (name.endsWith(".DSA") ||
1430             name.endsWith(".RSA") ||
1431             name.endsWith(".SF")  ||
1432             name.endsWith(".EC")  ||
1433             name.startsWith("SIG-") ||
1434             name.equals("MANIFEST.MF")) {
1435             return true;
1436         }
1437         return false;
1438     }
1439 
1440     private void writeJarEntry(InputStream is, JarEntry je, JarOutputStream jos)
1441         throws IOException, SignatureException
1442     {
1443         JarEntry entry = new JarEntry(je.getName());
1444         entry.setMethod(isDeflated() ? ZipEntry.DEFLATED : ZipEntry.STORED);
1445         entry.setTime(je.getTime());
1446         try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
1447             int size = 0;
1448             byte[] bs = new byte[1024];
1449             int cc = 0;
1450             // This will throw a SecurityException if a signature is invalid.
1451             while ((cc = is.read(bs)) > 0) {
1452                 baos.write(bs, 0, cc);
1453                 size += cc;
1454             }
1455             if (!isDeflated()) {
1456                 entry.setSize(size);
1457                 entry.setCrc(je.getCrc());
1458                 entry.setCompressedSize(size);
1459             }
1460             jos.putNextEntry(entry);
1461             if (baos.size() > 0)
1462                 baos.writeTo(jos);
1463             jos.closeEntry();
1464         } catch (SecurityException se) {
1465             throw new SignatureException(se);
1466         }
1467     }
1468 
1469     private ModuleId installWhileLocked(File mf, boolean verifySignature, boolean strip)
1470         throws ConfigurationException, IOException, SignatureException
1471     {
1472         if (mf.getName().endsWith(".jar"))
1473             return installFromJarFile(mf, verifySignature, strip);
1474         else {
1475             // Assume jmod file
1476             try (FileInputStream in = new FileInputStream(mf)) {
1477                 return installWhileLocked(in, verifySignature, strip);
1478             }
1479         }
1480     }
1481 
1482     public void install(Collection<File> mfs, boolean verifySignature, boolean strip)
1483         throws ConfigurationException, IOException, SignatureException
1484     {
1485         List<ModuleId> mids = new ArrayList<>();
1486         boolean complete = false;
1487         FileChannel fc = FileChannel.open(lockf.toPath(), WRITE);
1488         try {
1489             fc.lock();
1490             moduleDictionary.load();
1491             for (File mf : mfs)
1492                 mids.add(installWhileLocked(mf, verifySignature, strip));
1493             configureWhileModuleDirectoryLocked(mids);
1494             complete = true;
1495         } catch (ConfigurationException | IOException | SignatureException |
1496                  ModuleFileParserException x) {  // ## catch throwable??
1497             try {
1498                 for (ModuleId mid : mids) {
1499                     ModuleInfo mi = readLocalModuleInfo(mid);
1500                     if (mi != null) {
1501                         moduleDictionary.remove(mi);
1502                     }
1503                 }
1504             } catch (IOException y) {
1505                 x.addSuppressed(y);
1506             }
1507             throw x;
1508         } finally {
1509             if (complete) {
1510                 moduleDictionary.store();
1511             }
1512             fc.close();
1513         }
1514     }
1515 
1516     @Override
1517     public void install(Collection<File> mfs, boolean verifySignature)
1518         throws ConfigurationException, IOException, SignatureException
1519     {
1520         install(mfs, verifySignature, false);
1521     }
1522 
1523     // Public entry point, since the Resolver itself is package-private
1524     //
1525     @Override
1526     public Resolution resolve(Collection<ModuleIdQuery> midqs)
1527         throws ConfigurationException, IOException
1528     {
1529         try (FileChannel fc = FileChannel.open(lockf.toPath(), WRITE)) {
1530             fc.lock();
1531             return Resolver.run(this, midqs);
1532         }
1533     }
1534 
1535     public void install(Resolution res, boolean verifySignature, boolean strip)
1536         throws ConfigurationException, IOException, SignatureException
1537     {
1538         boolean complete = false;
1539         FileChannel fc = FileChannel.open(lockf.toPath(), WRITE);
1540         try {
1541             fc.lock();
1542             moduleDictionary.load();
1543 
1544             // ## Handle case of installing multiple root modules
1545             assert res.rootQueries.size() == 1;
1546             ModuleIdQuery midq = res.rootQueries.iterator().next();
1547             ModuleInfo root = null;
1548             for (String mn : res.moduleViewForName.keySet()) {
1549                 ModuleView mv = res.moduleViewForName.get(mn);
1550                 if (midq.matches(mv.id())) {
1551                     root = mv.moduleInfo();
1552                     break;
1553                 }
1554             }
1555             assert root != null;
1556 
1557             // Download
1558             //
1559             for (ModuleId mid : res.modulesNeeded()) {
1560                 URI u = res.locationForName.get(mid.name());
1561                 assert u != null;
1562                 RemoteRepository rr = repositoryList().firstRepository();
1563                 assert rr != null;
1564                 Repository.ModuleFileMetaData mfmd = rr.fetchMetaData(mid);
1565                 // Installee must have the same architecture as the library
1566                 if (!mfmd.architecture().matches(architecture()))
1567                     throw new IOException("Module architecture [" + mfmd.architecture()
1568                                           + "] does not match library ["
1569                                           + architecture() + "]");  // ## exception type??
1570                 installWhileLocked(mfmd.getType(),
1571                                    rr.fetch(mid),
1572                                    verifySignature,
1573                                    strip);
1574                 res.locationForName.put(mid.name(), location());
1575                 // ## If something goes wrong, delete all our modules
1576             }
1577 
1578             // Configure
1579             //
1580             configureWhileModuleDirectoryLocked(res.modulesNeeded());
1581             complete = true;
1582         } catch (ConfigurationException | IOException | SignatureException |
1583                  ModuleFileParserException x) {  // ## catch throwable??
1584             try {
1585                 for (ModuleId mid : res.modulesNeeded()) {
1586                     ModuleInfo mi = readLocalModuleInfo(mid);
1587                     if (mi != null) {
1588                         moduleDictionary.remove(mi);
1589                     }
1590                 }
1591             } catch (IOException y) {
1592                 x.addSuppressed(y);
1593             }
1594             throw x;
1595         } finally {
1596             if (complete) {
1597                 moduleDictionary.store();
1598             }
1599             fc.close();
1600         }
1601     }
1602 
1603     @Override
1604     public void install(Resolution res, boolean verifySignature)
1605         throws ConfigurationException, IOException, SignatureException
1606     {
1607         install(res, verifySignature, false);
1608     }
1609 
1610     @Override
1611     public void removeForcibly(List<ModuleId> mids)
1612         throws IOException
1613     {
1614         try {
1615             remove(mids, true, false);
1616         } catch (ConfigurationException x) {
1617             throw new Error("should not be thrown when forcibly removing", x);
1618         }
1619     }
1620 
1621     @Override
1622     public void remove(List<ModuleId> mids, boolean dry)
1623         throws ConfigurationException, IOException
1624     {
1625         remove(mids, false,  dry);
1626     }
1627 
1628     private void remove(List<ModuleId> mids, boolean force, boolean dry)
1629         throws ConfigurationException, IOException
1630     {
1631         IOException ioe = null;
1632 
1633         try (FileChannel fc = FileChannel.open(lockf.toPath(), WRITE)) {
1634             fc.lock();
1635             for (ModuleId mid : mids) {
1636                 // ## Should we support alias and/or non-default view names??
1637                 if (moduleDictionary.findDeclaringModuleDir(mid) == null)
1638                     throw new IllegalArgumentException(mid + ": No such module");
1639             }
1640             if (!force)
1641                 ensureNotInConfiguration(mids);
1642             if (dry)
1643                 return;
1644 
1645             // The library may be altered after this point, so the modules
1646             // dictionary needs to be refreshed
1647             List<IOException> excs = removeWhileLocked(mids);
1648             try {
1649                 moduleDictionary.refresh();
1650                 moduleDictionary.store();
1651             } catch (IOException x) {
1652                 excs.add(x);
1653             }
1654             if (!excs.isEmpty()) {
1655                 ioe = excs.remove(0);
1656                 for (IOException x : excs)
1657                     ioe.addSuppressed(x);
1658             }
1659         } finally {
1660             if (ioe != null)
1661                 throw ioe;
1662         }
1663     }
1664 
1665     private void ensureNotInConfiguration(List<ModuleId> mids)
1666         throws ConfigurationException, IOException
1667     {
1668         // ## We do not know if a root module in a child library depends on one
1669         // ## of the 'to be removed' modules. We would break it's configuration.
1670 
1671         // check each root configuration for reference to a module in mids
1672         for (ModuleId rootid : libraryRoots()) {
1673             // skip any root modules being removed
1674             if (mids.contains(rootid))
1675                 continue;
1676 
1677             Configuration<Context> cf = readConfiguration(rootid);
1678             for (Context cx : cf.contexts()) {
1679                 for (ModuleId mid : cx.modules()) {
1680                     if (mids.contains(mid))
1681                         throw new ConfigurationException(mid +
1682                                 ": being used by " + rootid);
1683                 }
1684             }
1685         }
1686     }
1687 
1688     private static final String TRASH = ".trash";
1689     // lazy initialization of Random
1690     private static class LazyInitialization {
1691         static final Random random = new Random();
1692     }
1693     private static Path moduleTrashDir(File trash, ModuleId mid)
1694         throws IOException
1695     {
1696         String mn = mid.name();
1697         Version version = mid.version();
1698         String v = (version != null) ? version.toString() : "default";
1699         for (;;) {
1700             long n = LazyInitialization.random.nextLong();
1701             n = (n == Long.MIN_VALUE) ? 0 : Math.abs(n);
1702             String modTrashName = mn + '_' + v + '_' + Long.toString(n);
1703             File mtd = new File(trash, modTrashName);
1704             if (!mtd.exists())
1705                 return mtd.toPath();
1706         }
1707     }
1708 
1709     private List<IOException> removeWhileLocked(List<ModuleId> mids) {
1710         List<IOException> excs = new ArrayList<>();
1711         // First move the modules to the .trash dir
1712         for (ModuleId mid : mids) {
1713             try {
1714                 File md = moduleDir(root, mid);
1715                 java.nio.file.Files.move(md.toPath(),
1716                                          moduleTrashDir(trash, mid),
1717                                          ATOMIC_MOVE);
1718                 File p = md.getParentFile();
1719                 if (p.list().length == 0)
1720                     java.nio.file.Files.delete(p.toPath());
1721             } catch (IOException x) {
1722                 excs.add(x);
1723             }
1724         }
1725         for (String tm : trash.list())
1726             excs.addAll(ModuleFile.Reader.remove(new File(trash, tm)));
1727 
1728         return excs;
1729     }
1730 
1731     /**
1732      * <p> Pre-install one or more modules to an arbitrary destination
1733      * directory. </p>
1734      *
1735      * <p> A pre-installed module has the same format as within the library
1736      * itself, except that there is never a configuration file. </p>
1737      *
1738      * <p> This method is provided for use by the module-packaging tool. </p>
1739      *
1740      * @param   mfs
1741      *          The manifest describing the contents of the modules to be
1742      *          pre-installed
1743      *
1744      * @param   dst
1745      *          The destination directory, with one subdirectory per module
1746      *          name, each of which contains one subdirectory per version
1747      */
1748     public void preInstall(Collection<Manifest> mfs, File dst)
1749         throws IOException
1750     {
1751         Files.mkdirs(dst, "module destination");
1752         try (FileChannel fc = FileChannel.open(lockf.toPath(), WRITE)) {
1753             fc.lock();
1754             for (Manifest mf : mfs) {
1755                 installWhileLocked(mf, dst, false);
1756             }
1757             // no update to the module directory
1758         }
1759     }
1760 
1761     public void preInstall(Manifest mf, File dst)
1762         throws IOException
1763     {
1764         preInstall(Collections.singleton(mf), dst);
1765     }
1766 
1767     /**
1768      * Refresh the module library.
1769      */
1770     public void refresh() throws IOException {
1771         try (FileChannel fc = FileChannel.open(lockf.toPath(), WRITE)) {
1772             fc.lock();
1773             moduleDictionary.refresh();
1774             moduleDictionary.store();
1775         }
1776     }
1777 
1778     /**
1779      * <p> Update the configurations of any root modules affected by the
1780      * copying of the named modules, in pre-installed format, into this
1781      * library. </p>
1782      *
1783      * @param   mids
1784      *          The module ids of the new or updated modules, or
1785      *          {@code null} if the configuration of every root module
1786      *          should be (re)computed
1787      */
1788     public void configure(Collection<ModuleId> mids)
1789         throws ConfigurationException, IOException
1790     {
1791         try (FileChannel fc = FileChannel.open(lockf.toPath(), WRITE)) {
1792             fc.lock();
1793             configureWhileModuleDirectoryLocked(mids);
1794         }
1795     }
1796 
1797     private void configureWhileModuleDirectoryLocked(Collection<ModuleId> mids)
1798         throws ConfigurationException, IOException
1799     {
1800         // ## mids not used yet
1801         for (ModuleId mid : libraryRoots()) {
1802             // ## We could be a lot more clever about this!
1803             Configuration<Context> cf
1804                 = Configurator.configure(this, mid.toQuery());
1805             File md = moduleDictionary.findDeclaringModuleDir(mid);
1806             new StoredConfiguration(md, cf).store();
1807         }
1808     }
1809 
1810     private List<ModuleId> libraryRoots()
1811         throws IOException
1812     {
1813         List<ModuleId> roots = new ArrayList<>();
1814         for (ModuleId mid : listLocalDeclaringModuleIds()) {
1815             // each module can have multiple entry points, but
1816             // only one configuration for each module.
1817             ModuleInfo mi = readModuleInfo(mid);
1818             for (ModuleView mv : mi.views()) {
1819                 if (mv.mainClass() != null) {
1820                     roots.add(mid);
1821                     break;
1822                 }
1823             }
1824         }
1825         return roots;
1826     }
1827 
1828     public URI findLocalResource(ModuleId mid, String name)
1829         throws IOException
1830     {
1831         return locateContent(mid, name);
1832     }
1833 
1834     public File findLocalNativeLibrary(ModuleId mid, String name)
1835         throws IOException
1836     {
1837         File f = natlibs();
1838         if (f == null) {
1839             f = moduleDictionary.findDeclaringModuleDir(mid);
1840             if (f == null)
1841                 return null;
1842             f = new File(f, "lib");
1843         }
1844         f = new File(f, name);
1845         if (!f.exists())
1846             return null;
1847         return f;
1848     }
1849 
1850     public File classPath(ModuleId mid)
1851         throws IOException
1852     {
1853         File md = moduleDictionary.findDeclaringModuleDir(mid);
1854         if (md == null) {
1855             if (parent != null)
1856                 return parent.classPath(mid);
1857             return null;
1858         }
1859         // ## Check for other formats here
1860         return new File(md, "classes");
1861     }
1862 
1863     /**
1864      * <p> Re-index the classes of the named previously-installed modules, and
1865      * then update the configurations of any affected root modules. </p>
1866      *
1867      * <p> This method is intended for use during development, when a build
1868      * process may update a previously-installed module in place, adding or
1869      * removing classes. </p>
1870      *
1871      * @param   mids
1872      *          The module ids of the new or updated modules, or
1873      *          {@code null} if the configuration of every root module
1874      *          should be (re)computed
1875      */
1876     public void reIndex(List<ModuleId> mids)
1877         throws ConfigurationException, IOException
1878     {
1879         for (ModuleId mid : mids)
1880             reIndex(mid);
1881         configure(mids);
1882     }
1883 
1884     private static final class ModuleDictionary
1885     {
1886         private static final String FILE
1887             = FileConstants.META_PREFIX + "mids";
1888 
1889         private final File root;
1890         private final File file;
1891         private Map<String,Set<ModuleId>> moduleIdsForName;
1892         private Map<ModuleId,ModuleId> providingModuleIds;
1893         private Set<ModuleId> modules;
1894         private long lastUpdated;
1895 
1896         ModuleDictionary(File root) {
1897             this.root = root;
1898             this.file = new File(root, FILE);
1899             this.providingModuleIds = new LinkedHashMap<>();
1900             this.moduleIdsForName = new LinkedHashMap<>();
1901             this.modules = new HashSet<>();
1902             this.lastUpdated = -1;
1903         }
1904 
1905         private static FileHeader fileHeader() {
1906             return (new FileHeader()
1907                     .type(FileConstants.Type.LIBRARY_MODULE_IDS)
1908                     .majorVersion(Header.MAJOR_VERSION)
1909                     .minorVersion(Header.MINOR_VERSION));
1910         }
1911 
1912         void load() throws IOException {
1913             if (lastUpdated == file.lastModified())
1914                 return;
1915 
1916             providingModuleIds = new LinkedHashMap<>();
1917             moduleIdsForName = new LinkedHashMap<>();
1918             modules = new HashSet<>();
1919             lastUpdated = file.lastModified();
1920 
1921             try (FileInputStream fin = new FileInputStream(file);
1922                  DataInputStream in = new DataInputStream(new BufferedInputStream(fin)))
1923             {
1924                 FileHeader fh = fileHeader();
1925                 fh.read(in);
1926                 int nMids = in.readInt();
1927                 for (int j = 0; j < nMids; j++) {
1928                     ModuleId mid = jms.parseModuleId(in.readUTF());
1929                     ModuleId pmid = jms.parseModuleId(in.readUTF());
1930                     providingModuleIds.put(mid, pmid);
1931                     addModuleId(mid);
1932                     addModuleId(pmid);
1933                     if (mid.equals(pmid))
1934                         modules.add(mid);
1935                 }
1936             }
1937         }
1938 
1939         void store() throws IOException {
1940             File newfn = new File(root, "mids.new");
1941             FileOutputStream fout = new FileOutputStream(newfn);
1942             DataOutputStream out = new DataOutputStream(new BufferedOutputStream(fout));
1943             try {
1944                 try {
1945                     fileHeader().write(out);
1946                     out.writeInt(providingModuleIds.size());
1947                     for (Map.Entry<ModuleId, ModuleId> e : providingModuleIds.entrySet()) {
1948                         out.writeUTF(e.getKey().toString());
1949                         out.writeUTF(e.getValue().toString());
1950                     }
1951                 } finally {
1952                     out.close();
1953                 }
1954             } catch (IOException x) {
1955                 newfn.delete();
1956                 throw x;
1957             }
1958             java.nio.file.Files.move(newfn.toPath(), file.toPath(), ATOMIC_MOVE);
1959         }
1960 
1961         void gatherLocalModuleIds(String moduleName, Set<ModuleId> mids)
1962                 throws IOException
1963         {
1964             if (lastUpdated != file.lastModified())
1965                 load();
1966 
1967             if (moduleName == null) {
1968                 mids.addAll(providingModuleIds.keySet());
1969             } else {
1970                 Set<ModuleId> res = moduleIdsForName.get(moduleName);
1971                 if (res != null)
1972                     mids.addAll(res);
1973             }
1974         }
1975 
1976         ModuleId getDeclaringModule(ModuleId mid) throws IOException {
1977             if (lastUpdated != file.lastModified())
1978                 load();
1979 
1980             ModuleId pmid = providingModuleIds.get(mid);
1981             if (pmid != null && !pmid.equals(providingModuleIds.get(pmid))) {
1982                 // mid is an alias
1983                 pmid = providingModuleIds.get(pmid);
1984             }
1985             return pmid;
1986         }
1987 
1988         File findDeclaringModuleDir(ModuleId mid)
1989                 throws IOException
1990         {
1991             ModuleId dmid = getDeclaringModule(mid);
1992             if (dmid == null)
1993                 return null;
1994 
1995             File md = moduleDir(root, dmid);
1996             assert md.exists();
1997             checkModuleDir(md);
1998             return md;
1999         }
2000 
2001         Set<ModuleId> modules() throws IOException {
2002             if (lastUpdated != file.lastModified())
2003                 load();
2004             return modules;
2005         }
2006 
2007         void addModuleId(ModuleId mid) {
2008             Set<ModuleId> mids = moduleIdsForName.get(mid.name());
2009             if (mids == null) {
2010                 mids = new HashSet<>();
2011                 moduleIdsForName.put(mid.name(), mids);
2012             }
2013             mids.add(mid);
2014         }
2015 
2016         File add(ModuleInfo mi)
2017                 throws ConfigurationException, IOException
2018         {
2019             File md = ensureNewModule(mi);
2020             addToDirectory(mi);
2021             return md;
2022         }
2023 
2024         private void addToDirectory(ModuleInfo mi) {
2025             modules.add(mi.id());
2026             for (ModuleView view : mi.views()) {
2027                 providingModuleIds.put(view.id(), mi.id());
2028                 addModuleId(view.id());
2029                 for (ModuleId alias : view.aliases()) {
2030                     providingModuleIds.put(alias, view.id());
2031                     addModuleId(alias);
2032                 }
2033             }
2034         }
2035 
2036         void remove(ModuleInfo mi) throws IOException {
2037             modules.remove(mi.id());
2038             for (ModuleView view : mi.views()) {
2039                 providingModuleIds.remove(view.id());
2040                 Set<ModuleId> mids = moduleIdsForName.get(view.id().name());
2041                 if (mids != null)
2042                     mids.remove(view.id());
2043                 for (ModuleId alias : view.aliases()) {
2044                     providingModuleIds.remove(alias);
2045                     mids = moduleIdsForName.get(alias.name());
2046                     if (mids != null)
2047                         mids.remove(view.id());
2048                 }
2049             }
2050             File md = moduleDir(root, mi.id());
2051             delete(md);
2052         }
2053 
2054         private void delete(File md) throws IOException {
2055             if (!md.exists())
2056                 return;
2057 
2058             checkModuleDir(md);
2059             ModuleFile.Reader.remove(md);
2060             File parent = md.getParentFile();
2061             if (parent.list().length == 0)
2062                 parent.delete();
2063         }
2064 
2065         void refresh() throws IOException {
2066             providingModuleIds = new LinkedHashMap<>();
2067             moduleIdsForName = new LinkedHashMap<>();
2068             modules = new HashSet<>();
2069 
2070             try (DirectoryStream<Path> ds = java.nio.file.Files.newDirectoryStream(root.toPath())) {
2071                 for (Path mnp : ds) {
2072                     String mn = mnp.toFile().getName();
2073                     if (mn.startsWith(FileConstants.META_PREFIX) ||
2074                         TRASH.equals(mn)) {
2075                         continue;
2076                     }
2077 
2078                     try (DirectoryStream<Path> mds = java.nio.file.Files.newDirectoryStream(mnp)) {
2079                         for (Path versionp : mds) {
2080                             File v = versionp.toFile();
2081                             if (!v.isDirectory()) {
2082                                 throw new IOException(versionp + ": Not a directory");
2083                             }
2084                             modules.add(jms.parseModuleId(mn, v.getName()));
2085                         }
2086                     }
2087                 }
2088             }
2089             for (ModuleId mid : modules) {
2090                 byte[] bs = Files.load(new File(moduleDir(root, mid), "info"));
2091                 ModuleInfo mi = jms.parseModuleInfo(bs);
2092                 addToDirectory(mi);
2093             }
2094         }
2095 
2096         private File ensureNewModule(ModuleInfo mi)
2097                 throws ConfigurationException, IOException
2098         {
2099             for (ModuleView view : mi.views()) {
2100                 if (providingModuleIds.containsKey(view.id())) {
2101                     throw new ConfigurationException("module view " + view.id()
2102                             + " already installed");
2103                 }
2104                 for (ModuleId alias : view.aliases()) {
2105                     ModuleId mid = alias;
2106                     if (providingModuleIds.containsKey(mid)) {
2107                         throw new ConfigurationException("alias " + alias
2108                                 + " already installed");
2109                     }
2110                 }
2111             }
2112             File md = moduleDir(root, mi.id());
2113             if (md.exists()) {
2114                 throw new ConfigurationException("module " + mi.id()
2115                         + " already installed");
2116             }
2117             if (!md.mkdirs()) {
2118                 throw new IOException(md + ": Cannot create");
2119             }
2120             return md;
2121         }
2122     }
2123 
2124     // -- Repositories --
2125 
2126     private static class RepoList
2127         implements RemoteRepositoryList
2128     {
2129 
2130         private static final int MINOR_VERSION = 0;
2131         private static final int MAJOR_VERSION = 0;
2132 
2133         private final File root;
2134         private final File listFile;
2135 
2136         private RepoList(File r) {
2137             root = new File(r, FileConstants.META_PREFIX + "repos");
2138             listFile = new File(root, FileConstants.META_PREFIX + "list");
2139         }
2140 
2141         private static FileHeader fileHeader() {
2142             return (new FileHeader()
2143                     .type(FileConstants.Type.REMOTE_REPO_LIST)
2144                     .majorVersion(MAJOR_VERSION)
2145                     .minorVersion(MINOR_VERSION));
2146         }
2147 
2148         private List<RemoteRepository> repos = null;
2149         private long nextRepoId = 0;
2150 
2151         private File repoDir(long id) {
2152             return new File(root, Long.toHexString(id));
2153         }
2154 
2155         private void load() throws IOException {
2156 
2157             repos = new ArrayList<>();
2158             if (!root.exists() || !listFile.exists())
2159                 return;
2160             FileInputStream fin = new FileInputStream(listFile);
2161             DataInputStream in
2162                 = new DataInputStream(new BufferedInputStream(fin));
2163             try {
2164 
2165                 FileHeader fh = fileHeader();
2166                 fh.read(in);
2167                 nextRepoId = in.readLong();
2168                 int n = in.readInt();
2169                 long[] ids = new long[n];
2170                 for (int i = 0; i < n; i++)
2171                     ids[i] = in.readLong();
2172                 RemoteRepository parent = null;
2173 
2174                 // Load in reverse order so that parents are correct
2175                 for (int i = n - 1; i >= 0; i--) {
2176                     long id = ids[i];
2177                     RemoteRepository rr
2178                         = RemoteRepository.open(repoDir(id), id, parent);
2179                     repos.add(rr);
2180                     parent = rr;
2181                 }
2182                 Collections.reverse(repos);
2183 
2184             } finally {
2185                 in.close();
2186             }
2187 
2188         }
2189 
2190         private List<RemoteRepository> roRepos = null;
2191 
2192         // Unmodifiable
2193         public List<RemoteRepository> repositories() throws IOException {
2194             if (repos == null) {
2195                 load();
2196                 roRepos = Collections.unmodifiableList(repos);
2197             }
2198             return roRepos;
2199         }
2200 
2201         public RemoteRepository firstRepository() throws IOException {
2202             repositories();
2203             return repos.isEmpty() ? null : repos.get(0);
2204         }
2205 
2206         private void store() throws IOException {
2207             File newfn = new File(root, "list.new");
2208             FileOutputStream fout = new FileOutputStream(newfn);
2209             DataOutputStream out
2210                 = new DataOutputStream(new BufferedOutputStream(fout));
2211             try {
2212                 try {
2213                     fileHeader().write(out);
2214                     out.writeLong(nextRepoId);
2215                     out.writeInt(repos.size());
2216                     for (RemoteRepository rr : repos)
2217                         out.writeLong(rr.id());
2218                 } finally {
2219                     out.close();
2220                 }
2221             } catch (IOException x) {
2222                 newfn.delete();
2223                 throw x;
2224             }
2225             java.nio.file.Files.move(newfn.toPath(), listFile.toPath(), ATOMIC_MOVE);
2226         }
2227 
2228         public RemoteRepository add(URI u, int position)
2229             throws IOException
2230         {
2231 
2232             if (repos == null)
2233                 load();
2234             for (RemoteRepository rr : repos) {
2235                 if (rr.location().equals(u)) // ## u not canonical
2236                     throw new IllegalStateException(u + ": Already in"
2237                                                     + " repository list");
2238             }
2239             if (!root.exists()) {
2240                 if (!root.mkdir())
2241                     throw new IOException(root + ": Cannot create directory");
2242             }
2243 
2244             if (repos.size() == Integer.MAX_VALUE)
2245                 throw new IllegalStateException("Too many repositories");
2246             if (position < 0)
2247                 throw new IllegalArgumentException("Invalid index");
2248 
2249             long id = nextRepoId++;
2250             RemoteRepository rr = RemoteRepository.create(repoDir(id), u, id);
2251             try {
2252                 rr.updateCatalog(true);
2253             } catch (IOException x) {
2254                 rr.delete();
2255                 nextRepoId--;
2256                 throw x;
2257             }
2258 
2259             if (position >= repos.size()) {
2260                 repos.add(rr);
2261             } else if (position >= 0) {
2262                 List<RemoteRepository> prefix
2263                     = new ArrayList<>(repos.subList(0, position));
2264                 List<RemoteRepository> suffix
2265                     = new ArrayList<>(repos.subList(position, repos.size()));
2266                 repos.clear();
2267                 repos.addAll(prefix);
2268                 repos.add(rr);
2269                 repos.addAll(suffix);
2270             }
2271             store();
2272 
2273             return rr;
2274 
2275         }
2276 
2277         public boolean remove(RemoteRepository rr)
2278             throws IOException
2279         {
2280             if (!repos.remove(rr))
2281                 return false;
2282             store();
2283             File rd = repoDir(rr.id());
2284             for (File f : rd.listFiles()) {
2285                 if (!f.delete())
2286                     throw new IOException(f + ": Cannot delete");
2287             }
2288             if (!rd.delete())
2289                 throw new IOException(rd + ": Cannot delete");
2290             return true;
2291         }
2292 
2293         public boolean areCatalogsStale() throws IOException {
2294             for (RemoteRepository rr : repos) {
2295                 if (rr.isCatalogStale())
2296                     return true;
2297             }
2298             return false;
2299         }
2300 
2301         public boolean updateCatalogs(boolean force) throws IOException {
2302             boolean updated = false;
2303             for (RemoteRepository rr : repos) {
2304                 if (rr.updateCatalog(force))
2305                     updated = true;
2306             }
2307             return updated;
2308         }
2309 
2310     }
2311 
2312     private RemoteRepositoryList repoList = null;
2313 
2314     public RemoteRepositoryList repositoryList()
2315         throws IOException
2316     {
2317         if (repoList == null)
2318             repoList = new RepoList(root);
2319         return repoList;
2320     }
2321 
2322 }
--- EOF ---