jjg@283: /* ohair@798: * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. jjg@283: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. jjg@283: * jjg@283: * This code is free software; you can redistribute it and/or modify it jjg@283: * under the terms of the GNU General Public License version 2 only, as ohair@554: * published by the Free Software Foundation. Oracle designates this jjg@283: * particular file as subject to the "Classpath" exception as provided ohair@554: * by Oracle in the LICENSE file that accompanied this code. jjg@283: * jjg@283: * This code is distributed in the hope that it will be useful, but WITHOUT jjg@283: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or jjg@283: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License jjg@283: * version 2 for more details (a copy is included in the LICENSE file that jjg@283: * accompanied this code). jjg@283: * jjg@283: * You should have received a copy of the GNU General Public License version jjg@283: * 2 along with this work; if not, write to the Free Software Foundation, jjg@283: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. jjg@283: * ohair@554: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA ohair@554: * or visit www.oracle.com if you need additional information or have any ohair@554: * questions. jjg@283: */ jjg@283: jjg@283: package com.sun.tools.javap; jjg@283: jjg@283: import java.io.BufferedReader; jjg@283: import java.io.IOException; jjg@283: import java.io.StringReader; jjg@283: import java.util.ArrayList; jjg@283: import java.util.List; jjg@283: import java.util.Set; jjg@283: import java.util.SortedMap; jjg@283: import java.util.SortedSet; jjg@283: import java.util.TreeMap; jjg@283: import java.util.TreeSet; jjg@283: import javax.tools.JavaFileManager; jjg@283: import javax.tools.JavaFileManager.Location; jjg@283: import javax.tools.JavaFileObject; jjg@283: import javax.tools.StandardLocation; jjg@283: jjg@283: import com.sun.tools.classfile.Attribute; jjg@283: import com.sun.tools.classfile.ClassFile; jjg@283: import com.sun.tools.classfile.Code_attribute; jjg@283: import com.sun.tools.classfile.ConstantPoolException; jjg@283: import com.sun.tools.classfile.Instruction; jjg@283: import com.sun.tools.classfile.LineNumberTable_attribute; jjg@283: import com.sun.tools.classfile.SourceFile_attribute; jjg@283: jjg@283: jjg@283: /** jjg@283: * Annotate instructions with source code. jjg@283: * jjg@581: *

This is NOT part of any supported API. jjg@581: * If you write code that depends on this, you do so at your own risk. jjg@283: * This code and its internal interfaces are subject to change or jjg@283: * deletion without notice. jjg@283: */ jjg@283: public class SourceWriter extends InstructionDetailWriter { jjg@283: static SourceWriter instance(Context context) { jjg@283: SourceWriter instance = context.get(SourceWriter.class); jjg@283: if (instance == null) jjg@283: instance = new SourceWriter(context); jjg@283: return instance; jjg@283: } jjg@283: jjg@283: protected SourceWriter(Context context) { jjg@283: super(context); jjg@283: context.put(SourceWriter.class, this); jjg@283: } jjg@283: jjg@283: void setFileManager(JavaFileManager fileManager) { jjg@283: this.fileManager = fileManager; jjg@283: } jjg@283: jjg@283: public void reset(ClassFile cf, Code_attribute attr) { jjg@283: setSource(cf); jjg@283: setLineMap(attr); jjg@283: } jjg@283: jjg@283: public void writeDetails(Instruction instr) { jjg@283: String indent = space(40); // could get from Options? jjg@283: Set lines = lineMap.get(instr.getPC()); jjg@283: if (lines != null) { jjg@283: for (int line: lines) { jjg@283: print(indent); jjg@283: print(String.format(" %4d ", line)); jjg@283: if (line < sourceLines.length) jjg@283: print(sourceLines[line]); jjg@283: println(); jjg@283: int nextLine = nextLine(line); jjg@283: for (int i = line + 1; i < nextLine; i++) { jjg@283: print(indent); jjg@283: print(String.format("(%4d)", i)); jjg@283: if (i < sourceLines.length) jjg@283: print(sourceLines[i]); jjg@283: println(); jjg@283: } jjg@283: } jjg@283: } jjg@660: } jjg@283: jjg@660: public boolean hasSource() { jjg@660: return (sourceLines.length > 0); jjg@283: } jjg@283: jjg@283: private void setLineMap(Code_attribute attr) { jjg@283: SortedMap> map = jjg@283: new TreeMap>(); jjg@283: SortedSet allLines = new TreeSet(); jjg@283: for (Attribute a: attr.attributes) { jjg@283: if (a instanceof LineNumberTable_attribute) { jjg@283: LineNumberTable_attribute t = (LineNumberTable_attribute) a; jjg@283: for (LineNumberTable_attribute.Entry e: t.line_number_table) { jjg@283: int start_pc = e.start_pc; jjg@283: int line = e.line_number; jjg@283: SortedSet pcLines = map.get(start_pc); jjg@283: if (pcLines == null) { jjg@283: pcLines = new TreeSet(); jjg@283: map.put(start_pc, pcLines); jjg@283: } jjg@283: pcLines.add(line); jjg@283: allLines.add(line); jjg@283: } jjg@283: } jjg@283: } jjg@283: lineMap = map; jjg@283: lineList = new ArrayList(allLines); jjg@283: } jjg@283: jjg@283: private void setSource(ClassFile cf) { jjg@283: if (cf != classFile) { jjg@283: classFile = cf; jjg@283: sourceLines = splitLines(readSource(cf)); jjg@283: } jjg@283: } jjg@283: jjg@283: private String readSource(ClassFile cf) { jjg@300: if (fileManager == null) jjg@300: return null; jjg@300: jjg@283: Location location; jjg@283: if (fileManager.hasLocation((StandardLocation.SOURCE_PATH))) jjg@283: location = StandardLocation.SOURCE_PATH; jjg@283: else jjg@283: location = StandardLocation.CLASS_PATH; jjg@283: jjg@283: // Guess the source file for a class from the package name for this jjg@283: // class and the base of the source file. This avoids having to read jjg@283: // additional classes to determine the outmost class from any jjg@283: // InnerClasses and EnclosingMethod attributes. jjg@283: try { jjg@283: String className = cf.getName(); jjg@283: SourceFile_attribute sf = jjg@283: (SourceFile_attribute) cf.attributes.get(Attribute.SourceFile); jjg@283: if (sf == null) { jjg@283: report(messages.getMessage("err.no.SourceFile.attribute")); jjg@283: return null; jjg@283: } jjg@283: String sourceFile = sf.getSourceFile(cf.constant_pool); jjg@283: String fileBase = sourceFile.endsWith(".java") jjg@283: ? sourceFile.substring(0, sourceFile.length() - 5) : sourceFile; jjg@283: int sep = className.lastIndexOf("/"); jjg@283: String pkgName = (sep == -1 ? "" : className.substring(0, sep+1)); jjg@283: String topClassName = (pkgName + fileBase).replace('/', '.'); jjg@283: JavaFileObject fo = jjg@283: fileManager.getJavaFileForInput(location, jjg@283: topClassName, jjg@283: JavaFileObject.Kind.SOURCE); jjg@283: if (fo == null) { jjg@283: report(messages.getMessage("err.source.file.not.found")); jjg@283: return null; jjg@283: } jjg@283: return fo.getCharContent(true).toString(); jjg@283: } catch (ConstantPoolException e) { jjg@283: report(e); jjg@283: return null; jjg@283: } catch (IOException e) { jjg@283: report(e.getLocalizedMessage()); jjg@283: return null; jjg@283: } jjg@283: } jjg@283: jjg@283: private static String[] splitLines(String text) { jjg@283: if (text == null) jjg@283: return new String[0]; jjg@283: jjg@283: List lines = new ArrayList(); jjg@283: lines.add(""); // dummy line 0 jjg@283: try { jjg@283: BufferedReader r = new BufferedReader(new StringReader(text)); jjg@283: String line; jjg@283: while ((line = r.readLine()) != null) jjg@283: lines.add(line); jjg@283: } catch (IOException ignore) { jjg@283: } jjg@283: return lines.toArray(new String[lines.size()]); jjg@283: } jjg@283: jjg@283: private int nextLine(int line) { jjg@283: int i = lineList.indexOf(line); jjg@283: if (i == -1 || i == lineList.size() - 1) jjg@283: return - 1; jjg@283: return lineList.get(i + 1); jjg@283: } jjg@283: jjg@283: private JavaFileManager fileManager; jjg@283: private ClassFile classFile; jjg@283: private SortedMap> lineMap; jjg@283: private List lineList; jjg@283: private String[] sourceLines; jjg@283: }