blob: b7db1e725b0f1571beba59624afbba5b7725a69b [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2015 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Alexander Kurtakov <akurtako@redhat.com> - bug 458490
*******************************************************************************/
package org.eclipse.core.tests.internal.runtime;
import java.io.*;
import java.util.ArrayList;
import java.util.StringTokenizer;
import org.eclipse.core.internal.runtime.Messages;
import org.eclipse.core.runtime.*;
import org.eclipse.osgi.util.NLS;
/**
* Reads a structured log from disk and reconstructs status and exception objects.
* General strategy: log entries that are malformed in any way are skipped, and an extra
* status is returned mentioned that there were problems.
*/
public class PlatformLogReader {
private ArrayList<IStatus> list = null;
private String currentLine = "";
private BufferedReader reader;
// constants copied from the PlatformLogWriter (since they are private
// to that class and this class should be used only in test suites)
private static final String KEYWORD_SESSION = "!SESSION";
private static final String KEYWORD_ENTRY = "!ENTRY";
private static final String KEYWORD_SUBENTRY = "!SUBENTRY";
private static final String KEYWORD_MESSAGE = "!MESSAGE";
private static final String KEYWORD_STACK = "!STACK";
private static final int NULL = -2;
private static final int SESSION = 1;
private static final int ENTRY = 2;
private static final int SUBENTRY = 4;
private static final int MESSAGE = 8;
private static final int STACK = 16;
private static final int UNKNOWN = 32;
protected void log(Exception ex) {
String msg = NLS.bind(Messages.meta_exceptionParsingLog, ex.getMessage());
list.add(new Status(IStatus.WARNING, Platform.PI_RUNTIME, Platform.PARSE_PROBLEM, msg, ex));
}
protected Throwable readException(String message) throws IOException {
if (currentLine == null || getLineType() != STACK) {
return null;
}
StringTokenizer tokenizer = new StringTokenizer(currentLine);
tokenizer.nextToken();
// FIXME: handle the code to read status objects which were
// nested inside of CoreExceptions in the log.
Integer.parseInt(tokenizer.nextToken());
currentLine = reader.readLine();
return new FakeException(null, readText());
}
/**
* Reads the given log file and returns the contained status objects.
* If the log file could not be read, a status object indicating this fact
* is returned.
*/
public synchronized IStatus[] readLogFile(String path) {
list = new ArrayList<>();
InputStream input = null;
try {
input = new FileInputStream(path);
reader = new BufferedReader(new InputStreamReader(input, "UTF-8"));//$NON-NLS-1$
currentLine = reader.readLine();
while (currentLine != null) {
switch (getLineType()) {
case ENTRY :
IStatus status = readEntry();
if (status != null) {
list.add(status);
}
break;
case SESSION :
readSession();
break;
case SUBENTRY :
case MESSAGE :
case STACK :
case UNKNOWN :
currentLine = reader.readLine();
break;
}
}
} catch (IOException e) {
log(e);
} finally {
try {
if (input != null) {
input.close();
}
} catch (IOException e) {
log(e);
}
}
return list.toArray(new IStatus[list.size()]);
}
protected int getLineType() {
if (currentLine == null) {
return NULL;
}
StringTokenizer tokenizer = new StringTokenizer(currentLine);
if (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
if (token.equals(KEYWORD_SESSION)) {
return SESSION;
}
if (token.equals(KEYWORD_ENTRY)) {
return ENTRY;
}
if (token.equals(KEYWORD_SUBENTRY)) {
return SUBENTRY;
}
if (token.equals(KEYWORD_MESSAGE)) {
return MESSAGE;
}
if (token.equals(KEYWORD_STACK)) {
return STACK;
}
}
return UNKNOWN;
}
/**
* A reconsituted exception that only contains a stack trace and a message.
*/
class FakeException extends Throwable {
/**
* All serializable objects should have a stable serialVersionUID
*/
private static final long serialVersionUID = 1L;
private String message;
private String stackTrace;
FakeException(String msg, String stack) {
this.message = msg;
this.stackTrace = stack;
}
@Override
public String getMessage() {
return message;
}
@Override
public void printStackTrace() {
printStackTrace(System.out);
}
@Override
public void printStackTrace(PrintWriter writer) {
writer.println(stackTrace);
}
@Override
public void printStackTrace(PrintStream stream) {
stream.println(stackTrace);
}
}
protected IStatus readEntry() throws IOException {
if (currentLine == null || getLineType() != ENTRY) {
return null;
}
StringTokenizer tokens = new StringTokenizer(currentLine);
// skip over the ENTRY keyword
tokens.nextToken();
String pluginID = tokens.nextToken();
int severity = Integer.parseInt(tokens.nextToken());
int code = Integer.parseInt(tokens.nextToken());
// ignore the rest of the line since its the date
currentLine = reader.readLine();
String message = readMessage();
Throwable exception = readException(message);
if (currentLine == null || getLineType() != SUBENTRY) {
return new Status(severity, pluginID, code, message, exception);
}
MultiStatus parent = new MultiStatus(pluginID, code, message, exception);
readSubEntries(parent);
return parent;
}
protected void readSubEntries(MultiStatus parent) throws IOException {
while (getLineType() == SUBENTRY) {
StringTokenizer tokens = new StringTokenizer(currentLine);
// skip over the subentry keyword
tokens.nextToken();
int currentDepth = Integer.parseInt(tokens.nextToken());
String pluginID = tokens.nextToken();
int severity = Integer.parseInt(tokens.nextToken());
int code = Integer.parseInt(tokens.nextToken());
// ignore the rest of the line since its the date
currentLine = reader.readLine();
String message = readMessage();
Throwable exception = readException(message);
IStatus current = new Status(severity, pluginID, code, message, exception);
if (currentLine == null || getLineType() != SUBENTRY) {
parent.add(current);
return;
}
tokens = new StringTokenizer(currentLine);
tokens.nextToken();
int depth = Integer.parseInt(tokens.nextToken());
if (currentDepth == depth) {
// next sub-entry is a sibling
parent.add(current);
} else if (currentDepth == (depth - 1)) {
// next sub-entry is a child
current = new MultiStatus(pluginID, code, message, exception);
readSubEntries((MultiStatus) current);
parent.add(current);
} else {
parent.add(current);
return;
}
}
}
protected int readDepth() {
StringTokenizer tokens = new StringTokenizer(currentLine);
// skip the keyword
tokens.nextToken();
return Integer.parseInt(tokens.nextToken());
}
protected String readMessage() throws IOException {
if (currentLine == null || getLineType() != MESSAGE) {
return "";
}
StringBuilder buffer = new StringBuilder();
buffer.append(currentLine.substring(KEYWORD_MESSAGE.length() + 1));
currentLine = reader.readLine();
buffer.append(readText());
return buffer.toString();
}
protected String readSession() throws IOException {
if (currentLine == null || getLineType() != SESSION) {
return "";
}
currentLine = reader.readLine();
return readText();
}
protected String readText() throws IOException {
StringBuilder buffer = new StringBuilder();
if (currentLine == null || getLineType() != UNKNOWN) {
return "";
}
buffer.append(currentLine);
boolean done = false;
while (!done) {
currentLine = reader.readLine();
if (currentLine == null) {
done = true;
continue;
}
if (getLineType() == UNKNOWN) {
// preserves line terminators between lines
buffer.append('\n');
buffer.append(currentLine);
} else {
done = true;
}
}
return buffer.toString();
}
}