1 /* 2 * Copyright (c) 2013, 2017, 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 24 package sun.hotspot.tools.ctw; 25 26 import java.io.Closeable; 27 import java.nio.file.Files; 28 import java.nio.file.Path; 29 import java.nio.file.Paths; 30 import java.util.Collections; 31 import java.util.List; 32 import java.util.Objects; 33 import java.util.concurrent.Executor; 34 import java.util.concurrent.atomic.AtomicLong; 35 import java.util.function.Function; 36 import java.util.regex.Matcher; 37 import java.util.regex.Pattern; 38 import java.util.stream.Stream; 39 40 /** 41 * Handler for a path, responsible for processing classes in the path. 42 */ 43 public class PathHandler implements Closeable { 44 public static abstract class PathEntry implements Closeable { 45 private final ClassLoader loader = new PathEntryClassLoader(this::findByteCode); 46 47 /** 48 * returns bytecode for the class 49 * @param name binary name of the class 50 * @return bytecode of the class or null if handler does not have any 51 * code for this name 52 */ 53 protected abstract byte[] findByteCode(String name); 54 55 protected final Path root; 56 57 /** 58 * @param root path entry root 59 * @throws NullPointerException if {@code root} is {@code null} 60 */ 61 protected PathEntry(Path root) { 62 Objects.requireNonNull(root, "root can not be null"); 63 this.root = root.normalize(); 64 } 65 66 /** 67 * @return classloader which will be used to define classes 68 */ 69 protected final ClassLoader loader() { 70 return loader; 71 } 72 73 /** 74 * @return stream of all classes in the specified path. 75 */ 76 protected abstract Stream<String> classes(); 77 78 /** 79 * @return string description of the specific path. 80 */ 81 protected abstract String description(); 82 83 public void close() { } 84 85 } 86 87 private static class PathEntryClassLoader extends java.lang.ClassLoader { 88 private final Function<String, byte[]> findByteCode; 89 90 private PathEntryClassLoader(Function<String, byte[]> findByteCode) { 91 this.findByteCode = findByteCode; 92 } 93 94 @Override 95 protected Class<?> findClass(String name) throws ClassNotFoundException { 96 byte[] code = findByteCode.apply(name); 97 if (code == null) { 98 return super.findClass(name); 99 } else { 100 return defineClass(name, code, 0, code.length); 101 } 102 } 103 } 104 105 private static final AtomicLong CLASS_COUNT = new AtomicLong(0L); 106 private static volatile boolean CLASSES_LIMIT_REACHED = false; 107 private static final Pattern JAR_IN_DIR_PATTERN 108 = Pattern.compile("^(.*[/\\\\])?\\*$"); 109 110 /** 111 * Factory method. Constructs list of handlers for {@code path}. 112 * 113 * @param path the path to process 114 * @throws NullPointerException if {@code path} or {@code executor} is 115 * {@code null} 116 */ 117 public static List<PathHandler> create(String path) { 118 Objects.requireNonNull(path); 119 Matcher matcher = JAR_IN_DIR_PATTERN.matcher(path); 120 if (matcher.matches()) { 121 path = matcher.group(1); 122 path = path.isEmpty() ? "." : path; 123 return ClassPathJarInDirEntry.create(Paths.get(path)); 124 } else { 125 path = path.isEmpty() ? "." : path; 126 Path p = Paths.get(path); 127 PathEntry entry; 128 if (isJarFile(p)) { 129 entry = new ClassPathJarEntry(p); 130 } else if (isListFile(p)) { 131 entry = new ClassesListInFile(p); 132 } else if (isJimageFile(p)) { 133 entry = new ClassPathJimageEntry(p); 134 } else { 135 entry = new ClassPathDirEntry(p); 136 } 137 return Collections.singletonList(new PathHandler(entry)); 138 } 139 } 140 141 private static boolean isJarFile(Path path) { 142 if (Files.isRegularFile(path)) { 143 String name = path.toString(); 144 return Utils.endsWithIgnoreCase(name, ".zip") 145 || Utils.endsWithIgnoreCase(name, ".jar"); 146 } 147 return false; 148 } 149 150 private static boolean isJimageFile(Path path) { 151 String filename = path.getFileName().toString(); 152 return Files.isRegularFile(path) 153 && ("modules".equals(filename) 154 || Utils.endsWithIgnoreCase(filename, ".jimage")); 155 } 156 157 private static boolean isListFile(Path path) { 158 if (Files.isRegularFile(path)) { 159 String name = path.toString(); 160 return Utils.endsWithIgnoreCase(name, ".lst"); 161 } 162 return false; 163 } 164 165 private final PathEntry entry; 166 protected PathHandler(PathEntry entry) { 167 Objects.requireNonNull(entry); 168 this.entry = entry; 169 } 170 171 172 @Override 173 public void close() { 174 entry.close(); 175 } 176 177 /** 178 * Processes all classes in the specified path. 179 * @param executor executor used for process task invocation 180 */ 181 public final void process(Executor executor) { 182 CompileTheWorld.OUT.println(entry.description()); 183 entry.classes().forEach(s -> processClass(s, executor)); 184 } 185 186 /** 187 * @return count of all classes in the specified path. 188 */ 189 public long classCount() { 190 return entry.classes().count(); 191 } 192 193 194 /** 195 * Processes specified class. 196 * @param name fully qualified name of class to process 197 */ 198 protected final void processClass(String name, Executor executor) { 199 Objects.requireNonNull(name); 200 if (isFinished()) { 201 return; 202 } 203 long id = CLASS_COUNT.incrementAndGet(); 204 if (id > Utils.COMPILE_THE_WORLD_STOP_AT) { 205 CLASSES_LIMIT_REACHED = true; 206 return; 207 } 208 if (id >= Utils.COMPILE_THE_WORLD_START_AT) { 209 Class<?> aClass; 210 Thread.currentThread().setContextClassLoader(entry.loader()); 211 try { 212 CompileTheWorld.OUT.printf("[%d]\t%s%n", id, name); 213 aClass = entry.loader().loadClass(name); 214 Compiler.compileClass(aClass, id, executor); 215 } catch (Throwable e) { 216 CompileTheWorld.OUT.printf("[%d]\t%s\tWARNING skipped: %s%n", 217 id, name, e); 218 e.printStackTrace(CompileTheWorld.ERR); 219 } 220 } 221 } 222 223 /** 224 * @return count of processed classes 225 */ 226 public static long getProcessedClassCount() { 227 long id = CLASS_COUNT.get(); 228 if (id < Utils.COMPILE_THE_WORLD_START_AT) { 229 return 0; 230 } 231 if (id > Utils.COMPILE_THE_WORLD_STOP_AT) { 232 return Utils.COMPILE_THE_WORLD_STOP_AT - Utils.COMPILE_THE_WORLD_START_AT + 1; 233 } 234 return id - Utils.COMPILE_THE_WORLD_START_AT + 1; 235 } 236 237 /** 238 * @return {@code true} if classes limit is reached and processing should be stopped 239 */ 240 public static boolean isFinished() { 241 return CLASSES_LIMIT_REACHED; 242 } 243 244 } 245