1 19 20 package org.netbeans.modules.java.source.usages; 21 22 import java.io.BufferedInputStream ; 23 import java.io.File ; 24 import java.io.FileInputStream ; 25 import java.io.IOException ; 26 import java.io.InputStream ; 27 import java.net.URI ; 28 import java.net.URL ; 29 import java.util.ArrayList ; 30 import java.util.Collection ; 31 import java.util.EnumSet ; 32 import java.util.Enumeration ; 33 import java.util.HashMap ; 34 import java.util.HashSet ; 35 import java.util.List ; 36 import java.util.Map ; 37 import java.util.Set ; 38 import java.util.concurrent.atomic.AtomicBoolean ; 39 import java.util.logging.Level ; 40 import java.util.logging.Logger ; 41 import java.util.zip.ZipEntry ; 42 import java.util.zip.ZipFile ; 43 import javax.lang.model.element.ElementKind; 44 import org.netbeans.api.progress.ProgressHandle; 45 import org.netbeans.modules.classfile.Access; 46 import org.netbeans.modules.classfile.CPClassInfo; 47 import org.netbeans.modules.classfile.CPFieldInfo; 48 import org.netbeans.modules.classfile.CPInterfaceMethodInfo; 49 import org.netbeans.modules.classfile.CPMethodInfo; 50 import org.netbeans.modules.classfile.ClassFile; 51 import org.netbeans.modules.classfile.ClassName; 52 import org.netbeans.modules.classfile.Code; 53 import org.netbeans.modules.classfile.ConstantPool; 54 import org.netbeans.modules.classfile.InvalidClassFormatException; 55 import org.netbeans.modules.classfile.LocalVariableTableEntry; 56 import org.netbeans.modules.classfile.LocalVariableTypeTableEntry; 57 import org.netbeans.modules.classfile.Method; 58 import org.netbeans.modules.classfile.Variable; 59 import org.netbeans.modules.classfile.Parameter; 60 import org.netbeans.modules.java.source.parsing.FileObjects; 61 import org.netbeans.modules.java.source.util.LowMemoryEvent; 62 import org.netbeans.modules.java.source.util.LowMemoryListener; 63 import org.netbeans.modules.java.source.util.LowMemoryNotifier; 64 import org.openide.filesystems.FileObject; 65 import org.openide.filesystems.FileUtil; 66 import org.openide.filesystems.URLMapper; 67 import org.openide.util.Exceptions; 68 import org.openide.util.NbBundle; 69 70 71 72 73 77 public class BinaryAnalyser implements LowMemoryListener { 78 79 static final String OBJECT = Object .class.getName(); 80 81 private final Index index; 82 private final Map <String ,List <String >> refs = new HashMap <String ,List <String >>(); 83 private final Set <String > toDelete = new HashSet <String > (); 84 private final AtomicBoolean lowMemory; 85 private boolean cacheCleared; 86 87 public BinaryAnalyser (Index index) { 88 assert index != null; 89 this.index = index; 90 this.lowMemory = new AtomicBoolean (false); 91 } 92 93 97 public final void analyse (final URL root, final ProgressHandle handle) throws IOException , IllegalArgumentException { 98 assert root != null; 99 ClassIndexManager.getDefault().writeLock(new ClassIndexManager.ExceptionAction<Void > () { 100 public Void run () throws IOException { 101 LowMemoryNotifier.getDefault().addLowMemoryListener (BinaryAnalyser.this); 102 try { 103 String mainP = root.getProtocol(); 104 if ("jar".equals(mainP)) { URL innerURL = FileUtil.getArchiveFile(root); 106 if ("file".equals(innerURL.getProtocol())) { File archive = new File (URI.create(innerURL.toExternalForm())); 109 if (archive.exists() && archive.canRead()) { 110 if (handle != null) { 111 handle.setDisplayName(String.format (NbBundle.getMessage(BinaryAnalyser.class,"MSG_Scannig"),archive.getAbsolutePath())); 112 } 113 if (!isUpToDate(null,archive.lastModified())) { 114 index.clear(); 115 if (handle != null) { handle.setDisplayName (String.format(NbBundle.getMessage(RepositoryUpdater.class,"MSG_Analyzing"),archive.getAbsolutePath())); 117 } 118 final ZipFile zipFile = new ZipFile (archive); 119 try { 120 analyseArchive( zipFile ); 121 } 122 finally { 123 zipFile.close(); 124 } 125 } 126 } 127 } 128 else { 129 FileObject rootFo = URLMapper.findFileObject(root); 130 if (rootFo != null) { 131 if (handle != null) { 132 handle.setDisplayName(String.format (NbBundle.getMessage(BinaryAnalyser.class,"MSG_Scannig"),FileUtil.getFileDisplayName(rootFo))); 133 } 134 if (!isUpToDate(null,rootFo.lastModified().getTime())) { 135 index.clear(); 136 if (handle != null) { handle.setDisplayName (String.format(NbBundle.getMessage(RepositoryUpdater.class,"MSG_Analyzing"),FileUtil.getFileDisplayName(rootFo))); 138 } 139 analyseFileObjects(rootFo); 140 } 141 } 142 } 143 } 144 else if ("file".equals(mainP)) { File rootFile = new File (URI.create(root.toExternalForm())); 147 if (rootFile.isDirectory()) { 148 String path = rootFile.getAbsolutePath (); 149 if (path.charAt(path.length()-1) != File.separatorChar) { 150 path = path + File.separatorChar; 151 } 152 if (handle != null) { handle.setDisplayName (String.format(NbBundle.getMessage(RepositoryUpdater.class,"MSG_Analyzing"),rootFile.getAbsolutePath())); 154 } 155 cacheCleared = false; 156 analyseFolder(rootFile, path); 157 } 158 } 159 else { 160 FileObject rootFo = URLMapper.findFileObject(root); 161 if (rootFo != null) { 162 if (handle != null) { handle.setDisplayName (String.format(NbBundle.getMessage(RepositoryUpdater.class,"MSG_Analyzing"),FileUtil.getFileDisplayName(rootFo))); 164 } 165 index.clear(); 166 analyseFileObjects(rootFo); 167 } 168 } 169 } finally { 170 LowMemoryNotifier.getDefault().removeLowMemoryListener(BinaryAnalyser.this); 171 } 172 store(); 173 return null; 174 }}); 175 } 176 177 183 private void analyseFolder (final File folder, final String rootPath) throws IOException { 184 if (folder.exists() && folder.canRead()) { 185 File [] children = folder.listFiles(); 186 for (File file : children) { 187 if (file.isDirectory()) { 188 analyseFolder(file, rootPath); 189 } 190 else if (this.accepts(file.getName())) { 191 String filePath = file.getAbsolutePath(); 192 long fileMTime = file.lastModified(); 193 int dotIndex = filePath.lastIndexOf('.'); 194 int slashIndex = filePath.lastIndexOf('/'); 195 int endPos; 196 if (dotIndex>slashIndex) { 197 endPos = dotIndex; 198 } 199 else { 200 endPos = filePath.length(); 201 } 202 String relativePath = FileObjects.convertFolder2Package (filePath.substring(rootPath.length(), endPos)); 203 if (this.accepts(file.getName()) && !isUpToDate (relativePath, fileMTime)) { 204 if (!cacheCleared) { 205 this.index.clear(); 206 cacheCleared = true; 207 } 208 InputStream in = new BufferedInputStream (new FileInputStream (file)); 209 try { 210 analyse (in); 211 } catch (InvalidClassFormatException icf) { 212 Logger.getLogger(BinaryAnalyser.class.getName()).info("Invalid class file format: "+file.getAbsolutePath()); } 214 finally { 215 in.close(); 216 } 217 if (this.lowMemory.getAndSet(false)) { 218 this.store(); 219 } 220 } 221 } 222 } 223 } 224 } 225 226 228 private void analyseArchive ( ZipFile zipFile ) throws IOException { 229 for( Enumeration e = zipFile.entries(); e.hasMoreElements(); ) { 230 ZipEntry ze = (ZipEntry )e.nextElement(); 231 if ( !ze.isDirectory() && this.accepts(ze.getName())) { 232 InputStream in = zipFile.getInputStream( ze ); 233 try { 234 analyse(in); 235 } catch (InvalidClassFormatException icf) { 236 Logger.getLogger(BinaryAnalyser.class.getName()).info("Invalid class file format: "+ new File (zipFile.getName()).toURI() + "!/" + ze.getName()); } catch (IOException x) { 238 Exceptions.attachMessage(x, "While scanning: " + ze.getName()); throw x; 240 } 241 finally { 242 in.close(); 243 } 244 if (this.lowMemory.getAndSet(false)) { 245 this.store(); 246 } 247 } 248 } 249 } 250 251 private void analyseFileObjects (FileObject folder) throws IOException { 252 for (FileObject fo : folder.getChildren()) { 253 if (fo.isFolder()) { 254 analyseFileObjects (fo); 255 } 256 else if (this.accepts(fo.getName())) { 257 InputStream in = new BufferedInputStream (fo.getInputStream()); 258 try { 259 analyse (in); 260 } catch (InvalidClassFormatException icf) { 261 Logger.getLogger(BinaryAnalyser.class.getName()).info("Invalid class file format: "+FileUtil.getFileDisplayName(fo)); } 263 finally { 264 in.close(); 265 } 266 if (this.lowMemory.getAndSet(false)) { 267 this.store(); 268 } 269 } 270 } 271 } 272 273 private final void delete (final String className) throws IOException { 275 assert className != null; 276 if (!this.index.isValid(false)) { 277 return; 278 } 279 this.toDelete.add(className); 280 } 281 282 public void lowMemory (final LowMemoryEvent event) { 283 this.lowMemory.set(true); 284 } 285 286 288 private boolean accepts(String name) { 289 int index = name.lastIndexOf('.'); 290 if (index == -1 || (index+1) == name.length()) { 291 return false; 292 } 293 return "CLASS".equalsIgnoreCase(name.substring(index+1)); } 295 296 private void analyse (InputStream inputStream ) throws IOException { 297 final ClassFile classFile = new ClassFile(inputStream); 298 final ClassName className = classFile.getName (); 299 final String classNameStr = nameToString (className); 300 this.delete (classNameStr); 301 final Map <ClassName, Set <ClassIndexImpl.UsageType>> usages = performAnalyse(classFile, classNameStr); 302 ElementKind kind = ElementKind.CLASS; 303 if (classFile.isEnum()) { 304 kind = ElementKind.ENUM; 305 } 306 else if (classFile.isAnnotation()) { 307 kind = ElementKind.ANNOTATION_TYPE; 308 } 309 else if ((classFile.getAccess() & Access.INTERFACE) == Access.INTERFACE) { 310 kind = ElementKind.INTERFACE; 311 } 312 final String classNameType = classNameStr + DocumentUtil.encodeKind(kind); 313 final List <String > references = getClassReferences (classNameType); 314 for (Map.Entry <ClassName,Set <ClassIndexImpl.UsageType>> entry : usages.entrySet()) { 315 ClassName name = entry.getKey(); 316 Set <ClassIndexImpl.UsageType> usage = entry.getValue(); 317 references.add (DocumentUtil.encodeUsage( nameToString(name), usage)); 318 } 319 } 320 321 private void store() throws IOException { 322 try { 323 if (this.refs.size()>0 || this.toDelete.size()>0) { 324 this.index.store(this.refs,this.toDelete); 325 } 326 } finally { 327 refs.clear(); 328 toDelete.clear(); 329 } 330 } 331 332 private final boolean isUpToDate(String resourceName, long resourceMTime) throws IOException { 333 return this.index.isUpToDate(resourceName, resourceMTime); 334 } 335 336 337 @SuppressWarnings ("unchecked") private Map <ClassName,Set <ClassIndexImpl.UsageType>> performAnalyse(final ClassFile classFile, final String className) throws IOException { 340 final Map <ClassName, Set <ClassIndexImpl.UsageType>> usages = new HashMap <ClassName, Set <ClassIndexImpl.UsageType>> (); 341 String signature = classFile.getTypeSignature(); 343 if (signature != null) { 344 try { 345 ClassName[] typeSigNames = ClassFileUtil.getTypesFromClassTypeSignature (signature); 346 for (ClassName typeSigName : typeSigNames) { 347 addUsage(usages, typeSigName, ClassIndexImpl.UsageType.TYPE_REFERENCE); 348 } 349 } catch (final RuntimeException re) { 350 final StringBuilder message = new StringBuilder ("BinaryAnalyser: Cannot read type: " + signature+" cause: " + re.getLocalizedMessage() + '\n'); final StackTraceElement [] elements = re.getStackTrace(); 352 for (StackTraceElement e : elements) { 353 message.append(e.toString()); 354 message.append('\n'); } 356 Logger.getLogger("global").log (Level.INFO, message.toString()); } 358 } 359 360 ClassName scName = classFile.getSuperClass(); 362 if ( scName != null ) { 363 addUsage (usages, scName, ClassIndexImpl.UsageType.SUPER_CLASS); 364 } 365 366 Collection <ClassName> interfaces = classFile.getInterfaces(); 368 for( ClassName ifaceName : interfaces ) { 369 addUsage (usages, ifaceName, ClassIndexImpl.UsageType.SUPER_INTERFACE); 370 } 371 372 final ConstantPool constantPool = classFile.getConstantPool(); 374 Collection <? extends CPFieldInfo> fields = constantPool.getAllConstants(CPFieldInfo.class); 375 for (CPFieldInfo field : fields) { 376 ClassName name = ClassFileUtil.getType(constantPool.getClass(field.getClassID())); 377 if (name != null) { 378 addUsage (usages, name, ClassIndexImpl.UsageType.FIELD_REFERENCE); 379 } 380 } 381 382 Collection <? extends CPMethodInfo> methodCalls = constantPool.getAllConstants(CPMethodInfo.class); 384 for (CPMethodInfo method : methodCalls) { 385 ClassName name = ClassFileUtil.getType(constantPool.getClass(method.getClassID())); 386 if (name != null) { 387 addUsage (usages, name, ClassIndexImpl.UsageType.METHOD_REFERENCE); 388 } 389 } 390 methodCalls = constantPool.getAllConstants(CPInterfaceMethodInfo.class); 391 for (CPMethodInfo method : methodCalls) { 392 ClassName name = ClassFileUtil.getType(constantPool.getClass(method.getClassID())); 393 if (name != null) { 394 addUsage (usages, name, ClassIndexImpl.UsageType.METHOD_REFERENCE); 395 } 396 } 397 398 Collection <Method> methods = classFile.getMethods(); 400 for (Method method : methods) { 401 String jvmTypeId = method.getReturnType(); 402 ClassName type = ClassFileUtil.getType (jvmTypeId); 403 if (type != null) { 404 addUsage(usages, type, ClassIndexImpl.UsageType.TYPE_REFERENCE); 405 } 406 List <Parameter> params = method.getParameters(); 407 for (Parameter param : params) { 408 jvmTypeId = param.getDescriptor(); 409 type = ClassFileUtil.getType (jvmTypeId); 410 if (type != null) { 411 addUsage(usages, type, ClassIndexImpl.UsageType.TYPE_REFERENCE); 412 } 413 } 414 CPClassInfo[] classInfos = method.getExceptionClasses(); 415 for (CPClassInfo classInfo : classInfos) { 416 type = classInfo.getClassName(); 417 if (type != null) { 418 addUsage(usages, type, ClassIndexImpl.UsageType.TYPE_REFERENCE); 419 } 420 } 421 jvmTypeId = method.getTypeSignature(); 422 if (jvmTypeId != null) { 423 try { 424 ClassName[] typeSigNames = ClassFileUtil.getTypesFromMethodTypeSignature (jvmTypeId); 425 for (ClassName typeSigName : typeSigNames) { 426 addUsage(usages, typeSigName, ClassIndexImpl.UsageType.TYPE_REFERENCE); 427 } 428 } catch (IllegalStateException is) { 429 Logger.getLogger("global").warning("Invalid method signature: "+className+"::"+method.getName()+" signature is:" + jvmTypeId); } 431 } 432 Code code = method.getCode(); 433 if (code != null) { 434 LocalVariableTableEntry[] vars = code.getLocalVariableTable(); 435 for (LocalVariableTableEntry var : vars) { 436 type = ClassFileUtil.getType (var.getDescription()); 437 if (type != null) { 438 addUsage(usages, type, ClassIndexImpl.UsageType.TYPE_REFERENCE); 439 } 440 } 441 LocalVariableTypeTableEntry[] varTypes = method.getCode().getLocalVariableTypeTable(); 442 for (LocalVariableTypeTableEntry varType : varTypes) { 443 try { 444 ClassName[] typeSigNames = ClassFileUtil.getTypesFromFiledTypeSignature (varType.getSignature()); 445 for (ClassName typeSigName : typeSigNames) { 446 addUsage(usages, typeSigName, ClassIndexImpl.UsageType.TYPE_REFERENCE); 447 } 448 } catch (IllegalStateException is) { 449 Logger.getLogger("global").warning("Invalid local variable signature: "+className+"::"+method.getName()); } 451 } 452 } 453 } 454 Collection <Variable> vars = classFile.getVariables(); 456 for (Variable var : vars) { 457 String jvmTypeId = var.getDescriptor(); 458 ClassName type = ClassFileUtil.getType (jvmTypeId); 459 if (type != null) { 460 addUsage (usages, type, ClassIndexImpl.UsageType.TYPE_REFERENCE); 461 } 462 jvmTypeId = var.getTypeSignature(); 463 if (jvmTypeId != null) { 464 try { 465 ClassName[] typeSigNames = ClassFileUtil.getTypesFromFiledTypeSignature (jvmTypeId); 466 for (ClassName typeSigName : typeSigNames) { 467 addUsage(usages, typeSigName, ClassIndexImpl.UsageType.TYPE_REFERENCE); 468 } 469 } catch (IllegalStateException is) { 470 Logger.getLogger("global").warning("Invalid field signature: "+className+"::"+var.getName()+" signature is: "+jvmTypeId); } 472 } 473 } 474 475 Collection <? extends CPClassInfo> cis = constantPool.getAllConstants(CPClassInfo.class); 477 for (CPClassInfo ci : cis) { 478 ClassName ciName = ClassFileUtil.getType(ci); 479 if (ciName != null && !usages.keySet().contains (ciName)) { 480 addUsage(usages, ciName, ClassIndexImpl.UsageType.TYPE_REFERENCE); 481 } 482 } 483 484 return usages; 485 } 486 487 private List <String > getClassReferences (final String className) { 488 assert className != null; 489 List <String > cr = this.refs.get (className); 490 if (cr == null) { 491 cr = new ArrayList <String > (); 492 this.refs.put (className, cr); 493 } 494 return cr; 495 } 496 497 498 500 private static String nameToString( ClassName name ) { 501 return name.getInternalName().replace('/', '.'); } 503 504 private static void addUsage (final Map <ClassName, Set <ClassIndexImpl.UsageType>> usages, final ClassName name, final ClassIndexImpl.UsageType usage) { 505 if (OBJECT.equals(name.getExternalName())) { 506 return; 507 } 508 Set <ClassIndexImpl.UsageType> uset = usages.get(name); 509 if (uset == null) { 510 uset = EnumSet.noneOf(ClassIndexImpl.UsageType.class); 511 usages.put(name, uset); 512 } 513 uset.add(usage); 514 } 515 } 516 | Popular Tags |