1 /* 2 * Copyright (c) 2010, 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. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 package com.sun.classanalyzer; 24 25 import com.sun.classanalyzer.ResourceFile.ServiceProviderConfigFile; 26 import com.sun.classanalyzer.ClassPath.*; 27 import java.io.*; 28 import java.util.*; 29 import java.util.jar.JarEntry; 30 import java.util.jar.JarFile; 31 32 /** 33 * Modularize classes and resources from legacy classpath to 34 * a module path containing a list of modules, one directory per module. 35 * 36 */ 37 public class Modularizer { 38 39 private final File modulepath; 40 private final Set<Module> modules; 41 private final ClassPath cpath; 42 public Modularizer(ClassPath cpath, File modulepath, Set<Module> modules) { 43 this.cpath = cpath; 44 this.modulepath = modulepath; 45 this.modules = modules; 46 } 47 48 /** 49 * Modularizes the legacy class path files into 50 * multiple modules. 51 * @param update true if only modules with newer classes 52 * resources are updated. 53 */ 54 void run(boolean update) throws IOException { 55 for (Module m : modules) { 56 File mdir = new File(modulepath, m.name()); 57 ModuleContent mc = new ModuleContent(m, mdir); 58 mc.copy(update); 59 if (mc.isUpdated()) { 60 mc.printStats(); 61 } 62 } 63 } 64 65 class ModuleContent { 66 int classes = 0; 67 int resources = 0; 68 long classBytes = 0; 69 long resourceBytes = 0; 70 final Module module; 71 final File classDir; // destination for classes and resources 72 final File mdir; 73 74 /** 75 * Module content. The 76 * 77 * @param m module 78 * @param dir directory of the module content 79 */ 80 ModuleContent(Module m, File dir) throws IOException { 81 this.module = m; 82 this.mdir = dir; 83 this.classDir = new File(dir, "classes"); 84 } 85 86 /** 87 * Tests if any file in this module content is updated. 88 */ 89 boolean isUpdated() { 90 return (classes + resources) > 0; 91 } 92 93 /** 94 * Copies the module content (classes and resources file) 95 * to the destination. 96 * 97 * @param update true if only modified files are copied; 98 * otherwise, all classes and resource files for this module 99 * are copied. 100 */ 101 void copy(boolean update) throws IOException { 102 if (!classDir.exists()) { 103 Files.mkdirs(classDir); 104 // override all files 105 update = false; 106 } 107 108 final boolean copyAll = update == false; 109 Module.Visitor<Void, File> visitor = new Module.Visitor<Void, File>() { 110 @Override 111 public Void visitClass(Klass k, File dir) { 112 String pathname = k.getClassFilePathname(); 113 Filter filter = copyAll ? null : new Filter(classDir, pathname); 114 long bytes; 115 try { 116 bytes = writeClass(k, filter); 117 if (bytes > 0) { 118 classes++; 119 classBytes += bytes; 120 } 121 } catch (IOException ex) { 122 throw new RuntimeException(ex); 123 } 124 return null; 125 } 126 127 @Override 128 public Void visitResource(ResourceFile r, File dir) { 129 String pathname = r.getPathname(); 130 Filter filter = copyAll ? null : new Filter(classDir, pathname); 131 try { 132 long bytes = writeResource(r, filter); 133 if (bytes > 0) { 134 resources++; 135 resourceBytes += bytes; 136 } 137 } catch (IOException ex) { 138 throw new RuntimeException(ex); 139 } 140 return null; 141 } 142 }; 143 144 module.visit(visitor, mdir); 145 } 146 147 void printStats() { 148 System.out.format("%s: %d classes (%d bytes) %d resource files (%d bytes) copied%n", 149 module.name(), classes, classBytes, resources, resourceBytes); 150 } 151 152 private ClassPathEntry lastVisitedClassPath = null; 153 /** 154 * Write the classfile of the given class if not filtered 155 * 156 * @param k a Klass 157 * @param filter a Filter 158 * @return the number of bytes copied 159 */ 160 long writeClass(Klass k, Filter filter) throws IOException { 161 String pathname = k.getClassFilePathname(); 162 Copier visitor = new Copier(classDir, filter); 163 if (lastVisitedClassPath != null) { 164 ClassPathEntry cp = lastVisitedClassPath.accept(visitor, pathname); 165 if (cp != null) { 166 assert cp == lastVisitedClassPath; 167 return visitor.bytes; 168 } 169 } 170 171 // locate the source of the given class from the classpath 172 for (ClassPathEntry cp : cpath.entries()) { 173 ClassPathEntry src = cp.accept(visitor, pathname); 174 if (src != null) { 175 // cache the ClassPathEntry from which this class is copied 176 // Most of the files in a module likely come from the 177 // same jar or directory. 178 lastVisitedClassPath = src; 179 return visitor.bytes; 180 } 181 } 182 return 0; 183 } 184 185 /** 186 * Write the resource file if not filtered 187 * 188 * @param res a ResourceFile 189 * @param filter a Filter 190 * @return the number of bytes copied 191 */ 192 long writeResource(ResourceFile res, Filter filter) throws IOException { 193 String pathname = res.getPathname(); 194 Copier visitor = new Copier(classDir, filter); 195 if (lastVisitedClassPath != null) { 196 ClassPathEntry cp = lastVisitedClassPath.accept(visitor, pathname); 197 if (cp != null) { 198 assert cp == lastVisitedClassPath; 199 return visitor.bytes; 200 } 201 } 202 203 // locate the source of the given resource file from the classpath 204 for (ClassPathEntry cp : cpath.entries()) { 205 ClassPathEntry src = cp.accept(visitor, pathname); 206 if (src != null) { 207 // cache the ClassPathEntry from which this class is copied 208 // Most of the files in a module likely come from the 209 // same jar or directory. 210 lastVisitedClassPath = src; 211 return visitor.bytes; 212 } 213 } 214 return 0; 215 } 216 217 /** 218 * A ClassPathEntry visitor to copy a file to the given destination 219 * if not filtered. 220 */ 221 class Copier implements ClassPathEntry.Visitor<ClassPathEntry, String> { 222 final Filter filter; 223 final File dest; 224 long bytes = 0; 225 226 Copier(File dest, Filter filter) { 227 this.filter = filter; 228 this.dest = dest; 229 } 230 231 @Override 232 public ClassPathEntry visitFile(File src, ClassPathEntry cp, String pathname) throws IOException { 233 String name = pathname.replace(File.separatorChar, '/'); 234 if (cp.getName().endsWith(File.separator + pathname) 235 && matches(src, name)) { 236 if (filter == null || filter.accept(src)) { 237 File dst = new File(dest, pathname); 238 bytes += copy(src, dst); 239 } 240 return cp; 241 } else { 242 return null; 243 } 244 } 245 246 @Override 247 public ClassPathEntry visitDir(File dir, ClassPathEntry cp, String pathname) throws IOException { 248 File src = new File(cp.getFile(), pathname); 249 File dst = new File(dest, pathname); 250 String name = pathname.replace(File.separatorChar, '/'); 251 252 if (src.exists() && matches(src, name)) { 253 if (filter == null || filter.accept(src)) { 254 bytes += copy(src, dst); 255 } 256 return cp; 257 } else { 258 return null; 259 } 260 } 261 262 @Override 263 public ClassPathEntry visitJarFile(JarFile jf, ClassPathEntry cp, String pathname) throws IOException { 264 String name = pathname.replace(File.separatorChar, '/'); 265 JarEntry e = jf.getJarEntry(name); 266 if (e != null && matches(jf, e, name)) { 267 if (filter == null || filter.accept(jf, e)) { 268 bytes += copy(jf, e); 269 } 270 return cp; 271 } else { 272 return null; 273 } 274 } 275 276 boolean matches(File src, String name) throws IOException { 277 if (!name.startsWith("META-INF/services")) { 278 return true; 279 } 280 281 BufferedInputStream in = new BufferedInputStream(new FileInputStream(src)); 282 try { 283 return matches(in, name); 284 } finally { 285 in.close(); 286 } 287 } 288 289 boolean matches(JarFile jf, JarEntry e, String name) throws IOException { 290 if (!name.startsWith("META-INF/services")) { 291 return true; 292 } 293 return matches(jf.getInputStream(e), name); 294 } 295 296 boolean matches(InputStream in, String name) throws IOException { 297 ServiceProviderConfigFile sp = new ServiceProviderConfigFile(name, in); 298 for (String p : sp.providers) { 299 Klass k = Klass.findKlass(p); 300 if (k == null) { 301 Trace.trace("Service %s: provider class %s not found%n", sp.service, p); 302 continue; 303 } 304 if (module.contains(k)) { 305 return true; 306 } 307 } 308 // return true if no provider; otherwise false 309 return sp.providers.isEmpty(); 310 } 311 312 long copy(JarFile jf, JarEntry e) throws IOException { 313 File dst = new File(dest, e.getName().replace('/', File.separatorChar)); 314 if (!dst.exists()) { 315 Files.createFile(dst); 316 } 317 318 byte[] buf = new byte[8192]; 319 InputStream in = jf.getInputStream(e); 320 long bytes = 0; 321 try { 322 FileOutputStream out = new FileOutputStream(dst); 323 try { 324 int n; 325 while ((n = in.read(buf)) > 0) { 326 out.write(buf, 0, n); 327 bytes += n; 328 } 329 } finally { 330 out.close(); 331 } 332 } finally { 333 in.close(); 334 } 335 336 long lastModified = e.getTime(); 337 if (lastModified > 0) { 338 dst.setLastModified(lastModified); 339 } 340 return bytes; 341 } 342 343 long copy(File src, File dst) 344 throws IOException { 345 assert src.exists(); 346 347 if (!dst.exists()) { 348 Files.createFile(dst); 349 } 350 351 BufferedInputStream in = new BufferedInputStream(new FileInputStream(src)); 352 byte[] buf = new byte[8192]; 353 long bytes = 0; 354 try { 355 FileOutputStream out = new FileOutputStream(dst); 356 try { 357 int n; 358 while ((n = in.read(buf)) > 0) { 359 out.write(buf, 0, n); 360 bytes += n; 361 } 362 } finally { 363 out.close(); 364 } 365 } finally { 366 in.close(); 367 } 368 dst.setLastModified(src.lastModified()); 369 if (src.canExecute()) { 370 dst.setExecutable(true, false); 371 } 372 return bytes; 373 } 374 } 375 } 376 377 /** 378 * A filter that accepts files that don't exist in the given 379 * location or modified since it's copied. 380 */ 381 class Filter implements ClassPath.Filter { 382 private final long timestamp; 383 Filter(File dir, String pathname) { 384 File destfile = new File(dir, pathname); 385 this.timestamp = destfile.exists() ? destfile.lastModified() : -1L; 386 } 387 388 @Override 389 public boolean accept(File f) throws IOException { 390 if (f.isDirectory()) { 391 return true; 392 } 393 394 long ts = f.lastModified(); 395 return (timestamp < 0 || ts < 0 || timestamp < ts); 396 } 397 398 @Override 399 public boolean accept(JarFile jf, JarEntry e) throws IOException { 400 long ts = e.getTime(); 401 return timestamp < 0 || ts < 0 || timestamp < ts; 402 } 403 } 404 405 public static void main(String[] args) throws Exception { 406 String jdkhome = null; 407 String classpath = null; 408 String classlistDir = null; 409 String modulepath = null; 410 boolean update = false; 411 412 // process arguments 413 int i = 0; 414 while (i < args.length) { 415 String arg = args[i++]; 416 if (arg.equals("-jdkhome")) { 417 jdkhome = getOption(args, i++); 418 } else if (arg.equals("-classpath")) { 419 classpath = getOption(args, i++); 420 } else if (arg.equals("-modulepath")) { 421 modulepath = getOption(args, i++); 422 } else if (arg.equals("-classlistdir")) { 423 classlistDir = getOption(args, i++); 424 } else if (arg.equals("-update")) { 425 // copy new files only 426 update = true; 427 } else { 428 error("Invalid option: " + arg); 429 } 430 } 431 432 if (jdkhome == null && classpath == null) { 433 error("-jdkhome and -classpath not set"); 434 } 435 436 if (jdkhome != null && classpath != null) { 437 error("Both -jdkhome and -classpath are set"); 438 } 439 440 if (classlistDir == null || modulepath == null) { 441 error("-modulepath or -classlist not set"); 442 } 443 444 ClassPath cpath = null; 445 if (jdkhome != null) { 446 cpath = ClassPath.newJDKClassPath(jdkhome); 447 } else if (classpath != null) { 448 cpath = ClassPath.newInstance(classpath); 449 } 450 451 ClassListReader reader = new ClassListReader(classlistDir, "default"); 452 Set<Module> modules = reader.run(); 453 Modularizer modularizer = new Modularizer(cpath, new File(modulepath), modules); 454 modularizer.run(update); 455 } 456 457 private static String getOption(String[] args, int index) { 458 if (index < args.length) { 459 return args[index]; 460 } else { 461 error(args[index-1] + ": Missing argument"); 462 } 463 return null; 464 } 465 466 private static void error(String msg) { 467 System.err.println("ERROR: " + msg); 468 System.out.println(usage()); 469 System.exit(-1); 470 } 471 472 private static String usage() { 473 StringBuilder sb = new StringBuilder(); 474 sb.append("Usage: Modularizer <options>\n"); 475 sb.append("Options: \n"); 476 sb.append("\t-jdkhome <JDK home> where all jars will be parsed\n"); 477 sb.append("\t-classpath <classpath> where classes and jars will be parsed\n"); 478 sb.append("\t Either -jdkhome or -classpath option can be used.\n"); 479 sb.append("\t-classlistdir <classlist dir>\n"); 480 sb.append("\t-update update modules with newer files\n"); 481 sb.append("\t-modulepath <module-path> for writing modules\n"); 482 return sb.toString(); 483 } 484 }