/**
* Copyright (C) 2001-2017 by RapidMiner and the contributors
*
* Complete list of developers available at our web site:
*
* http://rapidminer.com
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU Affero General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see http://www.gnu.org/licenses/.
*/
package com.rapidminer.operator;
import java.text.MessageFormat;
import java.util.ResourceBundle;
import com.rapidminer.NoBugError;
import com.rapidminer.tools.I18N;
import com.rapidminer.tools.Tools;
/**
* Exception class whose instances are thrown due to a user error, for example missing files or
* wrong operator architecture. <br>
* In order to create a UserError, do the following:
* <ul>
* <li>Open the file <code>UserErrorMessages.properties</code> in the <code>resources</code>
* directory. Look for an appropriate message. If you find one, remember its id. If not, create a
* new one in the correct group</li>
* <li>The entry must include name, short message and long message. The name and long message will
* be presented to the user literally. The short message will be parsed by
* <code>java.text.MessageFormat</code>. Especially, any occurrence of curly brackets will be
* replaced. Be careful with quotes; it might be a good idea to read the documentation of
* MessageFormat first.</li>
* <li>Create a UserError by using this id. If the UserError is created because of another
* exception, e.g. a FileNotFoundException, this exception should be passed to the UserError in the
* constructor.</li>
* </ul>
* <b>Attention!</b><br>
* Although the current UserErrorMessages.properties only contain numbers, the current
* implementation supports arbitrary strings as identifiers. Writers of Extensions are encouraged to
* use Strings for separating their self defined errors from the errors defined in the core. You
* should prepend the namespace of the extension like this: error.<extensions
* namespace>.error_id.short = ...
*
* @author Simon Fischer, Ingo Mierswa, Sebastian Land
*/
public class UserError extends OperatorException implements NoBugError {
private static final long serialVersionUID = -8441036860570180869L;
private static ResourceBundle messages = I18N.getUserErrorMessagesBundle();
private static final MessageFormat formatter = new MessageFormat("");
private String errorIdentifier = null;
private final int code;
private transient Operator operator;
private final Object[] arguments;
/**
* Creates a new UserError.
*
* @param operator
* The {@link Operator} in which the exception occured.
* @param cause
* The exception that caused the user error. May be null. Using this makes debugging
* a lot easier.
* @param code
* The error code referring to a message in the file
* <code>UserErrorMessages.properties</code>
* @param arguments
* Arguments for the short or long message.
*/
public UserError(Operator operator, Throwable cause, int code, Object... arguments) {
super(getErrorMessage(code, arguments), cause);
this.code = code;
this.operator = operator;
this.arguments = arguments;
}
/** Convenience constructor for messages with no arguments and cause. */
public UserError(Operator operator, Throwable cause, int code) {
this(operator, code, new Object[0], cause);
}
public UserError(Operator operator, int code, Object... arguments) {
this(operator, null, code, arguments);
}
/** Convenience constructor for messages with no arguments. */
public UserError(Operator operator, int code) {
this(operator, null, code, new Object[0]);
}
public UserError(Operator operator, Throwable cause, String errorId, Object... arguments) {
super(getErrorMessage(errorId, arguments), cause);
this.code = -1;
this.errorIdentifier = errorId;
this.operator = operator;
this.arguments = arguments;
}
/**
* Convenience constructor for messages with no arguments and cause. This constructor is in fact
* equivalent to the call of the above constructor but must kept for compatibility issues for
* existing compiled extensions.
*/
public UserError(Operator operator, Throwable cause, String errorId) {
this(operator, cause, errorId, new Object[0]);
}
public UserError(Operator operator, String errorId, Object... arguments) {
this(operator, null, errorId, arguments);
}
/** Convenience constructor for messages with no arguments. */
public UserError(Operator operator, String errorId) {
this(operator, null, errorId, new Object[0]);
}
@Override
public String getDetails() {
if (errorIdentifier == null) {
String details = getResourceString(code, "long", "Description missing.");
return addArguments(arguments, details);
} else {
// allow arguments for error details of new user errors
String message = getResourceString(errorIdentifier, "long", "Description missing.");
return addArguments(arguments, message);
}
}
@Override
public String getErrorName() {
if (errorIdentifier == null) {
return getResourceString(code, "name", "Unnamed error.");
} else {
return getResourceString(errorIdentifier, "name", "Unnamed error.");
}
}
@Override
public int getCode() {
return code;
}
/**
* Returns the ErrorIdentifier if the UserError was created with a constructor that specifies
* an error ID. Returns null if the UserError was created with a constructor that specifies an
* error code.
*/
public String getErrorIdentifier() {
return errorIdentifier;
}
public Operator getOperator() {
return operator;
}
public void setOperator(Operator operator) {
this.operator = operator;
}
public static String getErrorMessage(int code, Object[] arguments) {
String message = getResourceString(code, "short", "No message.");
return addArguments(arguments, message);
}
public static String getErrorMessage(String identifier, Object[] arguments) {
String message = getResourceString(identifier, "short", "No message.");
return addArguments(arguments, message);
}
/**
* Adds the arguments to the message.
*
* @param arguments
* @param message
* @return the message including the arguments or the message of the exception if one occurs
*/
private static String addArguments(Object[] arguments, String message) {
try {
formatter.applyPattern(message);
String formatted = formatter.format(arguments);
return formatted;
} catch (Throwable t) {
return message;
}
}
/**
* Returns a resource message for the given error code.
*
* @param key
* one out of "name", "short", "long"
*/
public static String getResourceString(int code, String key, String deflt) {
return getResourceString(code + "", key, deflt);
}
/**
* This returns a resource message of the internationalized error messages identified by an id.
* Compared to the legacy method {@link #getResourceString(int, String, String)} this supports a
* more detailed identifier. This makes it easier to ensure extensions don't reuse already
* defined core errors. It is common sense to add the extensions namespace identifier as second
* part of the key, just after error. For example: error.rmx_web.operator.unusable = This
* operator {0} is unusable.
*
* @param id
* The identifier of the error. "error." will be automatically prepended-
* @param key
* The part of the error description that should be shown.
* @param deflt
* The default if no resource bundle is available.
*/
public static String getResourceString(String id, String key, String deflt) {
if (messages == null) {
return deflt;
}
try {
return messages.getString("error." + id + "." + key);
} catch (java.util.MissingResourceException e) {
return deflt;
}
}
@Override
public String getHTMLMessage() {
// return "<html>Error in: <b>" + getOperator() + "</b><br>" + Tools.escapeXML(getMessage())
// + "<hr>" + Tools.escapeXML(getDetails()) +
// "</html>";
return "<html>Error in: <b>" + getOperator() + "</b><br>" + Tools.escapeXML(getMessage()) + "</html>";
}
}