/******************************************************************************* * Copyright (c) 2009 the CHISEL group and contributors. * 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: * Del Myers - initial API and implementation *******************************************************************************/ package ca.gc.drdc.oasis.tracing.cjvmtracer.internal; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.lang.reflect.InvocationTargetException; import ca.uvic.chisel.javasketch.SketchPlugin; /** * Interface for commands supported by the Oasis trace server. * @author Del Myers * */ public class OasisCommand { /** * Version of this object. */ private static final long serialVersionUID = 1L; /** * The "command acknowledged" command. Sent to clients after a command has been * received. The data of the command will always be the value of the command last * received. In case of an error (for example, the server is in the wrong state to * receive a command), an acknowledgement will not be sent; an error will be sent * instead. */ public static final char ACK_COMMAND = 'a'; final short ACK_COMMAND_LENGTH = 1; /** * An error command. Sent by the server to the client to indicate that an error has * occurred. The data will be an ASCII character array with a length equal to * the size of data_len will always be 8, and the data will be an error code as * defined in oasis_errors.h. */ public static final char ERROR_COMMAND = 'e'; /** * The "connect command" sent by clients to indicate that they are ready for a connection. * It is expected to be immediately followed by a FILE_COMMAND or a START_COMMAND. */ public static final char CONNECT_COMMAND = 'c'; final short CONNECT_COMMAND_LENGTH = 0; /** * The command used to set a new file for logging. Sent by clients to the server. * It may come after a CONNECT_COMMAND * in order to set the file, but not begin tracing immediately. The data is expected * to be an ASCII character array of length data_len, containing the path to the * destination file. The character array is not expected to be null terminating, and * should not contain any control characters (such as new lines or line feeds). */ public static final char FILE_COMMAND = 'f'; /** * The command used to start logging. Sent by clients to the server. * If data_len is zero, than a previous FILE_COMMAND * must have been sent to set the log file. Otherwise, the semantics for the data * are the same as FILE_COMMAND. */ public static final char START_COMMAND = 's'; /** * The command to pause logging. Sent by clients to the server. data_len is expected * to be zero. Can only be called after a START_COMMAND, and has no meaning if already * paused. If either of these cases occurs, then clients will receive an error. */ public static final char PAUSE_COMMAND = 'p'; /** * The command to resume previously paused logging. Sent by clients to the server. * data_len is expected to be zero. Can only be called while the server has paused * logging, otherwise an error will be issued. */ public static final char RESUME_COMMAND = 'r'; /** * The command to apply a filter to the server. When using server-side * filters, only method calls that match the filter will be saved to disk, * reducing the size of the traced files and the amount of time that it * takes to analyze them, but also reducing robustness. */ public static final char FILTER_COMMAND = 'x'; /** * The command code */ private char command; /** * The data */ private byte[] data; /** * A string representation of the data. */ private transient String dataString; private OasisCommand(char command, byte[] data) { this.command = command; this.data = data; } public static OasisCommand newFileCommand(String fileName) { try { return new OasisCommand(FILE_COMMAND, fileName.getBytes("US-ASCII")); } catch (UnsupportedEncodingException e) { } return null; } public static OasisCommand newFileCommand(File file) { return newFileCommand(file.getAbsolutePath()); } public static OasisCommand newPauseCommand() { return new OasisCommand(PAUSE_COMMAND, new byte[0]); } public static OasisCommand newResumeCommand() { return new OasisCommand(RESUME_COMMAND, new byte[0]); } public static OasisCommand newConnectCommand() { return new OasisCommand(CONNECT_COMMAND, new byte[0]); } public static OasisCommand newStartCommand() { return new OasisCommand(START_COMMAND, new byte[0]); } public static OasisCommand newStartCommand(String fileName) { try { fileName = fileName + '\0'; return new OasisCommand(START_COMMAND, fileName.getBytes("US-ASCII")); } catch (UnsupportedEncodingException e) { } return null; } public static OasisCommand newStartCommand(File file) { return newStartCommand(file.getAbsolutePath()); } public static OasisCommand newFilterCommand(String filterString, boolean isExclusion) { byte[] stringBytes; try { stringBytes = filterString.getBytes("US-ASCII"); } catch (UnsupportedEncodingException e) { SketchPlugin.getDefault().log(new InvocationTargetException(e, "Unable to generate filter for java application")); return null; } byte[] data = new byte[stringBytes.length + 1]; System.arraycopy(stringBytes, 0, data, 1, stringBytes.length); data[0] = (isExclusion) ? (byte)1 : (byte)0; return new OasisCommand(FILTER_COMMAND, data); } /** * Returns the command id. * @return the command id. */ public char getCommand() { return command; } /** * Returns the data for this command. * @return the data for this command. */ public byte[] getData() { return data; } /** * Returns the as a string representation. * @return the data as a string representation, or null if none could be decoded. */ public String getDataString() { if (dataString != null) { return dataString; } try { dataString = new String(getData(), "US-ASCII"); } catch (UnsupportedEncodingException e) { dataString = null; } return dataString; } /** * Returns a byte representation of this command, suitable for sending over a wire. * @return the byte representation of this command. */ private byte[] getBytes() throws IOException { ByteArrayOutputStream byteOut = new ByteArrayOutputStream(data.length+3); DataOutputStream out = new DataOutputStream(byteOut); out.write(command); out.writeShort(data.length); if (data.length > 0) { out.write(data); } out.close(); byte[] bytes = byteOut.toByteArray(); if (bytes.length != data.length+3) { throw new IOException("Byte length mismatch got " + bytes.length + " expected " + (data.length+3)); } return bytes; } /** * Custom code for writing the commands to an output stream. * @param out * @throws IOException */ public void writeExternal(OutputStream out) throws IOException { out.write(getBytes()); } public static void writeExternal(OasisCommand command, OutputStream out) throws IOException { command.writeExternal(out); } /** * Custom code for reading a command from an output stream. * @param in * @throws IOException * @throws ClassNotFoundException */ public static OasisCommand readExternal(InputStream inStream) throws IOException { /** * First, read the command. */ DataInputStream in = new DataInputStream(inStream); int input = in.read(); checkCommand(input); char command = (char)input; //get the next two bytes short s = in.readShort(); //figure out the length int dataLength = (s & 0xFFFF); if (dataLength < 0 || dataLength > 0xFFFF) { //out of range throw new IOException("Data size out of range"); } byte[] data = new byte[dataLength]; //read the data if (dataLength > 0) { int read = in.read(data, 0, dataLength); if (read != dataLength) { throw new IOException("Data length mismatch. Got " + read + " expected " + dataLength); } } return new OasisCommand(command, data); } private static void checkCommand(int command) throws IOException { if (command < 0) { throw new IOException("Error reading command: end of data reached"); } switch (command) { case ACK_COMMAND: case CONNECT_COMMAND: case ERROR_COMMAND: case FILE_COMMAND: case PAUSE_COMMAND: case RESUME_COMMAND: case START_COMMAND: return; //valid command. } throw new IOException("Invalid command: '" + ((char)command) + "'"); } }