1/* 2 * Copyright (c) 2009, 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26package com.sun.tools.javap; 27 28import java.io.BufferedReader; 29import java.io.IOException; 30import java.io.StringReader; 31import java.util.ArrayList; 32import java.util.List; 33import java.util.Set; 34import java.util.SortedMap; 35import java.util.SortedSet; 36import java.util.TreeMap; 37import java.util.TreeSet; 38import javax.tools.JavaFileManager; 39import javax.tools.JavaFileManager.Location; 40import javax.tools.JavaFileObject; 41import javax.tools.StandardLocation; 42 43import com.sun.tools.classfile.Attribute; 44import com.sun.tools.classfile.ClassFile; 45import com.sun.tools.classfile.Code_attribute; 46import com.sun.tools.classfile.ConstantPoolException; 47import com.sun.tools.classfile.Instruction; 48import com.sun.tools.classfile.LineNumberTable_attribute; 49import com.sun.tools.classfile.SourceFile_attribute; 50 51 52/** 53 * Annotate instructions with source code. 54 * 55 * <p><b>This is NOT part of any supported API. 56 * If you write code that depends on this, you do so at your own risk. 57 * This code and its internal interfaces are subject to change or 58 * deletion without notice.</b> 59 */ 60public class SourceWriter extends InstructionDetailWriter { 61 static SourceWriter instance(Context context) { 62 SourceWriter instance = context.get(SourceWriter.class); 63 if (instance == null) 64 instance = new SourceWriter(context); 65 return instance; 66 } 67 68 protected SourceWriter(Context context) { 69 super(context); 70 context.put(SourceWriter.class, this); 71 } 72 73 void setFileManager(JavaFileManager fileManager) { 74 this.fileManager = fileManager; 75 } 76 77 public void reset(ClassFile cf, Code_attribute attr) { 78 setSource(cf); 79 setLineMap(attr); 80 } 81 82 public void writeDetails(Instruction instr) { 83 String indent = space(40); // could get from Options? 84 Set<Integer> lines = lineMap.get(instr.getPC()); 85 if (lines != null) { 86 for (int line: lines) { 87 print(indent); 88 print(String.format(" %4d ", line)); 89 if (line < sourceLines.length) 90 print(sourceLines[line]); 91 println(); 92 int nextLine = nextLine(line); 93 for (int i = line + 1; i < nextLine; i++) { 94 print(indent); 95 print(String.format("(%4d)", i)); 96 if (i < sourceLines.length) 97 print(sourceLines[i]); 98 println(); 99 } 100 } 101 } 102 } 103 104 public boolean hasSource() { 105 return (sourceLines.length > 0); 106 } 107 108 private void setLineMap(Code_attribute attr) { 109 SortedMap<Integer, SortedSet<Integer>> map = new TreeMap<>(); 110 SortedSet<Integer> allLines = new TreeSet<>(); 111 for (Attribute a: attr.attributes) { 112 if (a instanceof LineNumberTable_attribute) { 113 LineNumberTable_attribute t = (LineNumberTable_attribute) a; 114 for (LineNumberTable_attribute.Entry e: t.line_number_table) { 115 int start_pc = e.start_pc; 116 int line = e.line_number; 117 SortedSet<Integer> pcLines = map.get(start_pc); 118 if (pcLines == null) { 119 pcLines = new TreeSet<>(); 120 map.put(start_pc, pcLines); 121 } 122 pcLines.add(line); 123 allLines.add(line); 124 } 125 } 126 } 127 lineMap = map; 128 lineList = new ArrayList<>(allLines); 129 } 130 131 private void setSource(ClassFile cf) { 132 if (cf != classFile) { 133 classFile = cf; 134 sourceLines = splitLines(readSource(cf)); 135 } 136 } 137 138 private String readSource(ClassFile cf) { 139 if (fileManager == null) 140 return null; 141 142 Location location; 143 if (fileManager.hasLocation((StandardLocation.SOURCE_PATH))) 144 location = StandardLocation.SOURCE_PATH; 145 else 146 location = StandardLocation.CLASS_PATH; 147 148 // Guess the source file for a class from the package name for this 149 // class and the base of the source file. This avoids having to read 150 // additional classes to determine the outmost class from any 151 // InnerClasses and EnclosingMethod attributes. 152 try { 153 String className = cf.getName(); 154 SourceFile_attribute sf = 155 (SourceFile_attribute) cf.attributes.get(Attribute.SourceFile); 156 if (sf == null) { 157 report(messages.getMessage("err.no.SourceFile.attribute")); 158 return null; 159 } 160 String sourceFile = sf.getSourceFile(cf.constant_pool); 161 String fileBase = sourceFile.endsWith(".java") 162 ? sourceFile.substring(0, sourceFile.length() - 5) : sourceFile; 163 int sep = className.lastIndexOf("/"); 164 String pkgName = (sep == -1 ? "" : className.substring(0, sep+1)); 165 String topClassName = (pkgName + fileBase).replace('/', '.'); 166 JavaFileObject fo = 167 fileManager.getJavaFileForInput(location, 168 topClassName, 169 JavaFileObject.Kind.SOURCE); 170 if (fo == null) { 171 report(messages.getMessage("err.source.file.not.found")); 172 return null; 173 } 174 return fo.getCharContent(true).toString(); 175 } catch (ConstantPoolException e) { 176 report(e); 177 return null; 178 } catch (IOException e) { 179 report(e.getLocalizedMessage()); 180 return null; 181 } 182 } 183 184 private static String[] splitLines(String text) { 185 if (text == null) 186 return new String[0]; 187 188 List<String> lines = new ArrayList<>(); 189 lines.add(""); // dummy line 0 190 try { 191 BufferedReader r = new BufferedReader(new StringReader(text)); 192 String line; 193 while ((line = r.readLine()) != null) 194 lines.add(line); 195 } catch (IOException ignore) { 196 } 197 return lines.toArray(new String[lines.size()]); 198 } 199 200 private int nextLine(int line) { 201 int i = lineList.indexOf(line); 202 if (i == -1 || i == lineList.size() - 1) 203 return - 1; 204 return lineList.get(i + 1); 205 } 206 207 private JavaFileManager fileManager; 208 private ClassFile classFile; 209 private SortedMap<Integer, SortedSet<Integer>> lineMap; 210 private List<Integer> lineList; 211 private String[] sourceLines; 212} 213