August 27, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Processing Stack Trace Data in Java

  • September 17, 2002
  • By Richard G. Baldwin
  • Send Email »
  • More Articles »

The end of a chain of exceptions

Recall that this Throwable object was the result of a chain of operations involving methods catching objects of one type, encapsulating the incoming object as the cause in an object of another type, and then throwing the object of the new type.

Encapsulates the history of the call stack

As a result, this incoming object contains a lot of information about the history of the call stack from the point where the original exception was thrown to the end of the chain of exceptions.  In other words, the incoming Throwable object contains a Throwable cause, which contains a Throwable cause, which contains a Throwable cause, etc.

We will process the incoming Throwable object in order to extract all of that information.

What does a Throwable object contain?

According to Sun:

"A throwable contains a snapshot of the execution stack of its thread at the time it was created. It can also contain a message string that gives more information about the error. Finally, it can contain a cause: another throwable that caused this throwable to get thrown. The cause facility is new in release 1.4. It is also known as the chained exception facility, as the cause can, itself, have a cause, and so on, leading to a "chain" of exceptions, each caused by another."
Assumes a chain of exceptions

The procedure that I will use assumes that at each point along the sequence of exceptions being caught and thrown, a reference to the incoming Throwable object was encapsulated as a cause in the new object.

(This is what constitutes a chain of exceptions.  If the chain is broken by a failure of a catch block somewhere along the way to encapsulate its incoming exception as a cause, neither the printStackTrace method, nor this procedure can obtain information about what happened beyond the break in the chain.)
No cause in the original object

The original object that was thrown did not encapsulate a Throwable as a cause.  Therefore, when we reach the point where the cause encapsulated in the Throwable object equals null, we will conclude that we have reached the end of the chain.

Save stack trace data along the way

This algorithm will drill down, getting and processing Throwable (cause) objects, until it reaches the point where the getCause method returns null.  At each step along the way, it will get and save the stack trace information encapsulated in that particular Throwable object.  Each set of stack trace data will be saved in a Vector object.

Save the Vector objects in a Vector object

The set of Vector objects so produced will be saved in another Vector object.  Thus, we will end up with a Vector object containing references to other Vector objects.  Each of the secondary Vector objects will contain the stack trace data encapsulated in one of the Throwable objects encountered along the way.

Create the outer Vector object

The code in Listing 7 instantiates a new Vector object and saves it in a reference variable named vecOuter.  This Vector object will be populated with references to other Vector objects as the algorithm progresses.

Save incoming reference in variable named cause

The code in Listing 7 also saves the incoming reference in a reference variable having the descriptive name cause.  This reference variable is used in the conditional clause of the while loop that follows.

Begin a while loop

Listing 8 shows the beginning of a while loop.  This loop controls the process of drilling down to the point where a Throwable (cause) object is encountered with a cause value of null (the end of the chain of exceptions).
 

    while(cause != null){
      StackTraceElement[] trace 
               = cause.getStackTrace();

Listing 8

Get and save array of StackTraceElement objects

The code in Listing 8 invokes the getStackTrace method to get and save an array of references to StackTraceElement objects.  These objects represent the stack trace information encapsulated in the Throwable(cause) object being processed during this iteration of the while loop.

What is a StackTraceElement object?

Here is what Sun has to say about an object of type StackTraceElement:

"An element in a stack trace ... Each element represents a single stack frame. All stack frames except for the one at the top of the stack represent a method invocation. The frame at the top of the stack represents the execution point at which the stack trace was generated. Typically, this is the point at which the throwable corresponding to the stack trace was created."
Thus, each StackTraceElement in the array represents one stack frame, and the collection of StackTraceElement objects in the array represents the entire stack trace.

We will be able to correlate the information in each StackTraceElement object with source code line numbers when we examine the stack trace data later.

So, now we have an array of StackTraceElement objects

At this point, we have an array object containing references to StackTraceElement objects.  Each StackTraceElement object represents one stack frame, beginning at the point where the exception represented by this Throwable object was thrown, and ending at the bottom of the stack.

Encapsulate the data in a Vector

For convenient handling later, we will encapsulate the information in this array object in a Vector object, and encapsulate that Vector object, along with other Vector objects in another Vector object.

In addition, we will also encapsulate the type of the Throwable object, along with the message encapsulated in the Throwable object in our Vector object.

The code in Listing 9 instantiates a new Vector object to contain the data.
 

      Vector vec = new Vector();

      vec.add("Cause" 
                   + cause.toString());

Listing 9

Type of Throwable and message

The code in Listing 9 also invokes the toString method on the Throwable object and concatenates the resulting string with the word "Cause".  The string that results from the concatenation is added to in the Vector object.  We will use the word "Cause" later to identify the string containing the type of the Throwable object along with the message encapsulated in the Throwable object.

"The getMessage method is also available to get the message.  However, in this case, the toString method was preferable because it returned both the type of the object and the text of the message."
Get and save information about each stack frame

Each element in the array of StackTraceElement objects contains information about one frame of the stack.  The following four methods of the StackTraceElement class are available to access four different pieces of information describing each stack frame:

  • getClassName - Returns a String, which is the name of the class containing the execution point represented by this stack trace element.
  • getMethodName - Returns a String, which is the name of the method containing the execution point represented by this stack trace element.
  • getFileName - Returns a String, which is the name of the source file containing the execution point represented by this stack trace element.
  • getLineNumber - Returns an int, which is the line number of the source line containing the execution point represented by this stack trace element.
Iterate on the array object with a for loop

The for loop in Listing 10 iterates on the array object, extracting and saving each of the four pieces of information for each element in the array (for each frame in the stack).
 

      for(int i=0;i<trace.length;i++){
        vec.add("Class" + trace[i].
                       getClassName());
        vec.add("Method" + trace[i].
                      getMethodName());
        vec.add("File" + trace[i].
                        getFileName());
        vec.add("Line" + trace[i].
                      getLineNumber());
      }//end for loop

Listing 10

Concatenate with a descriptive word

For convenient handling later on, each of the four pieces of information is concatenated to a word that describes the type of information.

(Note that this concatenation process causes the int line number value to be converted to a string.)
Contents of the Vector object

Each of the four strings (each containing information about a single stack frame) is added to the Vector object during each iteration of the for loop.

Thus, when the entire array object has been processed, the Vector object contains one String object describing the type of Throwable being processed and the message encapsulated in that Throwable object.  In addition, the Vector object contains four String objects representing each stack frame that existed at the point in time that the exception was thrown.

(For example, if the stack contained three frames, the Vector object would contain thirteen elements, four elements representing each stack frame plus one element representing the Throwable object.)
Add the populated inner Vector to the outer Vector

This Vector object is now fully populated.  The single statement in Listing 11 adds it to the outer Vector object that serves as a container for the individual Vector objects being produced by this process.  (Note that the statement in Listing 11 is outside the for loop.)
 

      vecOuter.add(vec);

Listing 11

Get the encapsulated Throwable and do it again

Each Throwable object may encapsulate another throwable object known as the cause.

One Throwable object is processed during each iteration of the current while loop.

The code in Listing 12 invokes the getCause method on the current Throwable object to get a reference to the Throwable object (if any) that it encapsulates.  If this Throwable object doesn't encapsulate another throwable object, the getCause method returns null.
 

      cause = cause.getCause();      
    }//end while loop

    return vecOuter;
  }//end encapsulate

Listing 12

Go back to the top of the while loop

Then control returns to the top of the while loop where the new value of cause is tested for null.  If the value of cause is not null, (meaning that there is another Throwable object to be processed), another iteration of the while loop is executed to process that Throwable object.

If the value of cause is null, control exits the while loop, causing the return statement in Listing 12 to be executed.  This return statement returns a reference to the outer Vector object, which has been populated with Vector objects.  Each of the Vector objects contained in the outer Vector object contains stack trace information encapsulated by one of the Throwable objects involved in the series of chained exceptions.

Back to meth01

The return statement returns control to the method named meth01, as shown in Listing 13.
 

//continuing in meth01
      writeSer(vecOuter,"junk");
      Vector vecIn = 
               (Vector)readSer("junk");

Listing 13

Stack trace data is encapsulated in a Vector object

At this point, the full set of stack trace data for the set of chained exceptions has been encapsulated in an object of the class Vector.  Once we have the stack trace data in this form, we can do whatever we want with it.  For example, we could serialize the Vector object and write it in a disk file or send it across the network.

Demonstrate object serialization

The code in Listing 13 demonstrates this by first serializing the Vector object to a disk file, and then reading that disk file and reconstructing the Vector object in its original state.  This is accomplished by sequentially invoking two methods that I wrote named writeSer and readSer.

Once again, I will set the method named meth01 aside temporarily and discuss the two methods mentioned above.

The writeSer method

The method named writeSer is shown in Listing 14.  This method will serialize an incoming object and write the byte stream produced by serialization into a file whose name is specified by an incoming parameter of type String.  I'm not going to explain this code in detail (I have previously published lessons on object serialization on my web site).
 

  void writeSer(Object obj, 
                          String name){
    try{//to serialize the Vector obj
      ObjectOutputStream  outStream  =
               new  ObjectOutputStream(
                  new FileOutputStream(
                                name));
      outStream.writeObject(obj);
      outStream.flush();
      outStream.close();
    }catch(Exception excp){
      System.out.println(excp);
    }//end catch
  }//end writeSer

Listing 14

The key statement in Listing 14 is the invocation of the writeObject method, which performs the serialization of the Vector object and causes the byte stream to be written into a disk file.

The readSer method

The method named readSer, shown in Listing 15, will read the file containing the serialized object, reconstruct the original object, and return a reference to the reconstructed object as type Object.
 

  Object readSer(String name){
    try{
      ObjectInputStream inStream = 
                 new ObjectInputStream(
                   new FileInputStream(
                                name));
      return inStream.readObject();
       
    }catch(Exception excp){
      System.out.println(excp);
    }//end catch
    //required to satisfy compiler
    return null;
  }//end readSer

Listing 15

The key statement in Listing 15 is the invocation of the readObject method.  This method performs the reconstruction of the original Vector object using a stream of bytes as input.

Back to meth01 again

The return statement in Listing 15 returns control to meth01, as shown in Listing 16.  Listing 16 shows the remainder of meth01.
 

//continuing in meth01
      display(vecIn);
    }//end catch    
  }//end meth01

Listing 16

Display the stack trace data

The code in Listing 16 invokes a method named display that I wrote to display the stack trace data encapsulated in the Vector object.  This data is displayed in a custom format of my own design.

Obviously, at this point, you could display the stack trace data in any format that you choose.  I chose to display it in a format that resembles the standard format produced by the printStackTrace method.  This makes it easy to confirm the validity of the stack trace data encapsulated in the Vector object by comparing the two displays.

The display method

The display method begins in Listing 17.
 

  void display(Vector vecOuter){
    Enumeration enumOuter = 
                   vecOuter.elements();
    while(enumOuter.hasMoreElements()){
      Vector vecInner = (Vector)
               enumOuter.nextElement();

Listing 17

Nested Vector objects

Recall that the stack trace data is now contained in nested Vector objects.  That is to say, one Vector object serves as an outer container for several other Vector objects.  Each of the inner Vector objects contains stack trace information pertaining to one of the Throwable objects produced by the chain of exceptions and encapsulated in the final Throwable object.

Nested enumerations

I needed to display the data encapsulated in each of the inner Vector objects.  This led to a solution based on nested enumerations.  An outer loop enumerates on the outer Vector object, to extract each of the inner vector objects in sequence.

An inner loop enumerates on each of the inner Vector objects to extract, format, and display the data encapsulated in each of the inner Vector objects.

I don't plan to discuss enumeration in detail.  I have previously published tutorial lessons explaining the enumeration process on my web site.

The outer enumeration loop

The code in Listing 17 sets up the outer enumerator loop.  This loop gets a reference to one element contained in the outer Vector object during each iteration of the loop.  These elements are also Vector objects, and each of them is referred to as vecInner.

The Vector object accessed during each iteration of the outer loop contains String data describing stack trace data originally encapsulated in a Throwable object (in the chain of throwable objects).

The inner enumeration loop

The code in Listing 18 sets up an inner enumeration loop.  This loop gets a reference to one String element contained in the inner Vector object during each iteration of the loop.
 

      Enumeration enumInner = 
                   vecInner.elements();
      while(enumInner.
                    hasMoreElements()){
        String str = (String)
               enumInner.nextElement();

Listing 18

String identifiers

Recall that each of the String objects in the Vector object begins with one of the following words:

  • Cause
  • Class
  • Method
  • File
  • Line
There should be only one String object beginning with the word Cause in each Vector object.  Then there should be one String object beginning with each of the other four words for each frame that existed on the stack at the point in time that the Throwable object was constructed and thrown.

A decision tree

Although it looks complicated, the code in Listing 19 is simply a big decision tree that tests the String object to determine which of the five types of data it represents.  Then the code in Listing 19 formats the data appropriately and displays it on the screen.
 

        if(str.startsWith("Cause")){
          System.out.print(
                 str.substring("Cause".
                            length()));
        }else if(str.startsWith(
                             "Class")){
          System.out.println();
          System.out.print("  " 
               + str.substring("Class".
                            length()));
        }else if(str.startsWith(
                            "Method")){
          System.out.print("." 
              + str.substring("Method".
                            length()));
        }else if(str.startsWith(
                              "File")){
          System.out.print("(" 
                + str.substring("File".
                            length()));
        }else if(str.startsWith(
                              "Line")){
          System.out.print(":" 
                + str.substring("Line".
                      length()) + ")");
        }//end else

Listing 19

An example output

For example, the first five passes through the inner enumeration loop on an individual Vector object might produce something like that shown in Figure 2.
 

NewEx01: Thrown from meth02
  Class01.meth02(StackTr01.java:92)

Figure 2

The first line in Figure 2 results from the String object that begins with the word Cause.  The second line is a composite of the information extracted from four String objects beginning with the words Class, Method, File, and Line.



Page 2 of 3



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel