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             if (res.isService())
 194                 return writeService(res, filter);
 195 
 196             String pathname = res.getPathname();
 197             Copier visitor = new Copier(classDir, filter);
 198             if (lastVisitedClassPath != null) {
 199                 ClassPathEntry cp = lastVisitedClassPath.accept(visitor, pathname);
 200                 if (cp != null) {
 201                     assert cp == lastVisitedClassPath;
 202                     return visitor.bytes;
 203                 }
 204             }
 205 
 206             // locate the source of the given resource file from the classpath
 207             for (ClassPathEntry cp : cpath.entries()) {
 208                 ClassPathEntry src = cp.accept(visitor, pathname);
 209                 if (src != null) {
 210                     // cache the ClassPathEntry from which this class is copied
 211                     // Most of the files in a module likely come from the
 212                     // same jar or directory.
 213                     lastVisitedClassPath = src;
 214                     return visitor.bytes;
 215                 }
 216             }
 217             return 0;
 218         }
 219 
 220         /**
 221          * Write the service descriptor file if not filtered
 222          *
 223          * @param res a ResourceFile
 224          * @param filter a Filter
 225          * @return the number of bytes copied
 226          */
 227         long writeService(ResourceFile res, Filter filter) throws IOException {
 228             String pathname = res.getPathname();
 229             Copier visitor = new Copier(classDir, filter);
 230             boolean foundOne = false;
 231             int bytes = 0;
 232 
 233             // scan all class path entries for services
 234             for (ClassPathEntry cp : cpath.entries()) {
 235                 ClassPathEntry src = cp.accept(visitor, pathname);
 236                 if (src != null) {
 237                     bytes += visitor.bytes;
 238                     if (foundOne == false) {
 239                         foundOne = true;
 240                         visitor = new Copier(classDir, null, true); // append subsequent
 241                     }
 242                 }
 243             }
 244             return bytes;
 245         }
 246 
 247         /**
 248          * A ClassPathEntry visitor to copy a file to the given destination
 249          * if not filtered.
 250          */
 251         class Copier implements ClassPathEntry.Visitor<ClassPathEntry, String> {
 252             final Filter filter;
 253             final File dest;
 254             final boolean append;
 255             long bytes = 0;
 256 
 257             Copier(File dest, Filter filter) {
 258                 this(dest, filter, false);
 259             }
 260 
 261             Copier(File dest, Filter filter, boolean append) {
 262                 this.filter = filter;
 263                 this.dest = dest;
 264                 this.append = append;
 265             }
 266 
 267             private boolean isService(String name) {
 268                 return name.startsWith("META-INF/services") ? true : false;
 269             }
 270 
 271             @Override
 272             public ClassPathEntry visitFile(File src, ClassPathEntry cp, String pathname) throws IOException {
 273                 String name = pathname.replace(File.separatorChar, '/');
 274                 if (cp.getName().endsWith(File.separator + pathname)
 275                         && matches(src, name)) {
 276                     if (filter == null || filter.accept(src)) {
 277                         File dst = new File(dest, pathname);
 278                         bytes += copy(src, dst);
 279                     }
 280                     return cp;
 281                 } else {
 282                     return null;
 283                 }
 284             }
 285 
 286             @Override
 287             public ClassPathEntry visitDir(File dir, ClassPathEntry cp, String pathname) throws IOException {
 288                 File src = new File(cp.getFile(), pathname);
 289                 File dst = new File(dest, pathname);
 290                 String name = pathname.replace(File.separatorChar, '/');
 291 
 292                 if (src.exists() && matches(src, name)) {
 293                     if (filter == null || filter.accept(src)) {
 294                         bytes += copy(src, dst);
 295                     }
 296                     return cp;
 297                 } else {
 298                     return null;
 299                 }
 300             }
 301 
 302             @Override
 303             public ClassPathEntry visitJarFile(JarFile jf, ClassPathEntry cp, String pathname) throws IOException {
 304                 String name = pathname.replace(File.separatorChar, '/');
 305                 JarEntry e = jf.getJarEntry(name);
 306                 if (e != null && matches(jf, e, name)) {
 307                     if (filter == null || filter.accept(jf, e)) {
 308                         bytes += copy(jf, e);
 309                     }
 310                     return cp;
 311                 } else {
 312                     return null;
 313                 }
 314             }
 315 
 316             boolean matches(File src, String name) throws IOException {
 317                 if (!isService(name)) {
 318                     return true;
 319                 }
 320 
 321                 try (FileInputStream fis = new FileInputStream(src);
 322                      BufferedInputStream in = new BufferedInputStream(fis)) {
 323                     return matches(in, name);


 324                 }
 325             }
 326 
 327             boolean matches(JarFile jf, JarEntry e, String name) throws IOException {
 328                 if (!isService(name)) {
 329                     return true;
 330                 }
 331                 return matches(jf.getInputStream(e), name);
 332             }
 333 
 334             boolean matches(InputStream in, String name) throws IOException {
 335                 ServiceProviderConfigFile sp = new ServiceProviderConfigFile(name, in);
 336                 for (String p : sp.providers) {
 337                     Klass k = Klass.findKlass(p);
 338                     if (k == null) {
 339                         Trace.trace("Service %s: provider class %s not found%n", sp.service, p);
 340                         continue;
 341                     }
 342                     if (module.contains(k)) {
 343                         return true;
 344                     }
 345                 }
 346                 // return true if no provider; otherwise false
 347                 return sp.providers.isEmpty();
 348             }
 349 
 350             long copy(JarFile jf, JarEntry e) throws IOException {
 351                 File dst = new File(dest, e.getName().replace('/', File.separatorChar));
 352                 if (!dst.exists()) {
 353                     Files.createFile(dst);
 354                 }
 355 
 356                 byte[] buf = new byte[8192];

 357                 long bytes = 0;
 358                 try (InputStream in = jf.getInputStream(e);
 359                      FileOutputStream out = new FileOutputStream(dst, append)) {

 360                     int n;
 361                     while ((n = in.read(buf)) > 0) {
 362                         out.write(buf, 0, n);
 363                         bytes += n;
 364                     }


 365                 }



 366 
 367                 long lastModified = e.getTime();
 368                 if (lastModified > 0) {
 369                     dst.setLastModified(lastModified);
 370                 }
 371                 return bytes;
 372             }
 373 
 374             long copy(File src, File dst)
 375                     throws IOException {
 376                 assert src.exists();
 377 
 378                 if (!dst.exists()) {
 379                     Files.createFile(dst);
 380                 }
 381 

 382                 byte[] buf = new byte[8192];
 383                 long bytes = 0;
 384                 try (InputStream fin = new FileInputStream(src);
 385                      BufferedInputStream in = new BufferedInputStream(fin);
 386                      FileOutputStream out = new FileOutputStream(dst, append)) {
 387                     int n;
 388                     while ((n = in.read(buf)) > 0) {
 389                         out.write(buf, 0, n);
 390                         bytes += n;
 391                     }


 392                 }



 393                 dst.setLastModified(src.lastModified());
 394                 if (src.canExecute()) {
 395                     dst.setExecutable(true, false);
 396                 }
 397                 return bytes;
 398             }
 399         }
 400     }
 401 
 402     /**
 403      * A filter that accepts files that don't exist in the given
 404      * location or modified since it's copied.
 405      */
 406     class Filter implements ClassPath.Filter {
 407         private final long timestamp;
 408         Filter(File dir, String pathname) {
 409             File destfile = new File(dir, pathname);
 410             this.timestamp = destfile.exists() ? destfile.lastModified() : -1L;
 411         }
 412 
 413         @Override
 414         public boolean accept(File f) throws IOException {
 415             if (f.isDirectory()) {
 416                 return true;
 417             }
 418 
 419             long ts = f.lastModified();
 420             return (timestamp < 0 || ts < 0 || timestamp < ts);
 421         }
 422 
 423         @Override
 424         public boolean accept(JarFile jf, JarEntry e) throws IOException {
 425             long ts = e.getTime();
 426             return timestamp < 0 || ts < 0 || timestamp < ts;
 427         }
 428     }
 429 
 430     public static void main(String[] args) throws Exception {
 431         String jdkhome = null;
 432         String classpath = null;
 433         String classlistDir = null;
 434         String modulepath = null;
 435         boolean update = false;
 436 
 437         // process arguments
 438         int i = 0;
 439         while (i < args.length) {
 440             String arg = args[i++];
 441             if (arg.equals("-jdkhome")) {
 442                 jdkhome = getOption(args, i++);
 443             } else if (arg.equals("-classpath")) {
 444                 classpath = getOption(args, i++);
 445             } else if (arg.equals("-modulepath")) {
 446                 modulepath = getOption(args, i++);
 447             } else if (arg.equals("-classlistdir")) {
 448                 classlistDir = getOption(args, i++);
 449             } else if (arg.equals("-update")) {
 450                 // copy new files only
 451                 update = true;
 452             } else {
 453                 error("Invalid option: " + arg);
 454             }
 455         }
 456 
 457         if (jdkhome == null && classpath == null) {
 458             error("-jdkhome and -classpath not set");
 459         }
 460 
 461         if (jdkhome != null && classpath != null) {
 462             error("Both -jdkhome and -classpath are set");
 463         }
 464 
 465         if (classlistDir == null || modulepath == null) {
 466             error("-modulepath or -classlist not set");
 467         }
 468 
 469         ClassPath cpath = null;
 470         if (jdkhome != null) {
 471             cpath = ClassPath.newJDKClassPath(jdkhome);
 472         } else if (classpath != null) {
 473             cpath = ClassPath.newInstance(classpath);
 474         }
 475 
 476         ClassListReader reader = new ClassListReader(classlistDir, "default");
 477         Set<Module> modules = reader.run();
 478         Modularizer modularizer = new Modularizer(cpath, new File(modulepath), modules);
 479         modularizer.run(update);
 480     }
 481 
 482     private static String getOption(String[] args, int index) {
 483         if (index < args.length) {
 484             return args[index];
 485         } else {
 486             error(args[index-1] + ": Missing argument");
 487         }
 488         return null;
 489     }
 490 
 491     private static void error(String msg) {
 492         System.err.println("ERROR: " + msg);
 493         System.out.println(usage());
 494         System.exit(-1);
 495     }
 496 
 497     private static String usage() {
 498         StringBuilder sb = new StringBuilder();
 499         sb.append("Usage: Modularizer <options>\n");
 500         sb.append("Options: \n");
 501         sb.append("\t-jdkhome      <JDK home> where all jars will be parsed\n");
 502         sb.append("\t-classpath    <classpath> where classes and jars will be parsed\n");
 503         sb.append("\t              Either -jdkhome or -classpath option can be used.\n");
 504         sb.append("\t-classlistdir <classlist dir>\n");
 505         sb.append("\t-update       update modules with newer files\n");
 506         sb.append("\t-modulepath   <module-path> for writing modules\n");
 507         return sb.toString();
 508     }
 509 }
--- EOF ---