src/share/classes/org/openjdk/jigsaw/SimpleLibrary.java

Print this page




 136 
 137     /**
 138      * Defines the storage options that SimpleLibrary supports.
 139      */
 140     public static enum StorageOption {
 141         DEFLATED,
 142     }
 143 
 144     private static final class Header
 145         extends MetaData
 146     {
 147         private static final String FILE
 148             = FileConstants.META_PREFIX + "jigsaw-library";
 149 
 150         private static final int MAJOR_VERSION = 0;
 151         private static final int MINOR_VERSION = 1;
 152 
 153         private static final int DEFLATED = 1 << 0;
 154 
 155         private File parent;







 156         private Set<StorageOption> opts;
 157 
 158         public File parent() { return parent; }


 159         public boolean isDeflated() {
 160            return opts.contains(StorageOption.DEFLATED);
 161         }
 162 
 163         private Header(File root, File p, Set<StorageOption> opts) {

 164             super(MAJOR_VERSION, MINOR_VERSION,
 165                   FileConstants.Type.LIBRARY_HEADER,
 166                   new File(root, FILE));
 167             this.parent = p;


 168             this.opts = new HashSet<>(opts);
 169         }
 170 
 171         private Header(File root) {
 172             this(root, null, Collections.<StorageOption>emptySet());
 173         }
 174 
 175         protected void storeRest(DataOutputStream out)
 176             throws IOException
 177         {
 178             int flags = 0;
 179             if (isDeflated())
 180                 flags |= DEFLATED;
 181             out.writeShort(flags);
 182             out.writeByte((parent != null) ? 1 : 0);
 183             if (parent != null)
 184                 out.writeUTF(parent.toString());






 185         }
 186 
 187         protected void loadRest(DataInputStream in)
 188             throws IOException
 189         {
 190             opts = new HashSet<StorageOption>();
 191             int flags = in.readShort();
 192             if ((flags & DEFLATED) == DEFLATED)
 193                 opts.add(StorageOption.DEFLATED);
 194             int b = in.readByte();
 195             if (b != 0)
 196                 parent = new File(in.readUTF());






 197         }
 198 
 199         private static Header load(File f)
 200             throws IOException
 201         {
 202             Header h = new Header(f);
 203             h.load();
 204             return h;
 205         }
 206 
 207     }
 208 
 209     private final File root;
 210     private final File canonicalRoot;
 211     private File parentPath = null;
 212     private SimpleLibrary parent = null;


 213     private final Header hd;
 214 
 215     public String name() { return root.toString(); }
 216     public File root() { return canonicalRoot; }
 217     public int majorVersion() { return hd.majorVersion; }
 218     public int minorVersion() { return hd.minorVersion; }
 219     public SimpleLibrary parent() { return parent; }


 220     public boolean isDeflated() { return hd.isDeflated(); }
 221 
 222     private URI location = null;
 223     public URI location() {
 224         if (location == null)
 225             location = root().toURI();
 226         return location;
 227     }
 228 
 229     @Override
 230     public String toString() {
 231         return (this.getClass().getName()
 232                 + "[" + canonicalRoot
 233                 + ", v" + hd.majorVersion + "." + hd.minorVersion + "]");
 234     }
 235 
 236     private SimpleLibrary(File path, boolean create, File parentPath, Set<StorageOption> opts)












 237         throws IOException
 238     {
 239         root = path;
 240         canonicalRoot = root.getCanonicalFile();
 241         if (root.exists()) {
 242             if (!root.isDirectory())




 243                 throw new IOException(root + ": Exists but is not a directory");
 244             hd = Header.load(root);
 245             if (hd.parent() != null) {
 246                 parent = open(hd.parent());
 247                 parentPath = hd.parent();
 248             }




 249             return;
 250         }
 251         if (!create)
 252             throw new FileNotFoundException(root.toString());






 253         if (parentPath != null) {
 254             this.parent = open(parentPath);
 255             this.parentPath = this.parent.root();
 256         }
 257         if (!root.mkdirs())
 258             throw new IOException(root + ": Cannot create library directory");
 259         hd = new Header(canonicalRoot, this.parentPath, opts);




















 260         hd.store();
 261     }
 262 
 263     public static SimpleLibrary create(File path, File parent, Set<StorageOption> opts)

 264         throws IOException
 265     {
 266         return new SimpleLibrary(path, true, parent, opts);
 267     }
 268 







 269     public static SimpleLibrary create(File path, File parent)
 270         throws IOException 
 271     {
 272         return new SimpleLibrary(path, true, parent, Collections.<StorageOption>emptySet());

 273     }
 274 
 275     public static SimpleLibrary create(File path, Set<StorageOption> opts)
 276         throws IOException
 277     {
 278         // ## Should default parent to $JAVA_HOME/lib/modules
 279         return new SimpleLibrary(path, true, null, opts);
 280     }
 281 
 282     public static SimpleLibrary open(File path)
 283         throws IOException
 284     {
 285         return new SimpleLibrary(path, false, null, Collections.<StorageOption>emptySet());

 286     }
 287 
 288     private static final JigsawModuleSystem jms
 289         = JigsawModuleSystem.instance();
 290 
 291     private static final class Index
 292         extends MetaData
 293     {
 294 
 295         private static String FILE = "index";
 296 
 297         private static int MAJOR_VERSION = 0;
 298         private static int MINOR_VERSION = 1;
 299 
 300         private Set<String> publicClasses;
 301         public Set<String> publicClasses() { return publicClasses; }
 302 
 303         private Set<String> otherClasses;
 304         public Set<String> otherClasses() { return otherClasses; }
 305 


 952     {
 953         install(mfs, root, strip);
 954         configure(null);
 955     }
 956 
 957     @Override
 958     public void installFromManifests(Collection<Manifest> mfs)
 959         throws ConfigurationException, IOException
 960     {
 961         installFromManifests(mfs, false);
 962     }
 963 
 964     private ModuleFileVerifier.Parameters mfvParams;
 965 
 966     private ModuleId install(InputStream is, boolean verifySignature, boolean strip)
 967         throws ConfigurationException, IOException, SignatureException
 968     {
 969         BufferedInputStream bin = new BufferedInputStream(is);
 970         DataInputStream in = new DataInputStream(bin);
 971         File md = null;
 972         try (ModuleFile.Reader mr = new ModuleFile.Reader(in)) {
 973             byte[] mib = mr.readStart();
 974             ModuleInfo mi = jms.parseModuleInfo(mib);
 975             md = moduleDir(mi.id());
 976             ModuleId mid = mi.id();
 977             if (md.exists())
 978                 throw new ConfigurationException(mid + ": Already installed");
 979             if (!md.mkdirs())
 980                 throw new IOException(md + ": Cannot create");
 981 
 982             if (verifySignature && mr.hasSignature()) {
 983                 ModuleFileVerifier mfv = new SignedModule.PKCS7Verifier(mr);
 984                 if (mfvParams == null) {
 985                     mfvParams = new SignedModule.VerifierParameters();
 986                 }
 987                 // Verify the module signature and validate the signer's
 988                 // certificate chain
 989                 Set<CodeSigner> signers = mfv.verifySignature(mfvParams);
 990 
 991                 // Verify the module header hash and the module info hash
 992                 mfv.verifyHashesStart(mfvParams);


 995                 // ## should be granted?
 996 
 997                 // Store signer info
 998                 new Signers(md, signers).store();
 999 
1000                 // Read and verify the rest of the hashes
1001                 mr.readRest(md, isDeflated());
1002                 mfv.verifyHashesRest(mfvParams);
1003             } else {
1004                 mr.readRest(md, isDeflated());
1005             }
1006  
1007             if (strip) 
1008                 strip(md);
1009             reIndex(mid);         // ## Could do this while reading module file
1010             return mid;
1011 
1012         } catch (IOException | SignatureException x) {
1013             if (md != null && md.exists()) {
1014                 try {
1015                     Files.deleteTree(md);
1016                 } catch (IOException y) {
1017                     y.initCause(x);
1018                     throw y;
1019                 }
1020             }
1021             throw x;
1022         }
1023     }
1024 
1025     private ModuleId installFromJarFile(File mf, boolean verifySignature, boolean strip)
1026         throws ConfigurationException, IOException, SignatureException
1027     {
1028         File md = null;
1029         try (JarFile jf = new JarFile(mf, verifySignature)) {
1030             ModuleInfo mi = jf.getModuleInfo();
1031             if (mi == null)
1032                 throw new ConfigurationException(mf + ": not a modular JAR file");
1033 
1034             md = moduleDir(mi.id());
1035             ModuleId mid = mi.id();


1169                 entry.setSize(size);
1170                 entry.setCrc(je.getCrc());
1171                 entry.setCompressedSize(size);
1172             }
1173             jos.putNextEntry(entry);
1174             if (baos.size() > 0)
1175                 baos.writeTo(jos);
1176             jos.closeEntry();
1177         } catch (SecurityException se) {
1178             throw new SignatureException(se);
1179         }
1180     }
1181 
1182     private ModuleId install(File mf, boolean verifySignature, boolean strip)
1183         throws ConfigurationException, IOException, SignatureException
1184     {
1185         ModuleId mid;
1186         if (mf.getName().endsWith(".jar"))
1187             mid = installFromJarFile(mf, verifySignature, strip);
1188         else {

1189             try (FileInputStream in = new FileInputStream(mf)) {
1190                 mid = install(in, verifySignature, strip);
1191             }
1192         }
1193         return mid;
1194     }
1195 
1196     public void install(Collection<File> mfs, boolean verifySignature, boolean strip)
1197         throws ConfigurationException, IOException, SignatureException
1198     {
1199         List<ModuleId> mids = new ArrayList<>();
1200         boolean complete = false;
1201         Throwable ox = null;
1202         try {
1203             for (File mf : mfs)
1204                 mids.add(install(mf, verifySignature, strip));
1205             configure(mids);
1206             complete = true;
1207         } catch (IOException|ConfigurationException x) {
1208             ox = x;


1321         throws ConfigurationException, IOException
1322     {
1323         // ## mids not used yet
1324         for (ModuleInfo mi : listLocalRootModuleInfos()) {
1325             // ## We could be a lot more clever about this!
1326             Configuration<Context> cf
1327                 = Configurator.configure(this, mi.id().toQuery());
1328             new StoredConfiguration(moduleDir(mi.id()), cf).store();
1329         }
1330     }
1331 
1332     public URI findLocalResource(ModuleId mid, String name)
1333         throws IOException
1334     {
1335         return locateContent(mid, name);
1336     }
1337 
1338     public File findLocalNativeLibrary(ModuleId mid, String name)
1339         throws IOException
1340     {
1341         File md = findModuleDir(mid);
1342         if (md == null)


1343             return null;
1344         File f = new File(new File(md, "lib"), name);


1345         if (!f.exists())
1346             return null;
1347         return f;
1348     }
1349 
1350     public File classPath(ModuleId mid)
1351         throws IOException
1352     {
1353         File md = findModuleDir(mid);
1354         if (md == null) {
1355             if (parent != null)
1356                 return parent.classPath(mid);
1357             return null;
1358         }
1359         // ## Check for other formats here
1360         return new File(md, "classes");
1361     }
1362 
1363     /**
1364      * <p> Re-index the classes of the named previously-installed modules, and




 136 
 137     /**
 138      * Defines the storage options that SimpleLibrary supports.
 139      */
 140     public static enum StorageOption {
 141         DEFLATED,
 142     }
 143 
 144     private static final class Header
 145         extends MetaData
 146     {
 147         private static final String FILE
 148             = FileConstants.META_PREFIX + "jigsaw-library";
 149 
 150         private static final int MAJOR_VERSION = 0;
 151         private static final int MINOR_VERSION = 1;
 152 
 153         private static final int DEFLATED = 1 << 0;
 154 
 155         private File parent;
 156         // location of native libs for this library (may be outside the library)
 157         // null:default, to use a per-module 'lib' directory
 158         private File natlibs;
 159         // location of native cmds for this library (may be outside the library)
 160         // null:default, to use a per-module 'bin' directory
 161         private File natcmds;
 162 
 163         private Set<StorageOption> opts;
 164 
 165         public File parent() { return parent; }
 166         public File natlibs() { return natlibs; }
 167         public File natcmds() { return natcmds; }
 168         public boolean isDeflated() {
 169            return opts.contains(StorageOption.DEFLATED);
 170         }
 171 
 172         private Header(File root, File p, File natlibs, File natcmds,
 173                        Set<StorageOption> opts) {
 174             super(MAJOR_VERSION, MINOR_VERSION,
 175                   FileConstants.Type.LIBRARY_HEADER,
 176                   new File(root, FILE));
 177             this.parent = p;
 178             this.natlibs = natlibs;
 179             this.natcmds = natcmds;
 180             this.opts = new HashSet<>(opts);
 181         }
 182 
 183         private Header(File root) {
 184             this(root, null, null, null, Collections.<StorageOption>emptySet());
 185         }
 186 
 187         protected void storeRest(DataOutputStream out)
 188             throws IOException
 189         {
 190             int flags = 0;
 191             if (isDeflated())
 192                 flags |= DEFLATED;
 193             out.writeShort(flags);
 194             out.writeByte((parent != null) ? 1 : 0);
 195             if (parent != null)
 196                 out.writeUTF(parent.toString());
 197             out.writeByte((natlibs != null) ? 1 : 0);
 198             if (natlibs != null)
 199                 out.writeUTF(natlibs.toString());
 200             out.writeByte((natcmds != null) ? 1 : 0);
 201             if (natcmds != null)
 202                 out.writeUTF(natcmds.toString());
 203         }
 204 
 205         protected void loadRest(DataInputStream in)
 206             throws IOException
 207         {
 208             opts = new HashSet<StorageOption>();
 209             int flags = in.readShort();
 210             if ((flags & DEFLATED) == DEFLATED)
 211                 opts.add(StorageOption.DEFLATED);
 212             int b = in.readByte();
 213             if (b != 0)
 214                 parent = new File(in.readUTF());
 215             b = in.readByte();
 216             if (b != 0)
 217                 natlibs = new File(in.readUTF());
 218             b = in.readByte();
 219             if (b != 0)
 220                 natcmds = new File(in.readUTF());
 221         }
 222 
 223         private static Header load(File f)
 224             throws IOException
 225         {
 226             Header h = new Header(f);
 227             h.load();
 228             return h;
 229         }
 230 
 231     }
 232 
 233     private final File root;
 234     private final File canonicalRoot;
 235     private File parentPath;
 236     private File natlibs;
 237     private File natcmds;
 238     private SimpleLibrary parent;
 239     private final Header hd;
 240 
 241     public String name() { return root.toString(); }
 242     public File root() { return canonicalRoot; }
 243     public int majorVersion() { return hd.majorVersion; }
 244     public int minorVersion() { return hd.minorVersion; }
 245     public SimpleLibrary parent() { return parent; }
 246     public File natlibs() { return natlibs; }
 247     public File natcmds() { return natcmds; }
 248     public boolean isDeflated() { return hd.isDeflated(); }
 249 
 250     private URI location = null;
 251     public URI location() {
 252         if (location == null)
 253             location = root().toURI();
 254         return location;
 255     }
 256 
 257     @Override
 258     public String toString() {
 259         return (this.getClass().getName()
 260                 + "[" + canonicalRoot
 261                 + ", v" + hd.majorVersion + "." + hd.minorVersion + "]");
 262     }
 263 
 264     private static void ensureDirectory(File dir) throws IOException {
 265         if (!dir.isDirectory())
 266             throw new IOException(dir + ": Not a directory");
 267     }
 268 
 269     private static void warnWriteable(File dir) {
 270         if (!dir.canWrite())
 271             // TODO: where to write the warning message???
 272             System.out.println("Warning: " + dir + " is not writeable.");
 273     }
 274 
 275     private SimpleLibrary(File path, boolean create, File parentPath,
 276                           File natlibs, File natcmds, Set<StorageOption> opts)
 277         throws IOException
 278     {
 279         root = path;
 280         canonicalRoot = root.getCanonicalFile();
 281         boolean exists = root.exists();
 282         boolean directory = root.isDirectory();
 283         if (!create) {
 284             if (!exists)
 285                 throw new FileNotFoundException(root.toString());
 286             if (!directory)
 287                 throw new IOException(root + ": Exists but is not a directory");
 288             hd = Header.load(root);
 289             if (hd.parent() != null) {
 290                 parent = open(hd.parent());
 291                 this.parentPath = hd.parent();
 292             }
 293             if (hd.natlibs() != null)
 294                 this.natlibs = hd.natlibs();
 295             if (hd.natcmds() != null)
 296                 this.natcmds = hd.natcmds();
 297             return;
 298         }
 299         // create
 300         if (exists) {
 301             if (!directory)
 302                 throw new IOException(root + ": Not a directory");
 303             else if (root.list().length != 0)
 304                 throw new IOException(root + ": Already Exists");
 305         } else if (!root.mkdirs())
 306             throw new IOException(root + ": Cannot create library directory");
 307         if (parentPath != null) {
 308             this.parent = open(parentPath);
 309             this.parentPath = this.parent.root();
 310         }
 311         if (natlibs != null) {
 312             // resolve against the working dir, and store the absolute path
 313             this.natlibs = natlibs.getCanonicalFile();
 314             if (!this.natlibs.exists()) {
 315                 if (!this.natlibs.mkdir())
 316                     throw new IOException(this.natlibs + ": Cannot create lib directory");
 317             } else {
 318                 ensureDirectory(this.natlibs);
 319                 warnWriteable(this.natlibs);
 320             }
 321         }
 322         if (natcmds != null) {
 323             this.natcmds = natcmds.getCanonicalFile();
 324             if (!this.natcmds.exists()) {
 325                 if (!this.natcmds.mkdir())
 326                     throw new IOException(this.natcmds + ": Cannot create cmd directory");
 327             } else {
 328                 ensureDirectory(this.natcmds);
 329                 warnWriteable(this.natcmds);
 330             }
 331         }
 332         hd = new Header(canonicalRoot, this.parentPath, this.natlibs,
 333                         this.natcmds, opts);
 334         hd.store();
 335     }
 336 
 337     public static SimpleLibrary create(File path, File parent, File natlibs,
 338                                        File natcmds, Set<StorageOption> opts)
 339         throws IOException
 340     {
 341         return new SimpleLibrary(path, true, parent, natlibs, natcmds, opts);
 342     }
 343 
 344     public static SimpleLibrary create(File path, File parent,
 345                                        Set<StorageOption> opts)
 346         throws IOException
 347     {
 348         return new SimpleLibrary(path, true, parent, null, null, opts);
 349     }
 350 
 351     public static SimpleLibrary create(File path, File parent)
 352         throws IOException
 353     {
 354         return new SimpleLibrary(path, true, parent, null, null,
 355                                  Collections.<StorageOption>emptySet());
 356     }
 357 
 358     public static SimpleLibrary create(File path, Set<StorageOption> opts)
 359         throws IOException
 360     {
 361         // ## Should default parent to $JAVA_HOME/lib/modules
 362         return new SimpleLibrary(path, true, null, null, null, opts);
 363     }
 364 
 365     public static SimpleLibrary open(File path)
 366         throws IOException
 367     {
 368         return new SimpleLibrary(path, false, null, null, null,
 369                                  Collections.<StorageOption>emptySet());
 370     }
 371 
 372     private static final JigsawModuleSystem jms
 373         = JigsawModuleSystem.instance();
 374 
 375     private static final class Index
 376         extends MetaData
 377     {
 378 
 379         private static String FILE = "index";
 380 
 381         private static int MAJOR_VERSION = 0;
 382         private static int MINOR_VERSION = 1;
 383 
 384         private Set<String> publicClasses;
 385         public Set<String> publicClasses() { return publicClasses; }
 386 
 387         private Set<String> otherClasses;
 388         public Set<String> otherClasses() { return otherClasses; }
 389 


1036     {
1037         install(mfs, root, strip);
1038         configure(null);
1039     }
1040 
1041     @Override
1042     public void installFromManifests(Collection<Manifest> mfs)
1043         throws ConfigurationException, IOException
1044     {
1045         installFromManifests(mfs, false);
1046     }
1047 
1048     private ModuleFileVerifier.Parameters mfvParams;
1049 
1050     private ModuleId install(InputStream is, boolean verifySignature, boolean strip)
1051         throws ConfigurationException, IOException, SignatureException
1052     {
1053         BufferedInputStream bin = new BufferedInputStream(is);
1054         DataInputStream in = new DataInputStream(bin);
1055         File md = null;
1056         try (ModuleFile.Reader mr = new ModuleFile.Reader(in, this)) {
1057             byte[] mib = mr.readStart();
1058             ModuleInfo mi = jms.parseModuleInfo(mib);
1059             md = moduleDir(mi.id());
1060             ModuleId mid = mi.id();
1061             if (md.exists())
1062                 throw new ConfigurationException(mid + ": Already installed");
1063             if (!md.mkdirs())
1064                 throw new IOException(md + ": Cannot create");
1065 
1066             if (verifySignature && mr.hasSignature()) {
1067                 ModuleFileVerifier mfv = new SignedModule.PKCS7Verifier(mr);
1068                 if (mfvParams == null) {
1069                     mfvParams = new SignedModule.VerifierParameters();
1070                 }
1071                 // Verify the module signature and validate the signer's
1072                 // certificate chain
1073                 Set<CodeSigner> signers = mfv.verifySignature(mfvParams);
1074 
1075                 // Verify the module header hash and the module info hash
1076                 mfv.verifyHashesStart(mfvParams);


1079                 // ## should be granted?
1080 
1081                 // Store signer info
1082                 new Signers(md, signers).store();
1083 
1084                 // Read and verify the rest of the hashes
1085                 mr.readRest(md, isDeflated());
1086                 mfv.verifyHashesRest(mfvParams);
1087             } else {
1088                 mr.readRest(md, isDeflated());
1089             }
1090 
1091             if (strip)
1092                 strip(md);
1093             reIndex(mid);         // ## Could do this while reading module file
1094             return mid;
1095 
1096         } catch (IOException | SignatureException x) {
1097             if (md != null && md.exists()) {
1098                 try {
1099                    ModuleFile.Reader.remove(md);
1100                 } catch (IOException y) {
1101                     y.initCause(x);
1102                     throw y;
1103                 }
1104             }
1105             throw x;
1106         }
1107     }
1108 
1109     private ModuleId installFromJarFile(File mf, boolean verifySignature, boolean strip)
1110         throws ConfigurationException, IOException, SignatureException
1111     {
1112         File md = null;
1113         try (JarFile jf = new JarFile(mf, verifySignature)) {
1114             ModuleInfo mi = jf.getModuleInfo();
1115             if (mi == null)
1116                 throw new ConfigurationException(mf + ": not a modular JAR file");
1117 
1118             md = moduleDir(mi.id());
1119             ModuleId mid = mi.id();


1253                 entry.setSize(size);
1254                 entry.setCrc(je.getCrc());
1255                 entry.setCompressedSize(size);
1256             }
1257             jos.putNextEntry(entry);
1258             if (baos.size() > 0)
1259                 baos.writeTo(jos);
1260             jos.closeEntry();
1261         } catch (SecurityException se) {
1262             throw new SignatureException(se);
1263         }
1264     }
1265 
1266     private ModuleId install(File mf, boolean verifySignature, boolean strip)
1267         throws ConfigurationException, IOException, SignatureException
1268     {
1269         ModuleId mid;
1270         if (mf.getName().endsWith(".jar"))
1271             mid = installFromJarFile(mf, verifySignature, strip);
1272         else {
1273             // ## Assume jmod file, should we check extension?
1274             try (FileInputStream in = new FileInputStream(mf)) {
1275                 mid = install(in, verifySignature, strip);
1276             }
1277         }
1278         return mid;
1279     }
1280 
1281     public void install(Collection<File> mfs, boolean verifySignature, boolean strip)
1282         throws ConfigurationException, IOException, SignatureException
1283     {
1284         List<ModuleId> mids = new ArrayList<>();
1285         boolean complete = false;
1286         Throwable ox = null;
1287         try {
1288             for (File mf : mfs)
1289                 mids.add(install(mf, verifySignature, strip));
1290             configure(mids);
1291             complete = true;
1292         } catch (IOException|ConfigurationException x) {
1293             ox = x;


1406         throws ConfigurationException, IOException
1407     {
1408         // ## mids not used yet
1409         for (ModuleInfo mi : listLocalRootModuleInfos()) {
1410             // ## We could be a lot more clever about this!
1411             Configuration<Context> cf
1412                 = Configurator.configure(this, mi.id().toQuery());
1413             new StoredConfiguration(moduleDir(mi.id()), cf).store();
1414         }
1415     }
1416 
1417     public URI findLocalResource(ModuleId mid, String name)
1418         throws IOException
1419     {
1420         return locateContent(mid, name);
1421     }
1422 
1423     public File findLocalNativeLibrary(ModuleId mid, String name)
1424         throws IOException
1425     {
1426         File f = natlibs();
1427         if (f == null) {
1428             f = findModuleDir(mid);
1429             if (f == null)
1430                 return null;
1431             f = new File(f, "lib");
1432         }
1433         f = new File(f, name);
1434         if (!f.exists())
1435             return null;
1436         return f;
1437     }
1438 
1439     public File classPath(ModuleId mid)
1440         throws IOException
1441     {
1442         File md = findModuleDir(mid);
1443         if (md == null) {
1444             if (parent != null)
1445                 return parent.classPath(mid);
1446             return null;
1447         }
1448         // ## Check for other formats here
1449         return new File(md, "classes");
1450     }
1451 
1452     /**
1453      * <p> Re-index the classes of the named previously-installed modules, and