/*
* Copyright 2004-2013 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.lealone.message;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.sql.SQLException;
import java.text.MessageFormat;
import java.util.Locale;
import java.util.Properties;
import java.util.Map.Entry;
import org.lealone.constant.ErrorCode;
import org.lealone.jdbc.JdbcSQLException;
import org.lealone.util.SortedProperties;
import org.lealone.util.StringUtils;
import org.lealone.util.Utils;
/**
* This exception wraps a checked exception.
* It is used in methods where checked exceptions are not supported,
* for example in a Comparator.
*/
public class DbException extends RuntimeException {
private static final long serialVersionUID = 1L;
private static final Properties MESSAGES = new Properties();
private Object source;
static {
try {
byte[] messages = Utils.getResource("/org/lealone/res/_messages_en.prop");
if (messages != null) {
MESSAGES.load(new ByteArrayInputStream(messages));
}
String language = Locale.getDefault().getLanguage();
if (!"en".equals(language)) {
byte[] translations = Utils.getResource("/org/lealone/res/_messages_" + language + ".prop");
// message: translated message + english
// (otherwise certain applications don't work)
if (translations != null) {
Properties p = SortedProperties.fromLines(new String(translations, "UTF-8"));
for (Entry<Object, Object> e : p.entrySet()) {
String key = (String) e.getKey();
String translation = (String) e.getValue();
if (translation != null && !translation.startsWith("#")) {
String original = MESSAGES.getProperty(key);
String message = translation + "\n" + original;
MESSAGES.put(key, message);
}
}
}
}
} catch (IOException e) {
TraceSystem.traceThrowable(e);
}
}
private DbException(SQLException e) {
super(e.getMessage(), e);
}
private static String translate(String key, String... params) {
String message = null;
if (MESSAGES != null) {
// Tomcat sets final static fields to null sometimes
message = MESSAGES.getProperty(key);
}
if (message == null) {
message = "(Message " + key + " not found)";
}
if (params != null) {
for (int i = 0; i < params.length; i++) {
String s = params[i];
if (s != null && s.length() > 0) {
params[i] = StringUtils.quoteIdentifier(s);
}
}
message = MessageFormat.format(message, (Object[]) params);
}
return message;
}
/**
* Get the SQLException object.
*
* @return the exception
*/
public SQLException getSQLException() {
return (SQLException) getCause();
}
/**
* Get the error code.
*
* @return the error code
*/
public int getErrorCode() {
return getSQLException().getErrorCode();
}
/**
* Set the SQL statement of the given exception.
* This method may create a new object.
*
* @param sql the SQL statement
* @return the exception
*/
public DbException addSQL(String sql) {
SQLException e = getSQLException();
if (e instanceof JdbcSQLException) {
JdbcSQLException j = (JdbcSQLException) e;
if (j.getSQL() == null) {
j.setSQL(sql);
}
return this;
}
e = new JdbcSQLException(e.getMessage(), sql, e.getSQLState(), e.getErrorCode(), e, null);
return new DbException(e);
}
/**
* Create a database exception for a specific error code.
*
* @param errorCode the error code
* @return the exception
*/
public static DbException get(int errorCode) {
return get(errorCode, (String) null);
}
/**
* Create a database exception for a specific error code.
*
* @param errorCode the error code
* @param p1 the first parameter of the message
* @return the exception
*/
public static DbException get(int errorCode, String p1) {
return get(errorCode, new String[] { p1 });
}
/**
* Create a database exception for a specific error code.
*
* @param errorCode the error code
* @param cause the cause of the exception
* @param params the list of parameters of the message
* @return the exception
*/
public static DbException get(int errorCode, Throwable cause, String... params) {
return new DbException(getJdbcSQLException(errorCode, cause, params));
}
/**
* Create a database exception for a specific error code.
*
* @param errorCode the error code
* @param params the list of parameters of the message
* @return the exception
*/
public static DbException get(int errorCode, String... params) {
return new DbException(getJdbcSQLException(errorCode, null, params));
}
/**
* Create a syntax error exception.
*
* @param sql the SQL statement
* @param index the position of the error in the SQL statement
* @return the exception
*/
public static DbException getSyntaxError(String sql, int index) {
sql = StringUtils.addAsterisk(sql, index);
return get(ErrorCode.SYNTAX_ERROR_1, sql);
}
/**
* Create a syntax error exception.
*
* @param sql the SQL statement
* @param index the position of the error in the SQL statement
* @param expected the expected keyword at the given position
* @return the exception
*/
public static DbException getSyntaxError(String sql, int index, String expected) {
sql = StringUtils.addAsterisk(sql, index);
return get(ErrorCode.SYNTAX_ERROR_2, sql, expected);
}
/**
* Gets a SQL exception meaning this feature is not supported.
*
* @param message what exactly is not supported
* @return the exception
*/
public static DbException getUnsupportedException(String message) {
return get(ErrorCode.FEATURE_NOT_SUPPORTED_1, message);
}
/**
* Gets a SQL exception meaning this value is invalid.
*
* @param param the name of the parameter
* @param value the value passed
* @return the IllegalArgumentException object
*/
public static DbException getInvalidValueException(String param, Object value) {
return get(ErrorCode.INVALID_VALUE_2, value == null ? "null" : value.toString(), param);
}
/**
* Throw an internal error. This method seems to return an exception object,
* so that it can be used instead of 'return', but in fact it always throws
* the exception.
*
* @param s the message
* @return the RuntimeException object
* @throws RuntimeException the exception
*/
public static RuntimeException throwInternalError(String s) {
RuntimeException e = new RuntimeException(s);
TraceSystem.traceThrowable(e);
throw e;
}
/**
* Throw an internal error. This method seems to return an exception object,
* so that it can be used instead of 'return', but in fact it always throws
* the exception.
*
* @return the RuntimeException object
*/
public static RuntimeException throwInternalError() {
return throwInternalError("Unexpected code path");
}
/**
* Convert an exception to a SQL exception using the default mapping.
*
* @param e the root cause
* @return the SQL exception object
*/
public static SQLException toSQLException(Exception e) {
if (e instanceof SQLException) {
return (SQLException) e;
}
return convert(e).getSQLException();
}
/**
* Convert a throwable to an SQL exception using the default mapping. All
* errors except the following are re-thrown: StackOverflowError,
* LinkageError.
*
* @param e the root cause
* @return the exception object
*/
public static DbException convert(Throwable e) {
if (e instanceof DbException) {
return (DbException) e;
} else if (e instanceof SQLException) {
return new DbException((SQLException) e);
} else if (e instanceof InvocationTargetException) {
return convertInvocation((InvocationTargetException) e, null);
} else if (e instanceof IOException) {
return get(ErrorCode.IO_EXCEPTION_1, e, e.toString());
} else if (e instanceof OutOfMemoryError) {
return get(ErrorCode.OUT_OF_MEMORY, e);
} else if (e instanceof StackOverflowError || e instanceof LinkageError) {
return get(ErrorCode.GENERAL_ERROR_1, e, e.toString());
} else if (e instanceof Error) {
throw (Error) e;
}
return get(ErrorCode.GENERAL_ERROR_1, e, e.toString());
}
/**
* Convert an InvocationTarget exception to a database exception.
*
* @param te the root cause
* @param message the added message or null
* @return the database exception object
*/
public static DbException convertInvocation(InvocationTargetException te, String message) {
Throwable t = te.getTargetException();
if (t instanceof SQLException || t instanceof DbException) {
return convert(t);
}
message = message == null ? t.getMessage() : message + ": " + t.getMessage();
return get(ErrorCode.EXCEPTION_IN_FUNCTION_1, t, message);
}
/**
* Convert an IO exception to a database exception.
*
* @param e the root cause
* @param message the message or null
* @return the database exception object
*/
public static DbException convertIOException(IOException e, String message) {
if (message == null) {
Throwable t = e.getCause();
if (t instanceof DbException) {
return (DbException) t;
}
return get(ErrorCode.IO_EXCEPTION_1, e, e.toString());
}
return get(ErrorCode.IO_EXCEPTION_2, e, e.toString(), message);
}
/**
* Gets the SQL exception object for a specific error code.
*
* @param errorCode the error code
* @param cause the cause of the exception
* @param params the list of parameters of the message
* @return the SQLException object
*/
private static JdbcSQLException getJdbcSQLException(int errorCode, Throwable cause, String... params) {
String sqlstate = ErrorCode.getState(errorCode);
String message = translate(sqlstate, params);
return new JdbcSQLException(message, null, sqlstate, errorCode, cause, null);
}
/**
* Convert an exception to an IO exception.
*
* @param e the root cause
* @return the IO exception
*/
public static IOException convertToIOException(Throwable e) {
if (e instanceof IOException) {
return (IOException) e;
}
if (e instanceof JdbcSQLException) {
JdbcSQLException e2 = (JdbcSQLException) e;
if (e2.getOriginalCause() != null) {
e = e2.getOriginalCause();
}
}
IOException io = new IOException(e.toString());
io.initCause(e);
return io;
}
public Object getSource() {
return source;
}
public void setSource(Object source) {
this.source = source;
}
}