001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: * The Original Software is NetBeans. The Initial Developer of the Original
026: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
027: * Microsystems, Inc. All Rights Reserved.
028: *
029: * If you wish your version of this file to be governed by only the CDDL
030: * or only the GPL Version 2, indicate your decision by adding
031: * "[Contributor] elects to include this software in this distribution
032: * under the [CDDL or GPL Version 2] license." If you do not indicate a
033: * single choice of license, a recipient has the option to distribute
034: * your version of this file under either the CDDL, the GPL Version 2 or
035: * to extend the choice of license to its licensees as provided above.
036: * However, if you add GPL Version 2 code and therefore, elected the GPL
037: * Version 2 license, then the option applies only if the new code is
038: * made subject to such option by the copyright holder.
039: */
040:
041: package org.netbeans.lib.profiler.classfile;
042:
043: import org.netbeans.lib.profiler.TargetAppRunner;
044: import org.netbeans.lib.profiler.global.CommonConstants;
045: import org.netbeans.lib.profiler.instrumentation.BadLocationException;
046: import org.netbeans.lib.profiler.utils.FileOrZipEntry;
047: import org.netbeans.lib.profiler.utils.MiscUtils;
048: import java.io.File;
049: import java.io.FilenameFilter;
050: import java.io.IOException;
051: import java.util.*;
052:
053: /**
054: * A collection of several static methods for general class file reading functionality. Allows to set
055: * a class path, read a class from class path, generate a class that does not have a .class file (such
056: * as an array class), etc. It also keeps track of classes ever loaded by it, and allows one to iterate
057: * over these classes.
058: *
059: * @author Tomas Hurka
060: * @author Misha Dmitirev
061: */
062: public abstract class ClassRepository implements CommonConstants {
063: //~ Inner Classes ------------------------------------------------------------------------------------------------------------
064:
065: // ------------------------ Method-class-source related stuff --------------------------------
066: public static class CodeRegionBCI {
067: //~ Instance fields ------------------------------------------------------------------------------------------------------
068:
069: public String className;
070: public String methodName;
071: public String methodSignature;
072: public int bci0;
073: public int bci1;
074:
075: //~ Constructors ---------------------------------------------------------------------------------------------------------
076:
077: public CodeRegionBCI(String className, String methodName,
078: String methodSignature, int bci0, int bci1) {
079: this .className = className;
080: this .methodName = methodName;
081: this .methodSignature = methodSignature;
082: this .bci0 = bci0;
083: this .bci1 = bci1;
084: }
085:
086: //~ Methods --------------------------------------------------------------------------------------------------------------
087:
088: public String toString() {
089: return "CodeRegionBCI [" // NOI18N
090: + "className: " + className // NOI18N
091: + ", methodName: " + methodName // NOI18N
092: + ", methodSignature: " + methodSignature // NOI18N
093: + ", bci0: " + bci0 // NOI18N
094: + ", bci1: " + bci1 // NOI18N
095: + "]"; // NOI18N
096: }
097: }
098:
099: //~ Static fields/initializers -----------------------------------------------------------------------------------------------
100:
101: // The below class file location signals to ClassFileCache that the class file should have been already supplied by the VM.
102: static final String LOCATION_VMSUPPLIED = "<VM_SUPPLIED>"; // NOI18N
103: private static ClassPath classPath;
104: private static Hashtable classes;
105: private static HashSet notFoundClasses;
106: private static Map definingClassLoaderMap;
107:
108: static {
109: clearCache();
110: }
111:
112: //~ Methods ------------------------------------------------------------------------------------------------------------------
113:
114: public static ArrayList getAllClassVersions(String className) {
115: className = className.replace('.', '/').intern(); // NOI18N
116:
117: Object entry = classes.get(className);
118:
119: if (entry != null) { // A single class or placeholder, or a group of them for this name, exists
120:
121: ArrayList ret = new ArrayList();
122:
123: if (entry instanceof BaseClassInfo) {
124: ret.add(entry);
125: } else {
126: ret = ((SameNameClassGroup) entry).getAll();
127: }
128:
129: return ret;
130: } else {
131: return null;
132: }
133: }
134:
135: public static Enumeration getClassEnumerationWithAllVersions() {
136: class ClassesEnumeration implements Enumeration {
137: private Enumeration baseEnum;
138: private Object nextElement;
139: private ArrayList classes;
140: private int idx;
141:
142: ClassesEnumeration(Enumeration baseEnum) {
143: this .baseEnum = baseEnum;
144: }
145:
146: public boolean hasMoreElements() {
147: if (nextElement == null) {
148: return baseEnum.hasMoreElements();
149: } else {
150: return true;
151: }
152: }
153:
154: public Object nextElement() {
155: if (nextElement != null) {
156: Object ret = nextElement;
157: idx++;
158:
159: if (idx == classes.size()) {
160: nextElement = null;
161: } else {
162: nextElement = classes.get(idx);
163: }
164:
165: return ret;
166: } else {
167: Object next = baseEnum.nextElement();
168:
169: if (next instanceof SameNameClassGroup) {
170: SameNameClassGroup g = (SameNameClassGroup) next;
171: classes = g.getAll();
172: next = classes.get(0);
173:
174: if (classes.size() > 1) {
175: idx = 1;
176: nextElement = classes.get(1);
177: } else {
178: classes = null;
179: }
180: }
181:
182: return next;
183: }
184: }
185: }
186:
187: return new ClassesEnumeration(classes.elements());
188: }
189:
190: public static ClassPath getClassPath() {
191: return classPath;
192: }
193:
194: /**
195: * Returns names of all classes that can be located on the given classpath.
196: * Since this method performs directory scanning, it is recommended to call it once and cache the results.
197: */
198: public static ArrayList getClassesOnClasspath(
199: ArrayList classPathElementList) { // TODO CHECK: unused method
200:
201: ArrayList list = new ArrayList();
202: list.addAll(classPathElementList);
203:
204: ArrayList res = new ArrayList();
205:
206: for (Iterator e = list.iterator(); e.hasNext();) {
207: String dirOrJar = (String) e.next();
208:
209: if (!(dirOrJar.endsWith(".jar") || dirOrJar
210: .endsWith(".zip"))) // NOI18N
211: {
212: MiscUtils.getAllClassesInDir(dirOrJar, "", true, res); // NOI18N
213: } else {
214: MiscUtils.getAllClassesInJar(dirOrJar, true, res);
215: }
216: }
217:
218: return res;
219: }
220:
221: public static CodeRegionBCI getMethodForSourceRegion(
222: ClassInfo clazz, int startLine, int endLine)
223: throws ClassNotFoundException, IOException,
224: BadLocationException {
225: if (startLine > endLine) {
226: return null; // Just in case...
227: }
228:
229: int[] idxAndBCI0 = clazz
230: .methodIdxAndBestBCIForLineNo(startLine);
231: int methodIdx = idxAndBCI0[0];
232:
233: if (methodIdx >= 0) {
234: String methodName = clazz.getMethodNames()[methodIdx];
235:
236: if ((methodName == "<init>") || (methodName == "<clinit>")) { // NOI18N
237: // See the comment in ClassInfo.methodIdxAndBestBCIForLineNo() regarding initializers scattered about the class text.
238: // Check if a method in a nested class matches the same spot in the source code.
239:
240: CodeRegionBCI res = getMethodForSourceRegionInNestedClasses(
241: clazz, startLine, endLine);
242:
243: if (res != null) {
244: return res;
245: }
246: }
247:
248: int[] minAndMaxLines = clazz
249: .getMinAndMaxLinesForMethod(methodIdx);
250:
251: if (endLine <= minAndMaxLines[1]) {
252: endLine++; // That's because we will need to inject code after the *last bytecode corresponding to endLine*
253: }
254:
255: int[] idxAndBCI1 = clazz
256: .methodIdxAndBestBCIForLineNo(endLine);
257:
258: // Now let's check if start and end lines are within the same method.
259: // If the end line is definitely within some other method, it's an error and we return.
260: // However, it may just cover one or more of '}'s in the end of this method, and these lines
261: // are just not within this method's line number table. If so, assume that the end line is
262: // the last line of the this method.
263: if (methodIdx != idxAndBCI1[0]) {
264: if (idxAndBCI1[0] != -1) { // Definitely this line belongs to some other method
265:
266: return null;
267: } else { // Couldn't find the line - assume it's the last line of the same method
268: idxAndBCI1[0] = methodIdx;
269:
270: // Need to find the bci of the last instruction in this method. It can only be a "return" or 'goto' ('goto_w').
271: // In either case, we should put the call before this instruction, since it would make no sense after it.
272: byte[] codeBytes = clazz
273: .getMethodBytecode(methodIdx);
274: idxAndBCI1[1] = ClassInfo.findPreviousBCI(
275: codeBytes, codeBytes.length);
276: }
277: }
278:
279: // Now here is another issue. It appears that at least "while() { }" is effectively compiled as "do..while",
280: // i.e. the condition check is located after the block, not before. Which leads to the problem: if the
281: // user points at the line with "while" as a first region line, the "exact" bytecode offset for this particular
282: // line may be greater than the offset for the next line after "while"! This leads to incorrect measurements
283: // results at best and to the JVM crash during bytecode oop map generation at worst. To handle this, we
284: // currently use heuristics which just looks up the line with the smallest bci in between startLine and endLine.
285: int bestBCI0 = idxAndBCI0[1];
286:
287: for (int lineNo = startLine + 1; lineNo < (endLine - 1); lineNo++) {
288: int otherBestBCI0 = clazz.bciForMethodAndLineNo(
289: methodIdx, lineNo);
290:
291: if (otherBestBCI0 < bestBCI0) {
292: bestBCI0 = otherBestBCI0;
293: }
294: }
295:
296: // Finally, check if the last bci is of the "goto" opcode. If so, we should actually return the bci of the
297: // previous opcode, since injecting code probe right after the "goto", as it will be done if no measures are
298: // taken, makes no sense. This code will not work as intended, and most likely will be just unreachable.
299: // THIS IS INCORRECT. We inject code *before*, not after, the given bytecode. So it can be goto as well.
300: //idxAndBCI1[1] = clazz.checkIfAtGoTo(methodIdx, idxAndBCI1[1]);
301: return new CodeRegionBCI(clazz.getName(), clazz
302: .getMethodNames()[methodIdx], clazz
303: .getMethodSignatures()[methodIdx], bestBCI0,
304: idxAndBCI1[1]);
305: } else if (methodIdx == -2) { // No line number tables in this class
306: throw new BadLocationException(
307: "Class does not have source line number tables.\nRecompile it with appropriate options."); // NOI18N
308: }
309:
310: // Suitable method not found. Look at nested classes, if there are any.
311: return getMethodForSourceRegionInNestedClasses(clazz,
312: startLine, endLine);
313: }
314:
315: /* public static int[] getMinAndMaxLinesForMethod(String className, String methodName, String methodSignature) // TODO CHECK: unused method
316: throws IOException, ClassFormatError {
317: ClassInfo clazz = lookupClassOnAllPaths(className);
318: String methodNames[] = clazz.getMethodNames();
319: String methodSignatures[] = clazz.getMethodSignatures();
320: methodName = methodName.intern();
321: methodSignature = methodSignature.intern();
322: int idx = clazz.getMethodIndex(methodName, methodSignature);
323: if (idx != -1)
324: return clazz.getMinAndMaxLinesForMethod(idx);
325: else
326: return null;
327: }
328: */
329: public static CodeRegionBCI getMethodMinAndMaxBCI(ClassInfo clazz,
330: String methodName, String methodSignature) {
331: methodName = methodName.intern();
332: methodSignature = methodSignature.intern();
333:
334: int idx = clazz.getMethodIndex(methodName, methodSignature);
335:
336: if (idx == -1) {
337: return null;
338: }
339:
340: // Note that className and clazz.getName() may be different, e.g. if className is specified as "x.y.Outer.Inner",
341: // when the correct format understood by the rest of JFluid is "x.y.Outer$Inner".
342: return new CodeRegionBCI(clazz.getName(), methodName,
343: methodSignature, 0,
344: clazz.getMethodBytecode(idx).length - 1);
345: }
346:
347: public static void addPlaceholder(PlaceholderClassInfo pci) {
348: BaseClassInfo singleExistingClazzOrPCI = null;
349: SameNameClassGroup classGroup = null;
350: String className = pci.getName();
351:
352: Object entry = classes.get(className);
353:
354: if (entry != null) { // A single class or placeholder, or a group of them for this name, exists
355:
356: if (entry instanceof BaseClassInfo) {
357: singleExistingClazzOrPCI = (BaseClassInfo) entry;
358: classGroup = new SameNameClassGroup();
359: classGroup.add(singleExistingClazzOrPCI);
360: classGroup.add(pci);
361: classes.put(className, classGroup);
362: } else { // entry is a SameNameClassGroup
363: classGroup = (SameNameClassGroup) entry;
364: classGroup.add(pci);
365: }
366: } else { // An entry with this name doesn't exist
367: classes.put(className, pci);
368: }
369: }
370:
371: /** Adds a VM-supplied class file to the class file cache, but not to this repository's hashtable yet. */
372: public static void addVMSuppliedClassFile(String className,
373: int classLoaderId, byte[] buf) {
374: className = className.replace('.', '/').intern(); // NOI18N
375: ClassFileCache.getDefault().addVMSuppliedClassFile(className,
376: classLoaderId, buf);
377: }
378:
379: /** Utility method - read .class file and return class name stored in it. */
380: public static ClassInfo classInfoForFile(File f)
381: throws ClassNotFoundException, IOException,
382: ClassFormatError { // TODO CHECK: unused method
383:
384: byte[] buf = MiscUtils
385: .readFileIntoBuffer(new FileOrZipEntry(f));
386: ClassInfo clazz = new ClassInfo(buf) {
387: protected byte[] getClassFileBytes() {
388: return null;
389: }
390: };
391:
392: String fileName = f.getCanonicalPath();
393: String className = clazz.getName() + ".class"; // NOI18N
394: String expectedClassName = fileName.substring(
395: fileName.length() - className.length()).replace('\\',
396: '/'); // NOI18N
397:
398: if (!className.equals(expectedClassName)) {
399: throw new ClassNotFoundException(
400: "Mismatch between name in .class file and location for "
401: + fileName // NOI18N
402: + "\nYour class path setting may be incorrect." // NOI18N
403: ); // NOI18N
404: }
405:
406: return clazz;
407: }
408:
409: /** Should be called after profiling finishes to cleanup any static data, close opened files, etc. */
410: public static void cleanup() {
411: clearCache();
412:
413: if (classPath != null) {
414: classPath.close();
415: classPath = null;
416: }
417: }
418:
419: /** Will reset any cached data, will not reset data pertinent to session in progress */
420: public static void clearCache() {
421: classes = new Hashtable();
422: ClassFileCache.resetDefaultCache();
423: notFoundClasses = new HashSet();
424: definingClassLoaderMap = new HashMap();
425: }
426:
427: /**
428: * This is the ClassRepository internal class path initialization method. The class path is initialized to the
429: * combination of the running VM's boot, extension and main class paths (if they are available; otherwise only
430: * the main path is obtained from the tool's settings), plus the secondary class path from the settings.
431: *
432: * @param workingDir working directory, needed in case the given paths are in the local form
433: * @param classPaths the 3 elements should be the user, extension, and boot class paths, respectively
434: */
435: public static void initClassPaths(String workingDir,
436: String[] classPaths) {
437: ArrayList userClassPathElementList = MiscUtils
438: .getPathComponents(classPaths[0], true, workingDir);
439: ArrayList bootClassPathElementList = MiscUtils
440: .getPathComponents(classPaths[2], true, workingDir);
441:
442: String extPath = classPaths[1];
443: ArrayList extClassPathElementList = new ArrayList();
444:
445: // Extension class path needs special handling, since it consists of directories, which contain .jars
446: // So we need to find all these .jars in all these dirs and add them to extClassPathElementList
447: ArrayList dirs = MiscUtils.getPathComponents(extPath, true,
448: workingDir);
449:
450: for (Iterator e = dirs.iterator(); e.hasNext();) {
451: File extDir = new File((String) e.next());
452: String[] extensions = extDir.list(new FilenameFilter() {
453: public boolean accept(File dir, String name) {
454: name = name.toLowerCase();
455:
456: return name.endsWith(".zip")
457: || name.endsWith(".jar"); // NOI18N
458: }
459: });
460:
461: if (extensions == null) {
462: continue;
463: }
464:
465: for (int i = 0; i < extensions.length; i++) {
466: extClassPathElementList.add(extPath
467: + File.separatorChar + extensions[i]);
468: }
469: }
470:
471: ArrayList list = new ArrayList();
472: list.addAll(bootClassPathElementList);
473: list.addAll(extClassPathElementList);
474: list.addAll(userClassPathElementList);
475:
476: StringBuffer buf = new StringBuffer();
477:
478: for (Iterator e = list.iterator(); e.hasNext();) {
479: buf.append((String) e.next());
480:
481: if (e.hasNext()) {
482: buf.append(File.pathSeparatorChar);
483: }
484: }
485:
486: classPath = new ClassPath(buf.toString(), true);
487:
488: notFoundClasses = new HashSet();
489: }
490:
491: /**
492: * Lookup a class in the class repository. If it's not there, look it up on the classpath (for classes with 0 loader)
493: * or in the cache of VM-supplied classes. If the class is not found anywhere, reports this and returns null.
494: * Guaranteed to return a real class or null, but not a placeholder. Should not be called for special (array)
495: * classes - there is lookupSpecialClass() for that.
496: */
497: public static DynamicClassInfo lookupClass(String className,
498: int classLoaderId) throws IOException, ClassFormatError {
499: return lookupClass(className, classLoaderId, true);
500: }
501:
502: /**
503: * Lookup a class in the class repository. If it's not there, don't bother checking the classpath etc. - just
504: * return an instance of PlaceholderClassInfo. The rationale is that we may not ever need the real class for
505: * className; and when we need it, lookupClass() above will deliver it.
506: */
507: public static BaseClassInfo lookupClassOrCreatePlaceholder(
508: String className, int classLoaderId) {
509: BaseClassInfo singleExistingClazzOrPCI = null;
510: BaseClassInfo clazzOrPCI = null;
511: SameNameClassGroup classGroup = null;
512: className = className.replace('.', '/').intern(); // NOI18N
513:
514: Object entry = classes.get(className);
515:
516: if (entry != null) { // A single class or placeholder, or a group of them for this name, exists
517:
518: if (entry instanceof BaseClassInfo) {
519: singleExistingClazzOrPCI = (BaseClassInfo) entry;
520: clazzOrPCI = SameNameClassGroup.checkForCompatibility(
521: singleExistingClazzOrPCI, classLoaderId);
522: } else { // entry is a SameNameClassGroup
523: classGroup = (SameNameClassGroup) entry;
524: clazzOrPCI = classGroup
525: .findCompatibleClass(classLoaderId);
526: }
527:
528: if (clazzOrPCI != null) { // Found compatible class or placeholder
529:
530: return clazzOrPCI;
531: } else { // Non-null entry for this class name, but no compatible class or placeholder
532: clazzOrPCI = new PlaceholderClassInfo(className,
533: classLoaderId);
534:
535: if (classGroup != null) {
536: classGroup.add(clazzOrPCI);
537: } else { // There is already a single incompatible class or placeholder in classes - create a new class group
538: classGroup = new SameNameClassGroup();
539: classGroup.add(singleExistingClazzOrPCI);
540: classGroup.add(clazzOrPCI);
541: classes.put(className, classGroup);
542: }
543:
544: return clazzOrPCI;
545: }
546: } else { // An entry with this name doesn't exist
547: clazzOrPCI = new PlaceholderClassInfo(className,
548: classLoaderId);
549: classes.put(className, clazzOrPCI);
550:
551: return clazzOrPCI;
552: }
553: }
554:
555: /**
556: * Lookup a class in the class repository, only among those currently loaded by the VM.
557: * If there is no loaded class and allowExistingPlaceholder is true, also check for an existing placeholders.
558: * Returns either a loaded class, or if allowed an existing placeholder, or null, but not a new placeholder.
559: */
560: public static BaseClassInfo lookupLoadedClass(String className,
561: int classLoaderId, boolean allowExistingPlaceholder) {
562: BaseClassInfo singleExistingClazzOrPCI = null;
563: BaseClassInfo clazzOrPCI = null;
564: className = className.replace('.', '/').intern(); // NOI18N
565:
566: Object entry = classes.get(className);
567:
568: if (entry != null) { // A single class or placeholder, or a group of them for this name, exists
569:
570: if (entry instanceof BaseClassInfo) {
571: singleExistingClazzOrPCI = (BaseClassInfo) entry;
572: clazzOrPCI = SameNameClassGroup.checkForCompatibility(
573: singleExistingClazzOrPCI, classLoaderId);
574: } else { // entry is a SameNameClassGroup
575:
576: SameNameClassGroup classGroup = (SameNameClassGroup) entry;
577: clazzOrPCI = classGroup
578: .findCompatibleClass(classLoaderId);
579: }
580:
581: if (clazzOrPCI != null) { // Found compatible class or placeholder
582:
583: if (!(clazzOrPCI instanceof PlaceholderClassInfo)) {
584: return clazzOrPCI;
585: } else if (allowExistingPlaceholder) {
586: return clazzOrPCI;
587: }
588: }
589: }
590:
591: return null;
592: }
593:
594: /**
595: * Used only for special classes, such as array classes, that don't have a .class file on the class path. If a class
596: * with the given name does not exist, a BaseClassInfo is created for it immediately.
597: */
598: public static BaseClassInfo lookupSpecialClass(String className) {
599: if (className.indexOf('.') != -1) { // NOI18N
600: className = className.replace('.', '/').intern(); // NOI18N
601: }
602:
603: BaseClassInfo clazz = (BaseClassInfo) classes.get(className);
604:
605: if (clazz == null) {
606: clazz = new BaseClassInfo(className, 0); // For now, we don't distinguish between Object array classes for different loaders (if such a thing exists)
607: classes.put(className, clazz);
608: }
609:
610: return clazz;
611: }
612:
613: static int getDefiningClassLoaderId(String className,
614: int classLoaderId) {
615: String classId = className + "#" + classLoaderId; // NOI18N
616: Integer loaderInt = (Integer) definingClassLoaderMap
617: .get(classId);
618:
619: if (loaderInt != null) {
620: return loaderInt.intValue();
621: }
622:
623: int loader = -1;
624:
625: try {
626: loader = TargetAppRunner.getDefault().getProfilerClient()
627: .getDefiningClassLoaderId(className, classLoaderId);
628: } catch (Exception ex) {
629: // Don't bother about reporting an exception - somebody will do that later
630: }
631:
632: definingClassLoaderMap.put(classId, new Integer(loader));
633:
634: return loader;
635: }
636:
637: private static CodeRegionBCI getMethodForSourceRegionInNestedClasses(
638: ClassInfo clazz, int startLine, int endLine)
639: throws ClassNotFoundException, IOException,
640: ClassFormatError {
641: String className = clazz.getName();
642: String[] nestedClassNames = clazz.getNestedClassNames();
643: int classNameLen = className.length();
644:
645: if (nestedClassNames != null) {
646: for (int i = 0; i < nestedClassNames.length; i++) {
647: if (!(nestedClassNames[i].startsWith(className) && (nestedClassNames[i]
648: .length() > classNameLen))) {
649: continue;
650: }
651:
652: try {
653: ClassInfo nestedClass = lookupClass(
654: nestedClassNames[i], clazz.getLoaderId());
655:
656: if (nestedClass != null) {
657: CodeRegionBCI res = getMethodForSourceRegion(
658: nestedClass, startLine, endLine);
659:
660: if (res != null) {
661: return res;
662: }
663: }
664: } catch (BadLocationException ex) {
665: // Clearly if we got into this method, there was a line number table in the upper level class. So the BadLocationException
666: // that can only be thrown if no line number table is found in this particular nested class is a bogus and misleading.
667: return null;
668: }
669: }
670: }
671:
672: return null;
673: }
674:
675: private static DynamicClassInfo checkForVMSuppliedClass(
676: String className, int classLoaderId) throws IOException,
677: ClassFormatError {
678: int realLoaderId = ClassFileCache.getDefault()
679: .hasVMSuppliedClassFile(className, classLoaderId);
680:
681: if (realLoaderId != -1) {
682: return new DynamicClassInfo(className, classLoaderId,
683: LOCATION_VMSUPPLIED + realLoaderId);
684: } else {
685: return null;
686: }
687: }
688:
689: private static DynamicClassInfo lookupClass(String className,
690: int classLoaderId, boolean reportIfNotFound)
691: throws IOException, ClassFormatError {
692: BaseClassInfo singleExistingClazzOrPCI = null;
693: BaseClassInfo clazzOrPCI = null;
694: SameNameClassGroup classGroup = null;
695: className = className.replace('.', '/').intern(); // NOI18N
696:
697: Object entry = classes.get(className);
698:
699: if (entry != null) { // A single class or placeholder, or a group of them for this name, exists
700:
701: if (entry instanceof BaseClassInfo) {
702: singleExistingClazzOrPCI = (BaseClassInfo) entry;
703: clazzOrPCI = SameNameClassGroup.checkForCompatibility(
704: singleExistingClazzOrPCI, classLoaderId);
705: } else { // entry is a SameNameClassGroup
706: classGroup = (SameNameClassGroup) entry;
707: clazzOrPCI = classGroup
708: .findCompatibleClass(classLoaderId);
709: }
710:
711: if (clazzOrPCI != null) { // Found compatible class or placeholder
712:
713: if (!(clazzOrPCI instanceof PlaceholderClassInfo)) {
714: return (DynamicClassInfo) clazzOrPCI;
715: } else { // Found a compatible placeholder
716:
717: PlaceholderClassInfo pci = (PlaceholderClassInfo) clazzOrPCI;
718: DynamicClassInfo clazz = tryLoadRealClass(
719: className, classLoaderId, reportIfNotFound);
720:
721: if (clazz != null) { // Found a real class for this placeholder
722: pci.transferDataIntoRealClass(clazz);
723:
724: if (classGroup != null) {
725: classGroup.replace(pci, clazz);
726: } else {
727: classes.put(className, clazz);
728: }
729:
730: return clazz;
731: } else {
732: return null; // Didn't find a real class for this placeholder
733: }
734: }
735: } else { // Non-null entry for this class name, but no compatible class or placeholder
736:
737: DynamicClassInfo clazz = tryLoadRealClass(className,
738: classLoaderId, reportIfNotFound);
739:
740: if (clazz != null) { // Managed to load a right class
741:
742: if (classGroup != null) {
743: classGroup.add(clazz);
744: } else { // There is already a single incompatible class or placeholder in classes - create a new class group
745: classGroup = new SameNameClassGroup();
746: classGroup.add(singleExistingClazzOrPCI);
747: classGroup.add(clazz);
748: classes.put(className, classGroup);
749: }
750:
751: return clazz;
752: } else {
753: return null; // Could not load a right class
754: }
755: }
756: } else { // An entry with this name doesn't exist
757:
758: DynamicClassInfo clazz = tryLoadRealClass(className,
759: classLoaderId, reportIfNotFound);
760:
761: if (clazz != null) {
762: classes.put(className, clazz);
763:
764: return clazz;
765: } else {
766: return null;
767: }
768: }
769: }
770:
771: private static DynamicClassInfo tryLoadRealClass(String className,
772: int classLoaderId, boolean reportIfNotFound)
773: throws IOException, ClassFormatError {
774: DynamicClassInfo clazz = null;
775: int loader = classLoaderId;
776:
777: do {
778: // In case of remote profiling, even system classes, that we otherwise can look up on disk locally, are
779: // supplied by the VM. That's why we always call checkForVMSuppliedClass first.
780: clazz = checkForVMSuppliedClass(className, loader);
781:
782: if (clazz == null) {
783: if (((loader == 0) || (loader == -1))
784: && (classPath != null)) { // sanity check; to prevent NPE in case the classPath hasn't been initialized (shouldn't happen anyway)
785: clazz = classPath.getClassInfoForClass(className,
786: loader);
787: }
788: }
789:
790: if ((clazz != null) || (loader == 0)) {
791: break;
792: }
793:
794: // Try parent loader - in some cases a class can be initially requested with the loader of its subclass
795: loader = ClassLoaderTable.getParentLoader(loader);
796: } while (loader >= 0);
797:
798: if (clazz == null) {
799: // In some cases, the class loader graph for the app may be a non-tree structure, i.e. one class loader may delegate
800: // not just to its parent loader, but to some other loader(s) as well. In that case, our last resort is to ask the
801: // initiating loader itself for this class, and then get its defining loader.
802: loader = getDefiningClassLoaderId(className, classLoaderId);
803:
804: if (loader != -1) {
805: clazz = checkForVMSuppliedClass(className, loader); // See above about remote profiling
806:
807: if (clazz == null) {
808: if (loader == 0) {
809: clazz = classPath.getClassInfoForClass(
810: className, loader);
811: }
812: }
813: }
814: }
815:
816: if ((clazz == null) && reportIfNotFound) {
817: if (!notFoundClasses.contains(className)) {
818: MiscUtils.printWarningMessage("class " + className
819: + ", ldr = " + classLoaderId
820: + " not found anywhere"); // NOI18N
821: notFoundClasses.add(className);
822: }
823: }
824:
825: return clazz;
826: }
827:
828: //----------------------------------- Debugging -----------------------------------
829:
830: /*
831: private static void dumpLineTable(Method method) {
832: LineNumberTable lnt = method.getLineNumberTable();
833: if (lnt == null) return;
834: LineNumber[] lns = lnt.getLineNumberTable();
835: System.out.println("Line number table for " + method.getName() + "." + method.getSignature());
836: for (int i = 0; i < lns.length; i++) {
837: System.out.println(lns[i].getLineNumber() + " " + lns[i].getStartPC());
838: }
839: }
840: */
841: }
|