2da5486008d6b7591c952eea783a6fcb6f3af5a5
[jalview.git] / srcjar2 / org / apache / log4j / spi / LocationInfo.java
1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  * 
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  * 
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17
18 // Contributors: Mathias Rupprecht <mmathias.rupprecht@fja.com>
19
20 package org.apache.log4j.spi;
21
22 import org.apache.log4j.Layout;
23 import org.apache.log4j.helpers.LogLog;
24
25 import java.io.PrintWriter;
26 import java.io.StringWriter;
27 import java.io.InterruptedIOException;
28 import java.lang.reflect.Method;
29 import java.lang.reflect.InvocationTargetException;
30
31 /**
32    The internal representation of caller location information.
33
34    @since 0.8.3
35 */
36 public class LocationInfo implements java.io.Serializable {
37
38   /**
39      Caller's line number.
40   */
41   transient String lineNumber;
42   /**
43      Caller's file name.
44   */
45   transient String fileName;
46   /**
47      Caller's fully qualified class name.
48   */
49   transient String className;
50   /**
51      Caller's method name.
52   */
53   transient String methodName;
54   /**
55      All available caller information, in the format
56      <code>fully.qualified.classname.of.caller.methodName(Filename.java:line)</code>
57     */
58   public String fullInfo;
59
60   private static StringWriter sw = new StringWriter();
61   private static PrintWriter pw = new PrintWriter(sw);
62
63   private static Method getStackTraceMethod;
64   private static Method getClassNameMethod;
65   private static Method getMethodNameMethod;
66   private static Method getFileNameMethod;
67   private static Method getLineNumberMethod;
68
69
70   /**
71      When location information is not available the constant
72      <code>NA</code> is returned. Current value of this string
73      constant is <b>?</b>.  */
74   public final static String NA = "?";
75
76   static final long serialVersionUID = -1325822038990805636L;
77
78     /**
79      * NA_LOCATION_INFO is provided for compatibility with log4j 1.3.
80      * @since 1.2.15
81      */
82     public static final LocationInfo NA_LOCATION_INFO =
83             new LocationInfo(NA, NA, NA, NA);
84
85
86
87   // Check if we are running in IBM's visual age.
88   static boolean inVisualAge = false;
89   static {
90     try {
91       inVisualAge = Class.forName("com.ibm.uvm.tools.DebugSupport") != null;
92       LogLog.debug("Detected IBM VisualAge environment.");
93     } catch(Throwable e) {
94       // nothing to do
95     }
96       try {
97           Class[] noArgs = null;
98           getStackTraceMethod = Throwable.class.getMethod("getStackTrace", noArgs);
99           Class stackTraceElementClass = Class.forName("java.lang.StackTraceElement");
100           getClassNameMethod = stackTraceElementClass.getMethod("getClassName", noArgs);
101           getMethodNameMethod = stackTraceElementClass.getMethod("getMethodName", noArgs);
102           getFileNameMethod = stackTraceElementClass.getMethod("getFileName", noArgs);
103           getLineNumberMethod = stackTraceElementClass.getMethod("getLineNumber", noArgs);
104       } catch(ClassNotFoundException ex) {
105           LogLog.debug("LocationInfo will use pre-JDK 1.4 methods to determine location.");
106       } catch(NoSuchMethodException ex) {
107           LogLog.debug("LocationInfo will use pre-JDK 1.4 methods to determine location.");
108       }
109   }
110
111   /**
112      Instantiate location information based on a Throwable. We
113      expect the Throwable <code>t</code>, to be in the format
114
115        <pre>
116         java.lang.Throwable
117         ...
118           at org.apache.log4j.PatternLayout.format(PatternLayout.java:413)
119           at org.apache.log4j.FileAppender.doAppend(FileAppender.java:183)
120         at org.apache.log4j.Category.callAppenders(Category.java:131)
121         at org.apache.log4j.Category.log(Category.java:512)
122         at callers.fully.qualified.className.methodName(FileName.java:74)
123         ...
124        </pre>
125
126        <p>However, we can also deal with JIT compilers that "lose" the
127        location information, especially between the parentheses.
128         @param t throwable used to determine location, may be null.
129         @param fqnOfCallingClass class name of first class considered part of
130            the logging framework.  Location will be site that calls a method on this class.
131
132     */
133     public LocationInfo(Throwable t, String fqnOfCallingClass) {
134       if(t == null || fqnOfCallingClass == null) {
135         return;
136     }
137       if (getLineNumberMethod != null) {
138           try {
139               Object[] noArgs = null;
140               Object[] elements =  (Object[]) getStackTraceMethod.invoke(t, noArgs);
141               String prevClass = NA;
142               for(int i = elements.length - 1; i >= 0; i--) {
143                   String thisClass = (String) getClassNameMethod.invoke(elements[i], noArgs);
144                   if(fqnOfCallingClass.equals(thisClass)) {
145                       int caller = i + 1;
146                       if (caller < elements.length) {
147                           className = prevClass;
148                           methodName = (String) getMethodNameMethod.invoke(elements[caller], noArgs);
149                           fileName = (String) getFileNameMethod.invoke(elements[caller], noArgs);
150                           if (fileName == null) {
151                               fileName = NA;
152                           }
153                           int line = ((Integer) getLineNumberMethod.invoke(elements[caller], noArgs)).intValue();
154                           if (line < 0) {
155                               lineNumber = NA;
156                           } else {
157                               lineNumber = String.valueOf(line);
158                           }
159                           StringBuffer buf = new StringBuffer();
160                           buf.append(className);
161                           buf.append(".");
162                           buf.append(methodName);
163                           buf.append("(");
164                           buf.append(fileName);
165                           buf.append(":");
166                           buf.append(lineNumber);
167                           buf.append(")");
168                           this.fullInfo = buf.toString();
169                       }
170                       return;
171                   }
172                   prevClass = thisClass;
173               }
174               return;
175           } catch(IllegalAccessException ex) {
176               LogLog.debug("LocationInfo failed using JDK 1.4 methods", ex);
177           } catch(InvocationTargetException ex) {
178               if (ex.getTargetException() instanceof InterruptedException
179                       || ex.getTargetException() instanceof InterruptedIOException) {
180                   Thread.currentThread().interrupt();
181               }
182               LogLog.debug("LocationInfo failed using JDK 1.4 methods", ex);
183           } catch(RuntimeException ex) {
184               LogLog.debug("LocationInfo failed using JDK 1.4 methods", ex);
185           }
186       }
187
188       String s;
189       // Protect against multiple access to sw.
190       synchronized(sw) {
191         t.printStackTrace(pw);
192         s = sw.toString();
193         sw.getBuffer().setLength(0);
194       }
195       //System.out.println("s is ["+s+"].");
196       int ibegin, iend;
197
198       // Given the current structure of the package, the line
199       // containing "org.apache.log4j.Category." should be printed just
200       // before the caller.
201
202       // This method of searching may not be fastest but it's safer
203       // than counting the stack depth which is not guaranteed to be
204       // constant across JVM implementations.
205       ibegin = s.lastIndexOf(fqnOfCallingClass);
206       if(ibegin == -1) {
207         return;
208     }
209
210       //
211       //   if the next character after the class name exists
212       //       but is not a period, see if the classname is
213       //       followed by a period earlier in the trace.
214       //       Minimizes mistakeningly matching on a class whose
215       //       name is a substring of the desired class.
216       //       See bug 44888.
217       if (ibegin + fqnOfCallingClass.length() < s.length() &&
218               s.charAt(ibegin + fqnOfCallingClass.length()) != '.') {
219           int i = s.lastIndexOf(fqnOfCallingClass + ".");
220           if (i != -1) {
221               ibegin = i;
222           }
223       }
224
225
226       ibegin = s.indexOf(Layout.LINE_SEP, ibegin);
227       if(ibegin == -1) {
228         return;
229     }
230       ibegin+= Layout.LINE_SEP_LEN;
231
232       // determine end of line
233       iend = s.indexOf(Layout.LINE_SEP, ibegin);
234       if(iend == -1) {
235         return;
236     }
237
238       // VA has a different stack trace format which doesn't
239       // need to skip the inital 'at'
240       if(!inVisualAge) {
241         // back up to first blank character
242         ibegin = s.lastIndexOf("at ", iend);
243         if(ibegin == -1) {
244         return;
245     }
246         // Add 3 to skip "at ";
247         ibegin += 3;
248       }
249       // everything between is the requested stack item
250       this.fullInfo = s.substring(ibegin, iend);
251     }
252
253     /**
254      *   Appends a location fragment to a buffer to build the 
255      *     full location info.
256      *    @param buf StringBuffer to receive content.
257      *    @param fragment fragment of location (class, method, file, line),
258      *        if null the value of NA will be appended.
259      *    @since 1.2.15
260      */
261     private static final void appendFragment(final StringBuffer buf,
262                                              final String fragment) {
263           if (fragment == null) {
264              buf.append(NA);
265           } else {
266              buf.append(fragment);
267           }
268     }
269
270     /**
271      * Create new instance.
272      * @param file source file name
273      * @param classname class name
274      * @param method method
275      * @param line source line number
276      *
277      * @since 1.2.15
278      */
279     public LocationInfo(
280       final String file,
281       final String classname,
282       final String method,
283       final String line) {
284       this.fileName = file;
285       this.className = classname;
286       this.methodName = method;
287       this.lineNumber = line;
288       StringBuffer buf = new StringBuffer();
289           appendFragment(buf, classname);
290           buf.append(".");
291           appendFragment(buf, method);
292           buf.append("(");
293           appendFragment(buf, file);
294           buf.append(":");
295           appendFragment(buf, line);
296           buf.append(")");
297           this.fullInfo = buf.toString();
298     }
299
300     /**
301        Return the fully qualified class name of the caller making the
302        logging request.
303     */
304     public
305     String getClassName() {
306       if(fullInfo == null) {
307         return NA;
308     }
309       if(className == null) {
310         // Starting the search from '(' is safer because there is
311         // potentially a dot between the parentheses.
312         int iend = fullInfo.lastIndexOf('(');
313         if(iend == -1) {
314         className = NA;
315     } else {
316           iend =fullInfo.lastIndexOf('.', iend);
317
318           // This is because a stack trace in VisualAge looks like:
319
320           //java.lang.RuntimeException
321           //  java.lang.Throwable()
322           //  java.lang.Exception()
323           //  java.lang.RuntimeException()
324           //  void test.test.B.print()
325           //  void test.test.A.printIndirect()
326           //  void test.test.Run.main(java.lang.String [])
327           int ibegin = 0;
328           if (inVisualAge) {
329             ibegin = fullInfo.lastIndexOf(' ', iend)+1;
330           }
331
332           if(iend == -1) {
333         className = NA;
334     } else {
335         className = this.fullInfo.substring(ibegin, iend);
336     }
337         }
338       }
339       return className;
340     }
341
342     /**
343        Return the file name of the caller.
344
345        <p>This information is not always available.
346     */
347     public
348     String getFileName() {
349       if(fullInfo == null) {
350         return NA;
351     }
352
353       if(fileName == null) {
354         int iend = fullInfo.lastIndexOf(':');
355         if(iend == -1) {
356         fileName = NA;
357     } else {
358           int ibegin = fullInfo.lastIndexOf('(', iend - 1);
359           fileName = this.fullInfo.substring(ibegin + 1, iend);
360         }
361       }
362       return fileName;
363     }
364
365     /**
366        Returns the line number of the caller.
367
368        <p>This information is not always available.
369     */
370     public
371     String getLineNumber() {
372       if(fullInfo == null) {
373         return NA;
374     }
375
376       if(lineNumber == null) {
377         int iend = fullInfo.lastIndexOf(')');
378         int ibegin = fullInfo.lastIndexOf(':', iend -1);
379         if(ibegin == -1) {
380         lineNumber = NA;
381     } else {
382         lineNumber = this.fullInfo.substring(ibegin + 1, iend);
383     }
384       }
385       return lineNumber;
386     }
387
388     /**
389        Returns the method name of the caller.
390     */
391     public
392     String getMethodName() {
393       if(fullInfo == null) {
394         return NA;
395     }
396       if(methodName == null) {
397         int iend = fullInfo.lastIndexOf('(');
398         int ibegin = fullInfo.lastIndexOf('.', iend);
399         if(ibegin == -1) {
400         methodName = NA;
401     } else {
402         methodName = this.fullInfo.substring(ibegin + 1, iend);
403     }
404       }
405       return methodName;
406     }
407 }