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