/*
* Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Business Objects nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* CompilerMessage.java
* Created: July 4, 2002
* By: Bo Ilic (from Luke's original Compiler.CompilationError)
*/
package org.openquark.cal.compiler;
/**
* A class to represent messages occurring during compilation such as errors, fatal
* errors, warnings, and informational messages.
* In this context 'compilation' refers to the entire process of generating a CAL
* program from source. This would include such steps as generation of expression
* format and type checking (performed by the CALCompiler), machine specific code
* generation, java source/byte code generation, etc.
* Creation date: (July 4, 2002).
* @author Bo Ilic (from Luke's original Compiler.CompilationError)
*/
public final class CompilerMessage {
/** The source position associated with this message. Can be null. */
private final SourceRange sourceRange;
/** The entity associated with this message. Can be null. */
private final Identifier associatedEntity;
/** The message kind associated with this message. Cannot be null. */
private final MessageKind messageKind;
/** The exception associated with this message. Can be null. */
private final Exception exception;
/**
* Warning- this class should only be used by the CAL compiler implementation. It is not part of the external API of the CAL platform.
*
* This error is thrown and caught internally by the compiler as a way of signalling that compilation should abort.
* These may occur if too many errors are logged, or in the presence of certain fatal errors.
*
* This class is intended to be internal to the compiler.
*/
public static final class AbortCompilation extends Error {
private static final long serialVersionUID = -6078350930009279716L;
AbortCompilation() {
}
}
/**
* Type-safe enum pattern to represent the severity of compilation error messages.
* Creation date: (July 4, 2002).
* @author Bo Ilic
*/
public static final class Severity implements Comparable<Severity> {
private final String description;
private final int level;
/** Purely informational, and not a problem of any sort. */
static final public Severity INFO = new Severity ("Info", 1);
static final public Severity WARNING = new Severity ("Warning", 2);
/**
* Normal level when reporting a user error in CAL source.
* Compilation can proceed, but should not be able to run the resulting program.
*/
static final public Severity ERROR = new Severity ("Error", 3);
/**
* Fatal errors should result in compilation immediately stopping.
* For example, this level is used for programming errors in the compiler.
*/
static final public Severity FATAL = new Severity ("Fatal Error", 4);
private Severity (String description_, int level_) {
description = description_;
level = level_;
}
@Override
public String toString() {
return description;
}
/** {@inheritDoc} */
public int compareTo(Severity o) {
int anotherLevel = o.level;
return level < anotherLevel ? -1 : (level == anotherLevel ? 0 : 1);
}
}
/**
* Represents the name of the CAL entity associated with a {@link CompilerMessage}. Having this in a compiler message
* helps the user in deciphering the message, especially when the message is the result of compiling a programmatically
* constructed source model (which has no source positions) or the result of loading a compiled module file.
*
* @author Joseph Wong
*/
public static final class Identifier {
/**
* The category of the name. e.g. top-level function, type cons, data cons...
*/
private final Category category;
/**
* The name of the entity associated with the message.
*/
/*
* @implementation
* This may need to be more general than QualifiedName as we associate other kinds of entities
* with messages, e.g. local functions, class instances.
*/
private final QualifiedName name;
/**
* An enumeration class containing constants for the different categories of entities that could be associated with
* a message.
*
* @author Joseph Wong
*/
public static final class Category {
/**
* A textual description of the status. For debug purposes.
*/
private final String description;
/**
* Private constructor for this enumeration class.
* @param description a textual description of the status. For debug purposes.
*/
private Category(final String description) {
if (description == null) {
throw new NullPointerException();
}
this.description = description;
}
/** Category constant for a data constructor. */
public static final Category
DATA_CONSTRUCTOR = new Category("DATA_CONSTRUCTOR");
/** Category constant for a type constructor. */
public static final Category
TYPE_CONSTRUCTOR = new Category("TYPE_CONSTRUCTOR");
/** Category constant for a type class. */
public static final Category
TYPE_CLASS = new Category("TYPE_CLASS");
/** Category constant for a top-level function. */
public static final Category
TOP_LEVEL_FUNCTION = new Category("TOP_LEVEL_FUNCTION");
/** Category constant for a class method. */
public static final Category
CLASS_METHOD = new Category("CLASS_METHOD");
/** {@inheritDoc} */
@Override
public String toString() {
return description;
}
}
/**
* Private constructor for this class. Instances should be constructed via the factory methods.
* @param category the category of the name.
* @param name the name of the entity associated with the message.
*/
private Identifier(final Category category, final QualifiedName name) {
if (category == null || name == null) {
throw new NullPointerException();
}
this.category = category;
this.name = name;
}
/**
* Factory method for constructing an instance representing a top-level function name.
* If the name is null, then null is returned.
* @param name the qualified name of the top-level function. Can be null.
* @return an instance of this class, or null if the specified name is null.
*/
static Identifier makeFunction(final QualifiedName name) {
if (name == null) {
return null;
} else {
return new Identifier(Category.TOP_LEVEL_FUNCTION, name);
}
}
/**
* Factory method for constructing an instance representing a type constructor name.
* If the name is null, then null is returned.
* @param name the qualified name of the type constructor. Can be null.
* @return an instance of this class, or null if the specified name is null.
*/
static Identifier makeTypeCons(final QualifiedName name) {
if (name == null) {
return null;
} else {
return new Identifier(Category.TYPE_CONSTRUCTOR, name);
}
}
/**
* Factory method for constructing an instance representing a data constructor name.
* If the name is null, then null is returned.
* @param name the qualified name of the data constructor. Can be null.
* @return an instance of this class, or null if the specified name is null.
*/
static Identifier makeDataCons(final QualifiedName name) {
if (name == null) {
return null;
} else {
return new Identifier(Category.DATA_CONSTRUCTOR, name);
}
}
/**
* Factory method for constructing an instance representing a type class name.
* If the name is null, then null is returned.
* @param name the qualified name of the type class. Can be null.
* @return an instance of this class, or null if the specified name is null.
*/
static Identifier makeTypeClass(final QualifiedName name) {
if (name == null) {
return null;
} else {
return new Identifier(Category.TYPE_CLASS, name);
}
}
/**
* Factory method for constructing an instance representing a class method name.
* If the name is null, then null is returned.
* @param name the qualified name of the class method. Can be null.
* @return an instance of this class, or null if the specified name is null.
*/
static Identifier makeClassMethod(final QualifiedName name) {
if (name == null) {
return null;
} else {
return new Identifier(Category.CLASS_METHOD, name);
}
}
/**
* @return the category of the name.
*/
public Category getCategory() {
return category;
}
/**
* @return the name of the entity associated with the message.
*/
public QualifiedName getName() {
return name;
}
/**
* @return the name of the module containing the entity associated with the message.
*/
public ModuleName getModuleName() {
return name.getModuleName();
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return "[Identifier: category=" + category + " name=" + name + "]";
}
}
/**
* Construct from severity, errorNode and message
* @param sourcePositionNode the parse tree node associated with the message, or null if there wasn't any.
*/
CompilerMessage(ParseTreeNode sourcePositionNode, MessageKind messageKind) {
this(sourcePositionNode == null ? null : sourcePositionNode.getAssemblySourceRange(), messageKind, null);
}
CompilerMessage(ParseTreeNode sourcePositionNode, MessageKind messageKind, Exception exception) {
this(sourcePositionNode == null ? null : sourcePositionNode.getAssemblySourceRange(), messageKind, exception);
}
public CompilerMessage(SourceRange sourceRange, MessageKind messageKind) {
this(sourceRange, messageKind, null);
}
/**
* Construct from severity and message
*/
public CompilerMessage(MessageKind messageKind_) {
this((SourceRange)null, messageKind_, null);
}
/**
* Construct from severity, message and exception
*/
public CompilerMessage(MessageKind messageKind_, Exception exception_) {
this((SourceRange)null, messageKind_, exception_);
}
/**
* Construct from source position, severity, message and exception
*/
CompilerMessage(SourceRange sourceRange_, MessageKind messageKind_, Exception exception_) {
this(sourceRange_, null, messageKind_, exception_);
}
/**
* Construct from source position, severity, message and associated entity
*/
CompilerMessage(SourceRange sourceRange_, Identifier associatedEntity, MessageKind messageKind_) {
this(sourceRange_, associatedEntity, messageKind_, null);
}
/**
* Construct from all compiler message parameters
*/
CompilerMessage(SourceRange sourceRange_, Identifier associatedEntity, MessageKind messageKind_, Exception exception_) {
if (sourceRange_ == null && associatedEntity != null) {
throw new IllegalArgumentException("If there is an associated entity, there should be a source range as well.");
}
this.associatedEntity = associatedEntity;
messageKind = messageKind_;
exception = exception_;
sourceRange = sourceRange_;
}
/**
* Copy the given compiler message and replace the source range with the
* given source range.
* @param sourceRange the new value for the source range
* @return a new CompilerMessage the same as the old except the source range is updated.
*/
CompilerMessage copy(SourceRange sourceRange){
return new CompilerMessage(sourceRange, associatedEntity, messageKind, exception);
}
/**
* @return the CAL source position with which the message is associated.
*/
public SourceRange getSourceRange() {
return sourceRange;
}
/**
* @return the severity of the compiler message
*/
public Severity getSeverity() {
return messageKind.getSeverity();
}
/**
* Get message
* Creation date: (2/2/01 5:59:03 PM)
* @return the summary message of the error
*/
public String getMessage() {
return messageKind.getMessage();
}
/**
* Get messageKind
* @return MessageKind the message kind object for the error
*/
public MessageKind getMessageKind() {
return messageKind;
}
/**
* Get underlying exception
* Creation date: (2/2/01 5:59:03 PM)
* @return Exception the underlying exception which caused the error
*/
public Exception getException() {
return exception;
}
/**
* Render the message as a string
*/
@Override
public String toString() {
String returnStr = messageKind.getMessage();
// We display the name of the associated entity, if there is one
if (associatedEntity != null) {
Identifier.Category category = associatedEntity.getCategory();
final String messageTemplateName;
if (category == Identifier.Category.TOP_LEVEL_FUNCTION) {
messageTemplateName = "AssociatedWithFunction";
} else if (category == Identifier.Category.TYPE_CONSTRUCTOR) {
messageTemplateName = "AssociatedWithTypeCons";
} else if (category == Identifier.Category.DATA_CONSTRUCTOR) {
messageTemplateName = "AssociatedWithDataCons";
} else if (category == Identifier.Category.TYPE_CLASS) {
messageTemplateName = "AssociatedWithTypeClass";
} else if (category == Identifier.Category.CLASS_METHOD) {
messageTemplateName = "AssociatedWithClassMethod";
} else {
messageTemplateName = "AssociatedWithGeneral";
}
final QualifiedName name = associatedEntity.getName();
final String displayName;
// we optimize the display of the associated entity: if it's defined in the same module
// as reported by the source position, then we display only the unqualified name of the entity
if (sourceRange != null && sourceRange.getSourceName().equals(name.getModuleName().toSourceText())) {
displayName = name.getUnqualifiedName();
} else {
displayName = name.getQualifiedName();
}
returnStr = CALMessages.getString(messageTemplateName, displayName, returnStr);
}
if(sourceRange != null) {
String sourceName = sourceRange.getSourceName();
String sourcePositionStr = null;
int line = sourceRange.getStartLine();
int column = sourceRange.getStartColumn();
if (line > 0 && column > 0) {
sourcePositionStr = CALMessages.getString("SourcePositionFormat", Integer.valueOf(line), Integer.valueOf(column));
} else if (line > 0) {
sourcePositionStr = CALMessages.getString("SourcePositionFormatNoColumn", Integer.valueOf(line));
}
if(sourceName != null && sourcePositionStr != null) {
sourcePositionStr = CALMessages.getString("MessageFormat", sourceName, sourcePositionStr);
returnStr = CALMessages.getString("MessageFormat", sourcePositionStr, returnStr);
} else if (sourceName == null && sourcePositionStr != null) {
returnStr = CALMessages.getString("MessageFormat", sourcePositionStr, returnStr);
} else if (sourceName != null && sourcePositionStr == null) {
returnStr = CALMessages.getString("MessageFormat", sourceName, returnStr);
}
}
returnStr = CALMessages.getString("MessageFormat", getSeverity().toString(), returnStr);
// Add information about the exception if present
if (exception != null) {
// Caused by
if (exception instanceof TypeException) {
//todoBI our own internal exception classes to do not need to display their names since the resulting
//error message is intended to be a "user readable" string. There are others except TypeException...
returnStr = CALMessages.getString("MessageWithTypeException", returnStr, exception.getLocalizedMessage());
} else {
returnStr = CALMessages.getString("MessageWithOtherException", new Object[] {returnStr, exception.getClass().getName(), exception.getLocalizedMessage()});
}
}
return returnStr;
}
}