/*******************************************************************************
* Copyright © 2006, 2013 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*
*******************************************************************************/
package org.eclipse.edt.javart.resources;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.io.Serializable;
import org.eclipse.edt.javart.Constants;
import org.eclipse.edt.javart.util.JavartUtil;
/**
* Trace is used to trace the behavior of a generated program. The output can go
* to a file, System.err or System.out. The <code>put</code> method causes a
* message to be traced.
* <P>
* Classes can use the <code>traceIsOn</code> methods to determine when they
* should add something to the trace. Both versions of <code>traceIsOn</code>
* examine the trace level, which is a bit field. Each bit represents a category
* of operations, and if a bit is set then any operation in that category should
* be traced. A trace level of zero means no tracing will be done.
*/
public class Trace implements Serializable
{
private static final long serialVersionUID = Constants.SERIAL_VERSION_UID;
/**
* The underlying object that tracing is written to.
*/
private transient PrintWriter out;
/**
* The operations that will be traced; possible values are:
* NO_TRACE no trace is enabled
* GENERAL_TRACE program setup attributes, function, CALL trace
* MATH_TRACE trace the Math functions
* STRING_TRACE trace the String functions
* TABLE_TRACE trace operations on tables
* CALL_PARM_TRACE trace parameters on CALL
* CALL_OPTIONS_TRACE trace settings on CALL
* UI_RECORD_TRACE trace operations on UI records
* JDBC_TRACE trace JDBC operations
* FILE_TRACE trace file I/O and driver calls
* PROPERTIES_TRACE trace the properties used by the program
*/
private int traceLevel;
/**
* True if we're writing the trace to a file.
*/
private boolean usingFile;
/**
* The name of the trace device.
*/
private String name;
public static final int NO_TRACE = 0;
public static final int GENERAL_TRACE = 1;
public static final int MATH_TRACE = 2;
public static final int STRING_TRACE = 4;
public static final int TABLE_TRACE = 8;
public static final int CALL_PARM_TRACE = 16;
public static final int CALL_OPTIONS_TRACE = 32;
public static final int UI_RECORD_TRACE = 64;
public static final int JDBC_TRACE = 128;
public static final int FILE_TRACE = 256;
public static final int PROPERTIES_TRACE = 512;
/**
* The default name of the trace file.
*/
public static final String DEFAULT_FILE_NAME = "egltrace.out";
/**
* Constructs the trace object. <code>devType</code> should be
* "0" to use System.out, "1" to use System.err, or "2" to use
* a file.
*
* <P> <code>file</code> names the file to use when <code>devType</code>
* is "2". It defaults to <code>DEFAULT_FILE_NAME</code>.
*
* @param trcLevel
* the initial level of tracing (0, 1, 2 ...).
* @param devType
* the device type (0, 1, or 2).
* @param file
* the file to trace to.
*/
public Trace( String trcLevel, String devType, String file )
{
traceLevel = convertStringToIntValue( trcLevel, NO_TRACE );
if ( traceLevel == NO_TRACE )
{
name = "not tracing";
}
else
{
if ( devType.equals( "0" ) )
{
out = new PrintWriter( System.out );
name = "System.out";
}
else if ( devType.equals( "2" ) )
{
if ( file == null || file.length() == 0 )
{
file = DEFAULT_FILE_NAME;
}
try
{
out =
new PrintWriter(
new BufferedOutputStream(
new FileOutputStream( file, true ) ),
true );
name = file;
usingFile = true;
}
catch ( Exception ex )
{
out = new PrintWriter( System.err );
name = "System.err";
put( " Exception on trace file: <" + file + ">" );
ex.printStackTrace( out );
}
}
else
{
out = new PrintWriter( System.err );
name = "System.err";
}
}
}
/**
* Returns false if the trace level is set to NO_TRACE, true otherwise.
*
* @return false if the trace level is set to NO_TRACE, true otherwise.
*/
public boolean traceIsOn()
{
return (traceLevel != NO_TRACE);
}
/**
* Returns true if the current trace level includes <code>level</code>,
* false otherwise.
*
* @param level
* the trace level to inquire about.
* @return true if the current trace level includes <code>level</code>,
* false otherwise.
*/
public boolean traceIsOn( int level )
{
return ((traceLevel & level) != 0);
}
/**
* Allows for programmatically changing the current trace level value.
* CUI is using this to turn off tracing so that when we assign
* invalid values to EGL variables bad error messages aren't displayed
* in the trace log. CUI turns tracing back on after the assignment.
* @param level
* @return int the old/previous trace level
*/
public int setTraceLevel(int level)
{
int oldlevel = this.traceLevel;
this.traceLevel = level;
return oldlevel;
}
/**
* Closes the trace object.
*/
public void close()
{
if ( usingFile && out != null )
{
out.close();
out = null;
}
}
/**
* If we're tracing to a file, and the trace object is closed, it will be
* reopened.
*/
private void checkReopen()
{
if ( usingFile && out == null )
{
try
{
out =
new PrintWriter(
new BufferedOutputStream(
new FileOutputStream( name, true ) ),
true );
}
catch ( Exception ex )
{
usingFile = false;
out = new PrintWriter( System.err );
put( " Exception on trace file: <" + name + ">" );
name = "System.err";
ex.printStackTrace( out );
}
}
}
/**
* Returns the integer representation of <code>s</code>, or
* <code>defVal</code> if any conversion error is detected.
*
* @param s
* the String to be converted.
* @param defVal
* the default integer value to return if there's an error.
* @return the integer representation of <code>s</code>.
*/
private int convertStringToIntValue( String s, int defVal )
{
if ( s == null )
{
return defVal;
}
try
{
return Integer.parseInt( s );
}
catch ( NumberFormatException e )
{
return defVal;
}
}
/**
* Returns the trace name and traceLevel.
*
* @return information about the state of this object.
*/
public String getInfo()
{
return "Trace to: " + name + ", level: " + traceLevel;
}
/**
* Writes text to the trace object.
*
* @param text
* the output string.
*/
public void put( String text )
{
checkReopen();
out.println( '(' + JavartUtil.getCurrentTime() + "> [" + Thread.currentThread().getName() + "]" + text );
out.flush();
}
/**
* Writes the data of a byte array to the trace output device. The output is
* written using <code>put<code>.
* <P>
* Every 16 bytes, write a new line of output to the trace device.
* Lines look like this:
* <PRE>
* a0 | 00000020 48454c4c 4f202020 20202020 | HELLO
* </PRE>
* The first part is the address of the first byte. The middle part shows
* each byte, and the last part shows the character representation of the
* bytes.
*
* @param bytes the byte array to be written.
*/
public void putBytes( byte[] bytes )
{
putBytes( bytes, bytes.length );
}
/**
* Writes the data of a byte array to the trace output
* device. The output is written using <code>put<code>.
* <P>
* Every 16 bytes, write a new line of output to the trace device.
* Lines look like this:
* <PRE>
* a0 | 00000020 48454c4c 4f202020 20202020 | HELLO
* </PRE>
* The first part is the address of the first byte. The middle part shows
* each byte, and the last part shows the character representation of the
* bytes.
*
* @param bytes the byte array to be written.
* @param length the number of bytes to write.
*/
public void putBytes( byte[] bytes, int length )
{
// **** CAUTION: This code is ugly! You might vomit! **** //
// Write the hex value of each byte into a StringBuilder.
StringBuilder byteStrBuf = new StringBuilder( " " );
String charStr;
String spaces;
int spacer = 0;
int label = 0;
int chars = 0;
int i = 0;
for ( ; i < length; i++ )
{
if ( chars == 16 )
{
// Time to output this line.
charStr = new String( bytes, i - 16, 16 );
if ( label < 0x10 )
{
spacer = 0;
}
else if ( label < 0x100 )
{
spacer = 1;
}
else if ( label < 0x1000 )
{
spacer = 2;
}
else
{
spacer = 3;
}
spaces = " ".substring( spacer );
put( spaces + Integer.toHexString( label ) + " |" + byteStrBuf + " | " + charStr );
byteStrBuf = new StringBuilder();
chars = 0;
label += 16;
}
if ( i % 4 == 0 && i > 0 )
{
byteStrBuf.append( ' ' );
}
if ( (bytes[ i ] & 0xff) < 0x10 )
{
byteStrBuf.append( '0' );
}
chars++;
byteStrBuf.append( Integer.toHexString( bytes[ i ] & 0xff ) );
}
if ( chars > 0 )
{
// Write out the last line.
if ( chars < 16 )
{
// It had less than 16 bytes in it. Add blanks.
String blanks = " ";
int bytesMissing;
int blocksMissing;
if ( chars % 4 == 0 )
{
bytesMissing = 0;
}
else
{
bytesMissing = 4 - (chars % 4);
}
blocksMissing = 3 - ((chars - 1) / 4);
int blanksNeeded = bytesMissing * 2 + blocksMissing * 9;
byteStrBuf.append( blanks.substring( 0, blanksNeeded ) );
}
charStr = new String( bytes, i - chars, chars );
if ( label < 0x10 )
{
spacer = 0;
}
else if ( label < 0x100 )
{
spacer = 1;
}
else if ( label < 0x1000 )
{
spacer = 2;
}
else
{
spacer = 3;
}
spaces = " ".substring( spacer );
put( spaces + Integer.toHexString( label ) + " |" + byteStrBuf + " | " + charStr );
}
}
/**
* Serializes an instance of this class.
*
* @param out The output stream.
* @throws IOException
*/
private void writeObject( ObjectOutputStream out )
throws IOException
{
out.defaultWriteObject();
close();
}
/**
* Deserializes an instance of this class.
*
* @param in The input stream.
* @throws IOException
* @throws ClassNotFoundException
*/
private void readObject( ObjectInputStream in )
throws IOException, ClassNotFoundException
{
in.defaultReadObject();
if ( traceIsOn() )
{
if ( usingFile )
{
checkReopen();
}
else if ( "System.out".equals( name ) )
{
out = new PrintWriter( System.out );
}
else
{
out = new PrintWriter( System.err );
}
}
}
public boolean isValidDeviceType()
{
return out != null;
}
}