package org.checkerframework.framework.source;
/*>>>
import org.checkerframework.checker.compilermsgs.qual.CompilerMessageKey;
import org.checkerframework.checker.nullness.qual.*;
*/
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import org.checkerframework.dataflow.qual.Pure;
import org.checkerframework.dataflow.qual.SideEffectFree;
import org.checkerframework.dataflow.util.HashCodeUtils;
/**
* Represents the outcome of a type-checking operation (success, warning, or failure, plus a list of
* explanatory messages). {@link Result}s created during type-checking can be reported using {@link
* SourceChecker#report}, which ultimately delivers an error or warning message via the JSR 199
* compiler interface.
*
* @see SourceChecker#report
*/
public final class Result {
private static enum Type {
SUCCESS,
FAILURE,
WARNING;
/** @return whichever of the given types is most serious */
public static final Type merge(Type a, Type b) {
if (a == FAILURE || b == FAILURE) {
return FAILURE;
} else if (a == WARNING || b == WARNING) {
return WARNING;
} else {
return SUCCESS;
}
}
}
/** The type of result (success, warning, failure). */
private final Type type;
/** The messages for the results */
private final List<DiagMessage> messages;
/** The success result. */
public static final Result SUCCESS = new Result(Type.SUCCESS, null);
/**
* Creates a new failure result with the given message key.
*
* @param messageKey the key representing the reason for failure
* @param args optional arguments to be included in the message
* @return the failure result
*/
public static Result failure(
/*@CompilerMessageKey*/ String messageKey, /*@Nullable*/ Object... args) {
return new Result(Type.FAILURE, Collections.singleton(new DiagMessage(messageKey, args)));
}
/**
* Creates a new warning result with the given message.
*
* @param messageKey the key for the warning message
* @param args optional arguments to be included in the message
* @return the warning result
*/
public static Result warning(
/*@CompilerMessageKey*/ String messageKey, /*@Nullable*/ Object... args) {
return new Result(Type.WARNING, Collections.singleton(new DiagMessage(messageKey, args)));
}
private Result(Type type, Collection<DiagMessage> messagePairs) {
this.type = type;
this.messages = new LinkedList<DiagMessage>();
if (messagePairs != null) {
for (DiagMessage msg : messagePairs) {
String message = msg.getMessageKey();
/*@Nullable*/ Object[] args = msg.getArgs();
if (args != null) {
args = Arrays.copyOf(msg.getArgs(), args.length);
}
this.messages.add(new DiagMessage(message, args));
}
}
}
/**
* Merges two results into one.
*
* @param r the result to merge with this result
* @return a result that is the success result if both this and {@code r} are success results,
* or a result that has the more significant type (failure > warning > success) and
* the message keys of both this result and {@code r}
*/
public Result merge(Result r) {
if (r == null) {
return this;
}
if (r.isSuccess() && this.isSuccess()) {
return SUCCESS;
}
List<DiagMessage> messages = new LinkedList<DiagMessage>();
messages.addAll(this.messages);
messages.addAll(r.messages);
return new Result(Type.merge(r.type, this.type), messages);
}
/** @return true if the result is success (not a failure or warning) */
public boolean isSuccess() {
return type == Type.SUCCESS;
}
/** @return true if the result is a failure */
public boolean isFailure() {
return type == Type.FAILURE;
}
/** @return true if the result is a warning */
public boolean isWarning() {
return type == Type.WARNING;
}
/** @return the message keys associated with the result */
public List<String> getMessageKeys() {
List<String> msgKeys = new LinkedList<String>();
for (DiagMessage msg : getDiagMessages()) {
msgKeys.add(msg.getMessageKey());
}
return Collections.</*@NonNull*/ String>unmodifiableList(msgKeys);
}
/** @return an unmodifiable list of the message pairs */
public List<DiagMessage> getDiagMessages() {
return Collections.</*@NonNull*/ DiagMessage>unmodifiableList(messages);
}
@SideEffectFree
@Override
public String toString() {
switch (type) {
case FAILURE:
return "FAILURE: " + messages;
case WARNING:
return "WARNING: " + messages;
case SUCCESS:
default:
return "SUCCESS";
}
}
/**
* A class that represents diagnostic messages.
*
* <p>{@code DiagMessage} encapsulate the message key which would identify the relevant standard
* error message according to the user locale.
*
* <p>The optional arguments are possible custom strings for the error message.
*/
public static class DiagMessage {
private final /*@CompilerMessageKey*/ String message;
private Object[] args;
protected DiagMessage(/*@CompilerMessageKey*/ String message, Object[] args) {
this.message = message;
if (args == null) {
this.args = new Object[0]; /*null->nn*/
} else {
this.args = Arrays.copyOf(args, args.length);
}
}
/** @return the message key of this DiagMessage */
public /*@CompilerMessageKey*/ String getMessageKey() {
return this.message;
}
/** @return the customized optional arguments for the message */
public Object[] getArgs() {
return this.args;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof DiagMessage)) {
return false;
}
DiagMessage other = (DiagMessage) obj;
return (message.equals(other.message) && Arrays.equals(args, other.args));
}
@Pure
@Override
public int hashCode() {
return HashCodeUtils.hash(this.message, this.args);
}
@SideEffectFree
@Override
public String toString() {
if (args.length == 0) {
return message;
}
return message + " : " + Arrays.toString(args);
}
}
}