/*******************************************************************************
* Copyright (c) 2012 Google, Inc.
* 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:
* Google, Inc. - initial API and implementation
*******************************************************************************/
package com.windowtester.codegen.debug;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;
import org.eclipse.swt.dnd.TextTransfer;
import org.eclipse.swt.dnd.Transfer;
import com.windowtester.codegen.CodeGenPlugin;
import com.windowtester.recorder.event.ISemanticEvent;
import com.windowtester.runtime.swt.internal.preferences.WindowTesterSupport;
/**
* A data structure holding recording information used for debugging purposes. Immediately
* before launching a recording, call {@link #newRecording()} to instantiate a new
* instance of this class, then use {@link #getInfo()} to cache the recording information
* during the launch operation. If the recording goes astray, then a debug recording
* dialog can be shown to the user with the information cached in this structure.
*/
public class DebugRecordingInfo
{
private static DebugRecordingInfo mostRecentInfo;
private IPath workspaceLocation;
private String[] recorderClasspath;
private String[] bundleClasspath;
private ISemanticEvent[] semanticEvents;
private String recLogContent;
private String devLogContent;
static final String RECORDER_CLASSPATH = "Recorder Classpath";
static final String PROJECT_CLASSPATH = "Project Classpath";
////////////////////////////////////////////////////////////////////////////
//
// Construction
//
////////////////////////////////////////////////////////////////////////////
/**
* Create and cache a new recording debug info structure
*/
public static void newRecording() {
mostRecentInfo = new DebugRecordingInfo();
}
/**
* Answer the information for the most recent recording
*
* @return the most recent information or <code>null</code> if none
*/
public static DebugRecordingInfo getInfo() {
return mostRecentInfo;
}
private DebugRecordingInfo() {
}
////////////////////////////////////////////////////////////////////////////
//
// Accessors
//
////////////////////////////////////////////////////////////////////////////
public IPath getWorkspaceLocation() {
return workspaceLocation;
}
public void setWorkspaceLocation(IPath location) {
this.workspaceLocation = location;
}
public String[] getRecorderClasspath() {
return recorderClasspath;
}
public void setRecorderClasspath(String[] classpath) {
this.recorderClasspath = classpath;
}
public String[] getBundleClasspath() {
return bundleClasspath;
}
public void setBundleClasspath(String[] classpath) {
this.bundleClasspath = classpath;
}
public Object[] getClasspathNames() {
Collection result = new ArrayList();
if (recorderClasspath != null)
result.add(RECORDER_CLASSPATH);
if (bundleClasspath != null)
result.add(PROJECT_CLASSPATH);
return (String[]) result.toArray(new String[result.size()]);
}
public Object[] getClasspath(Object parentElement) {
if (parentElement == DebugRecordingInfo.RECORDER_CLASSPATH)
return getRecorderClasspath();
if (parentElement == DebugRecordingInfo.PROJECT_CLASSPATH)
return getBundleClasspath();
return null;
}
public ISemanticEvent[] getSemanticEvents() {
return semanticEvents;
}
public void setSemanticEvents(ISemanticEvent[] semanticEvents) {
this.semanticEvents = semanticEvents;
}
public void setSemanticEvents(Collection events) {
setSemanticEvents((ISemanticEvent[]) (events != null ? events.toArray(new ISemanticEvent[events.size()]) : null));
}
/**
* Determine if the debug information should be shown to the user
*
* @return <code>true</code> if it is recommended to display this information to the
* user, else <code>false</code>
*/
public boolean shouldShow() {
return semanticEvents == null || semanticEvents.length == 0;
}
public String getRecorderLogContent() {
if (recLogContent == null)
recLogContent = readLogContent(workspaceLocation);
return recLogContent;
}
public String getDevelopmentLogContent() {
if (devLogContent == null)
devLogContent = readLogContent(ResourcesPlugin.getWorkspace().getRoot().getLocation());
return devLogContent;
}
private static String readLogContent(IPath workspaceLocation) {
if (workspaceLocation == null)
return "unspecified workspace location";
File logFile = workspaceLocation.append(".metadata").append(".log").toFile();
if (!logFile.exists())
return "no log file found:\n" + logFile.getPath();
StringBuffer buf = new StringBuffer(2000);
Reader logReader;
try {
logReader = new BufferedReader(new FileReader(logFile));
}
catch (FileNotFoundException e) {
return "failed to open log file found in recorder workspace:\n" + logFile.getPath() + "\n" + e;
}
char[] cbuf = new char[1024];
try {
logReader.skip(Math.max(0, logFile.length() - 50000));
while (true) {
int count = logReader.read(cbuf);
if (count == -1)
break;
buf.append(cbuf, 0, count);
}
}
catch (IOException e) {
buf.append("\nException while reading recorder log file\n");
buf.append(e);
}
finally {
try {
logReader.close();
}
catch (IOException e) {
buf.append("\nfailed to close recorder log reader\n");
buf.append(e);
}
}
String content = buf.toString();
int index = content.lastIndexOf(System.getProperty("line.separator") + "!SESSION ");
if (index > 0)
content = content.substring(index);
return content;
}
////////////////////////////////////////////////////////////////////////////
//
// Operations
//
////////////////////////////////////////////////////////////////////////////
/**
* Copy the receiver's content to the clipboard
*/
public void copyToClipboard() {
CodeGenPlugin.getDefault().getClipboard().setContents(new Object[]{
convertToText()
}, new Transfer[]{
TextTransfer.getInstance()
});
}
/**
* Convert the receiver's content into a single blob of text
*
* @return the text (not <code>null</code>)
*/
private String convertToText() {
StringWriter stringWriter = new StringWriter();
PrintWriter writer = new PrintWriter(stringWriter);
printInfo(writer);
return stringWriter.toString();
}
/**
* Append support information to the specified buffer. Subclasses should
* override {@link #printSupportInfo(PrintWriter)} rather than this method to provide
* additional information
*
* @param writer the print writer to which information is appended
*/
private void printInfo(PrintWriter writer) {
WindowTesterSupport.getInstance().printInfo(writer);
writer.println();
writer.println("****************** Recording Information");
printClasspath(writer, RECORDER_CLASSPATH, recorderClasspath);
printClasspath(writer, PROJECT_CLASSPATH, bundleClasspath);
writer.println();
writer.println("Recorder log:");
writer.println(getRecorderLogContent());
writer.println();
writer.println("****************** Development Information");
writer.println();
writer.println("Development log:");
writer.println(getDevelopmentLogContent());
}
private static void printClasspath(PrintWriter writer, String name, String[] classpath) {
writer.println();
writer.print(name);
writer.println(":");
if (classpath != null) {
for (int i = 0; i < classpath.length; i++) {
writer.print(" ");
writer.println(classpath[i]);
}
}
}
}