1   /**
2    * Distribution License:
3    * JSword is free software; you can redistribute it and/or modify it under
4    * the terms of the GNU Lesser General Public License, version 2.1 or later
5    * as published by the Free Software Foundation. This program is distributed
6    * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
7    * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
8    * See the GNU Lesser General Public License for more details.
9    *
10   * The License is available on the internet at:
11   *      http://www.gnu.org/copyleft/lgpl.html
12   * or by writing to:
13   *      Free Software Foundation, Inc.
14   *      59 Temple Place - Suite 330
15   *      Boston, MA 02111-1307, USA
16   *
17   * © CrossWire Bible Society, 2005 - 2016
18   *
19   */
20  package org.crosswire.common.util;
21  
22  import java.io.PrintWriter;
23  import java.io.StringWriter;
24  import java.util.Iterator;
25  import java.util.NoSuchElementException;
26  
27  /**
28   * Unscramble the current stack, and present the data from it to the user in
29   * various forms. This code is slightly dodgy in that it makes use of the way
30   * exceptions print their stack traces, however it is probably a safe enough
31   * assumption for the moment.
32   * 
33   * @see gnu.lgpl.License The GNU Lesser General Public License for details.
34   * @author Joe Walker
35   */
36  public final class StackTrace {
37      /**
38       * Generate a stack trace an model it
39       */
40      public StackTrace() {
41          init(new Throwable(), 2);
42      }
43  
44      /**
45       * We already have an Exception that we'd like to model
46       * 
47       * @param ex
48       *            The Exception to model
49       */
50      public StackTrace(Throwable ex) {
51          init(ex, 1);
52      }
53  
54      /**
55       * Create a stack trace of the code at this point
56       * 
57       * @param exception
58       *            The Throwable containing the Stack Trace
59       * @param discard
60       *            The number of uppermost stack frames to ignore
61       */
62      private void init(Throwable exception, int discard) {
63          StringWriter sout = new StringWriter();
64          exception.printStackTrace(new PrintWriter(sout));
65          String msg = new String(sout.getBuffer());
66          String[] calls = StringUtil.split(msg, "\n\r");
67  
68          int total = 0;
69          for (int i = 0; i < calls.length - discard; i++) {
70              String call = calls[i + discard];
71  
72              if (!(call.startsWith("Caused") || call.indexOf("...") >= 0)) {
73                  total++;
74              }
75          }
76  
77          classNames = new String[total];
78          methodNames = new String[total];
79          fileNames = new String[total];
80          lineNumbers = new int[total];
81  
82          int j = 0;
83          for (int i = 0; i < calls.length - discard; i++) {
84              String call = calls[i + discard];
85              boolean oops = false;
86              try {
87                  if (!(call.startsWith("Caused") || call.indexOf("...") >= 0)) {
88                      int spcIndex = call.indexOf(' ');
89                      int lhsIndex = call.indexOf('(');
90                      int clnIndex = call.indexOf(':');
91                      int rhsIndex = call.indexOf(')');
92  
93                      String fullFn = call.substring(spcIndex + 1, lhsIndex).trim();
94                      int lastDot = fullFn.lastIndexOf('.');
95  
96                      classNames[j] = fullFn.substring(0, lastDot).replace('/', '.');
97                      methodNames[j] = fullFn.substring(lastDot + 1);
98  
99                      if (clnIndex != -1 && lhsIndex < clnIndex) {
100                         fileNames[j] = call.substring(lhsIndex + 1, clnIndex);
101                         lineNumbers[j] = Integer.parseInt(call.substring(clnIndex + 1, rhsIndex));
102                     } else {
103                         fileNames[j] = call.substring(lhsIndex + 1, rhsIndex);
104                         lineNumbers[j] = 0;
105                     }
106                     j++;
107                 }
108             } catch (NumberFormatException ex) {
109                 oops = true;
110             } catch (StringIndexOutOfBoundsException ex) {
111                 // For whatever reason, Java 7 under Web Start is throwing this on
112                 // call.substring(spcIndex + 1, lhsIndex) with a -56 being passed.
113                 oops = true;
114             }
115             if (oops) {
116                 classNames[j] = "ParseError: ";
117                 methodNames[j] = call;
118                 fileNames[j] = "Error";
119                 lineNumbers[j] = 0;
120                 j++;
121             }
122         }
123     }
124 
125     /**
126      * How many stack elements are there?
127      * 
128      * @return the number of stack elements
129      */
130     public int countStackElements() {
131         return methodNames.length;
132     }
133 
134     /**
135      * Get the name of a function
136      * 
137      * @param level
138      *            Number of calling function
139      * @return the function name
140      */
141     public String getFunctionName(int level) {
142         return methodNames[level];
143     }
144 
145     /**
146      * Get the name of a function including class name
147      * 
148      * @param level
149      *            Number of calling function
150      * @return the full function name
151      */
152     public String getFullFunctionName(int level) {
153         return classNames[level] + '.' + methodNames[level] + "()";
154     }
155 
156     /**
157      * Get the name of a class
158      * 
159      * @param level
160      *            Number of calling function
161      * @return the class name
162      */
163     public String getClassName(int level) {
164         return classNames[level];
165     }
166 
167     /**
168      * Get the name of a file
169      * 
170      * @param level
171      *            Number of calling function
172      * @return the file name
173      */
174     public String getFileName(int level) {
175         return fileNames[level];
176     }
177 
178     /**
179      * Get the line number within a file
180      * 
181      * @param level
182      *            Number of calling function
183      * @return the line number
184      */
185     public int getLineNumber(int level) {
186         return lineNumbers[level];
187     }
188 
189     /**
190      * Get the count of classes
191      * 
192      * @return the number of classes
193      */
194     public int getClassCount() {
195         return classNames.length;
196     }
197 
198     /**
199      * Get the Class that owns the function
200      * 
201      * @param level
202      *            Number of calling function
203      * @return the function owner
204      */
205     public Class<?> getClass(int level) {
206         try {
207             return ClassUtil.forName(classNames[level]);
208         } catch (ClassNotFoundException ex) {
209             assert false : ex;
210             return null;
211         }
212     }
213 
214     /**
215      * Base class for the real enumeration implementations below
216      * @param <T> the type of the object in the stack
217      */
218     public abstract class AbstractStackIterator<T> implements Iterator<T> {
219         /* (non-Javadoc)
220          * @see java.util.Iterator#hasNext()
221          */
222         public boolean hasNext() {
223             return level < getClassCount();
224         }
225 
226         /* (non-Javadoc)
227          * @see java.util.Iterator#remove()
228          */
229         public void remove() {
230             throw new UnsupportedOperationException();
231         }
232 
233         /**
234          * @return the level.
235          * @throws NoSuchElementException 
236          */
237         public int getAndIncrementLevel() throws NoSuchElementException {
238             return level++;
239         }
240 
241         /**
242          * Are there more stack levels
243          */
244         private int level;
245     }
246 
247     /**
248      * To iterate over the class names
249      * 
250      * @return an iterator of class names
251      */
252     public Iterator<String> getClassNameElements() {
253         return new AbstractStackIterator<String>() {
254             public String next() throws NoSuchElementException {
255                 if (!hasNext()) {
256                     throw new NoSuchElementException();
257                 }
258                 return getClassName(getAndIncrementLevel());
259             }
260         };
261     }
262 
263     /**
264      * To iterate over the function names
265      * 
266      * @return an iterator of function names
267      */
268     public Iterator<String> getFunctionNameElements() {
269         return new AbstractStackIterator<String>() {
270             public String next() throws NoSuchElementException {
271                 if (!hasNext()) {
272                     throw new NoSuchElementException();
273                 }
274                 return getFunctionName(getAndIncrementLevel());
275             }
276         };
277     }
278 
279     /**
280      * To iterate over the full function names
281      * 
282      * @return an iterator of full function names
283      */
284     public Iterator<String> getFullFunctionNameElements() {
285         return new AbstractStackIterator<String>() {
286             public String next() throws NoSuchElementException {
287                 if (!hasNext()) {
288                     throw new NoSuchElementException();
289                 }
290                 return getFullFunctionName(getAndIncrementLevel());
291             }
292         };
293     }
294 
295     /**
296      * Array containing the class names
297      */
298     private String[] classNames;
299 
300     /**
301      * Array containing the method names
302      */
303     private String[] methodNames;
304 
305     /**
306      * Array containing the file names
307      */
308     private String[] fileNames;
309 
310     /**
311      * Array containing the line numbers
312      */
313     private int[] lineNumbers;
314 }
315