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