mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-16 14:23:28 +07:00
IDEA-126173 Specify exception type thrown by @NotNull annotations
This commit is contained in:
@@ -15,11 +15,11 @@
|
|||||||
*/
|
*/
|
||||||
package com.intellij.compiler.notNullVerification;
|
package com.intellij.compiler.notNullVerification;
|
||||||
|
|
||||||
|
import com.sun.istack.internal.NotNull;
|
||||||
|
import com.sun.istack.internal.Nullable;
|
||||||
import org.jetbrains.org.objectweb.asm.*;
|
import org.jetbrains.org.objectweb.asm.*;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -100,6 +100,15 @@ public class NotNullVerifyingInstrumenter extends ClassVisitor implements Opcode
|
|||||||
myClassName = name;
|
myClassName = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class NotNullState {
|
||||||
|
@Nullable String message;
|
||||||
|
@NotNull String exceptionType;
|
||||||
|
|
||||||
|
NotNullState(String exceptionType) {
|
||||||
|
this.exceptionType = exceptionType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MethodVisitor visitMethod(final int access, final String name, String desc, String signature, String[] exceptions) {
|
public MethodVisitor visitMethod(final int access, final String name, String desc, String signature, String[] exceptions) {
|
||||||
final Type[] args = Type.getArgumentTypes(desc);
|
final Type[] args = Type.getArgumentTypes(desc);
|
||||||
@@ -107,29 +116,32 @@ public class NotNullVerifyingInstrumenter extends ClassVisitor implements Opcode
|
|||||||
final MethodVisitor v = cv.visitMethod(access, name, desc, signature, exceptions);
|
final MethodVisitor v = cv.visitMethod(access, name, desc, signature, exceptions);
|
||||||
final Map<Integer, String> paramNames = myMethodParamNames.get(myClassName + '.' + name + desc);
|
final Map<Integer, String> paramNames = myMethodParamNames.get(myClassName + '.' + name + desc);
|
||||||
return new MethodVisitor(Opcodes.ASM5, v) {
|
return new MethodVisitor(Opcodes.ASM5, v) {
|
||||||
|
private final Map<Integer, NotNullState> myNotNullParams = new LinkedHashMap<Integer, NotNullState>();
|
||||||
private final List<Integer> myNotNullParams = new ArrayList<Integer>();
|
|
||||||
private int mySyntheticCount = 0;
|
private int mySyntheticCount = 0;
|
||||||
private boolean myIsNotNull = false;
|
private NotNullState myMethodNotNull;
|
||||||
private String myMessage = null;
|
|
||||||
private Label myStartGeneratedCodeLabel;
|
private Label myStartGeneratedCodeLabel;
|
||||||
|
|
||||||
|
private AnnotationVisitor collectNotNullArgs(AnnotationVisitor base, final NotNullState state) {
|
||||||
|
return new AnnotationVisitor(Opcodes.ASM5, base) {
|
||||||
|
@Override
|
||||||
|
public void visit(String methodName, Object o) {
|
||||||
|
if (ANNOTATION_DEFAULT_METHOD.equals(methodName) && !((String) o).isEmpty()) {
|
||||||
|
state.message = (String) o;
|
||||||
|
}
|
||||||
|
else if ("exception".equals(methodName) && o instanceof Type && !((Type)o).getClassName().equals(Exception.class.getName())) {
|
||||||
|
state.exceptionType = ((Type)o).getInternalName();
|
||||||
|
}
|
||||||
|
super.visit(methodName, o);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public AnnotationVisitor visitParameterAnnotation(final int parameter, final String anno, final boolean visible) {
|
public AnnotationVisitor visitParameterAnnotation(final int parameter, final String anno, final boolean visible) {
|
||||||
AnnotationVisitor av = mv.visitParameterAnnotation(parameter, anno, visible);
|
AnnotationVisitor av = mv.visitParameterAnnotation(parameter, anno, visible);
|
||||||
if (isReferenceType(args[parameter]) && anno.equals(NOT_NULL_TYPE)) {
|
if (isReferenceType(args[parameter]) && anno.equals(NOT_NULL_TYPE)) {
|
||||||
myNotNullParams.add(new Integer(parameter));
|
NotNullState state = new NotNullState(IAE_CLASS_NAME);
|
||||||
av = new AnnotationVisitor(Opcodes.ASM5, av) {
|
myNotNullParams.put(new Integer(parameter), state);
|
||||||
@Override
|
av = collectNotNullArgs(av, state);
|
||||||
public void visit(String methodName, Object o) {
|
|
||||||
if(ANNOTATION_DEFAULT_METHOD.equals(methodName)) {
|
|
||||||
String message = (String) o;
|
|
||||||
if(!message.isEmpty()) {
|
|
||||||
myMessage = message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
super.visit(methodName, o);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
else if (anno.equals(SYNTHETIC_TYPE)) {
|
else if (anno.equals(SYNTHETIC_TYPE)) {
|
||||||
// see http://forge.ow2.org/tracker/?aid=307392&group_id=23&atid=100023&func=detail
|
// see http://forge.ow2.org/tracker/?aid=307392&group_id=23&atid=100023&func=detail
|
||||||
@@ -143,19 +155,8 @@ public class NotNullVerifyingInstrumenter extends ClassVisitor implements Opcode
|
|||||||
public AnnotationVisitor visitAnnotation(String anno, boolean isRuntime) {
|
public AnnotationVisitor visitAnnotation(String anno, boolean isRuntime) {
|
||||||
AnnotationVisitor av = mv.visitAnnotation(anno, isRuntime);
|
AnnotationVisitor av = mv.visitAnnotation(anno, isRuntime);
|
||||||
if (isReferenceType(returnType) && anno.equals(NOT_NULL_TYPE)) {
|
if (isReferenceType(returnType) && anno.equals(NOT_NULL_TYPE)) {
|
||||||
myIsNotNull = true;
|
myMethodNotNull = new NotNullState(ISE_CLASS_NAME);
|
||||||
av = new AnnotationVisitor(Opcodes.ASM5, av) {
|
av = collectNotNullArgs(av, myMethodNotNull);
|
||||||
@Override
|
|
||||||
public void visit(String methodName, Object o) {
|
|
||||||
if(ANNOTATION_DEFAULT_METHOD.equals(methodName)) {
|
|
||||||
String message = (String) o;
|
|
||||||
if(!message.isEmpty()) {
|
|
||||||
myMessage = message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
super.visit(methodName, o);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return av;
|
return av;
|
||||||
@@ -167,7 +168,8 @@ public class NotNullVerifyingInstrumenter extends ClassVisitor implements Opcode
|
|||||||
myStartGeneratedCodeLabel = new Label();
|
myStartGeneratedCodeLabel = new Label();
|
||||||
mv.visitLabel(myStartGeneratedCodeLabel);
|
mv.visitLabel(myStartGeneratedCodeLabel);
|
||||||
}
|
}
|
||||||
for (Integer param : myNotNullParams) {
|
for (Map.Entry<Integer, NotNullState> entry : myNotNullParams.entrySet()) {
|
||||||
|
Integer param = entry.getKey();
|
||||||
int var = ((access & ACC_STATIC) == 0) ? 1 : 0;
|
int var = ((access & ACC_STATIC) == 0) ? 1 : 0;
|
||||||
for (int i = 0; i < param; ++i) {
|
for (int i = 0; i < param; ++i) {
|
||||||
var += args[i].getSize();
|
var += args[i].getSize();
|
||||||
@@ -177,14 +179,15 @@ public class NotNullVerifyingInstrumenter extends ClassVisitor implements Opcode
|
|||||||
Label end = new Label();
|
Label end = new Label();
|
||||||
mv.visitJumpInsn(IFNONNULL, end);
|
mv.visitJumpInsn(IFNONNULL, end);
|
||||||
|
|
||||||
|
NotNullState state = entry.getValue();
|
||||||
String paramName = paramNames == null ? null : paramNames.get(param);
|
String paramName = paramNames == null ? null : paramNames.get(param);
|
||||||
String descrPattern = myMessage != null
|
String descrPattern = state.message != null
|
||||||
? myMessage
|
? state.message
|
||||||
: paramName != null ? NULL_ARG_MESSAGE_NAMED : NULL_ARG_MESSAGE_INDEXED;
|
: paramName != null ? NULL_ARG_MESSAGE_NAMED : NULL_ARG_MESSAGE_INDEXED;
|
||||||
String[] args = myMessage != null
|
String[] args = state.message != null
|
||||||
? EMPTY_STRING_ARRAY
|
? EMPTY_STRING_ARRAY
|
||||||
: new String[]{paramName != null ? paramName : String.valueOf(param - mySyntheticCount), myClassName, name};
|
: new String[]{paramName != null ? paramName : String.valueOf(param - mySyntheticCount), myClassName, name};
|
||||||
generateThrow(IAE_CLASS_NAME, end, descrPattern, args);
|
generateThrow(state.exceptionType, end, descrPattern, args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,13 +202,13 @@ public class NotNullVerifyingInstrumenter extends ClassVisitor implements Opcode
|
|||||||
@Override
|
@Override
|
||||||
public void visitInsn(int opcode) {
|
public void visitInsn(int opcode) {
|
||||||
if (opcode == ARETURN) {
|
if (opcode == ARETURN) {
|
||||||
if (myIsNotNull) {
|
if (myMethodNotNull != null) {
|
||||||
mv.visitInsn(DUP);
|
mv.visitInsn(DUP);
|
||||||
final Label skipLabel = new Label();
|
final Label skipLabel = new Label();
|
||||||
mv.visitJumpInsn(IFNONNULL, skipLabel);
|
mv.visitJumpInsn(IFNONNULL, skipLabel);
|
||||||
String descrPattern = myMessage != null ? myMessage : NULL_RESULT_MESSAGE;
|
String descrPattern = myMethodNotNull.message != null ? myMethodNotNull.message : NULL_RESULT_MESSAGE;
|
||||||
String[] args = myMessage != null ? EMPTY_STRING_ARRAY : new String[]{myClassName, name};
|
String[] args = myMethodNotNull.message != null ? EMPTY_STRING_ARRAY : new String[]{myClassName, name};
|
||||||
generateThrow(ISE_CLASS_NAME, skipLabel, descrPattern, args);
|
generateThrow(myMethodNotNull.exceptionType, skipLabel, descrPattern, args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
package com.intellij.codeInspection.nullable;
|
package com.intellij.codeInspection.nullable;
|
||||||
|
|
||||||
import com.intellij.codeInsight.AnnotationUtil;
|
import com.intellij.codeInsight.AnnotationUtil;
|
||||||
import com.intellij.codeInsight.InferredAnnotationsManager;
|
|
||||||
import com.intellij.codeInsight.NullableNotNullManager;
|
import com.intellij.codeInsight.NullableNotNullManager;
|
||||||
import com.intellij.codeInsight.daemon.GroupNames;
|
import com.intellij.codeInsight.daemon.GroupNames;
|
||||||
import com.intellij.codeInsight.intention.AddAnnotationPsiFix;
|
import com.intellij.codeInsight.intention.AddAnnotationPsiFix;
|
||||||
@@ -204,6 +203,33 @@ public class NullableStuffInspectionBase extends BaseJavaBatchLocalInspectionToo
|
|||||||
if (!PsiUtil.isLanguageLevel5OrHigher(parameter)) return;
|
if (!PsiUtil.isLanguageLevel5OrHigher(parameter)) return;
|
||||||
check(parameter, holder, parameter.getType());
|
check(parameter, holder, parameter.getType());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitAnnotation(PsiAnnotation annotation) {
|
||||||
|
if (!AnnotationUtil.NOT_NULL.equals(annotation.getQualifiedName())) return;
|
||||||
|
|
||||||
|
PsiAnnotationMemberValue value = annotation.findDeclaredAttributeValue("exception");
|
||||||
|
if (value instanceof PsiClassObjectAccessExpression) {
|
||||||
|
PsiClass psiClass = PsiUtil.resolveClassInClassTypeOnly(((PsiClassObjectAccessExpression)value).getOperand().getType());
|
||||||
|
if (psiClass != null && !hasStringConstructor(psiClass)) {
|
||||||
|
holder.registerProblem(value,
|
||||||
|
"Custom exception class should have a constructor with a single message parameter of String type",
|
||||||
|
ProblemHighlightType.GENERIC_ERROR_OR_WARNING);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasStringConstructor(PsiClass aClass) {
|
||||||
|
for (PsiMethod method : aClass.getConstructors()) {
|
||||||
|
PsiParameterList list = method.getParameterList();
|
||||||
|
if (list.getParametersCount() == 1 &&
|
||||||
|
list.getParameters()[0].getType().equalsToText(CommonClassNames.JAVA_LANG_STRING)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,7 +258,7 @@ public class NullableStuffInspectionBase extends BaseJavaBatchLocalInspectionToo
|
|||||||
PsiAnnotation isDeclaredNotNull = AnnotationUtil.findAnnotation(parameter, manager.getNotNulls());
|
PsiAnnotation isDeclaredNotNull = AnnotationUtil.findAnnotation(parameter, manager.getNotNulls());
|
||||||
PsiAnnotation isDeclaredNullable = AnnotationUtil.findAnnotation(parameter, manager.getNullables());
|
PsiAnnotation isDeclaredNullable = AnnotationUtil.findAnnotation(parameter, manager.getNullables());
|
||||||
if (isDeclaredNullable != null && isDeclaredNotNull != null) {
|
if (isDeclaredNullable != null && isDeclaredNotNull != null) {
|
||||||
reportNullableNotNullConflict(holder, parameter, isDeclaredNullable, isDeclaredNotNull);
|
reportNullableNotNullConflict(holder, parameter, isDeclaredNullable, isDeclaredNotNull);
|
||||||
}
|
}
|
||||||
if ((isDeclaredNotNull != null || isDeclaredNullable != null) && type != null && TypeConversionUtil.isPrimitive(type.getCanonicalText())) {
|
if ((isDeclaredNotNull != null || isDeclaredNullable != null) && type != null && TypeConversionUtil.isPrimitive(type.getCanonicalText())) {
|
||||||
PsiAnnotation annotation = isDeclaredNotNull == null ? isDeclaredNullable : isDeclaredNotNull;
|
PsiAnnotation annotation = isDeclaredNotNull == null ? isDeclaredNullable : isDeclaredNotNull;
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public class CustomExceptionType {
|
||||||
|
public void foo(Object obj, @NotNull(exception = NullPointerException.class) Object obj2) { }
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
import org.jetbrains.annotations.*;
|
||||||
|
|
||||||
|
class Test {
|
||||||
|
public void foo(@NotNull(exception = NullPointerException.class) String a) { }
|
||||||
|
public void foo2(@NotNull(exception = <warning descr="Custom exception class should have a constructor with a single message parameter of String type">CustomException.class</warning>) String a) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
class CustomException extends Exception {}
|
||||||
@@ -30,6 +30,7 @@ public class NullableStuffInspectionTest extends LightCodeInsightFixtureTestCase
|
|||||||
public void testProblems2() throws Exception{ doTest(); }
|
public void testProblems2() throws Exception{ doTest(); }
|
||||||
public void testNullableFieldNotnullParam() throws Exception{ doTest(); }
|
public void testNullableFieldNotnullParam() throws Exception{ doTest(); }
|
||||||
public void testNotNullFieldNullableParam() throws Exception{ doTest(); }
|
public void testNotNullFieldNullableParam() throws Exception{ doTest(); }
|
||||||
|
public void testNotNullCustomException() throws Exception{ doTest(); }
|
||||||
|
|
||||||
public void testGetterSetterProblems() throws Exception{ doTest(); }
|
public void testGetterSetterProblems() throws Exception{ doTest(); }
|
||||||
public void testOverriddenMethods() throws Exception{
|
public void testOverriddenMethods() throws Exception{
|
||||||
|
|||||||
@@ -67,48 +67,48 @@ public class NotNullVerifyingInstrumenterTest extends UsefulTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void testSimpleReturn() throws Exception {
|
public void testSimpleReturn() throws Exception {
|
||||||
Class testClass = prepareTest();
|
Class<?> testClass = prepareTest();
|
||||||
Object instance = testClass.newInstance();
|
Object instance = testClass.newInstance();
|
||||||
Method method = testClass.getMethod("test");
|
Method method = testClass.getMethod("test");
|
||||||
verifyCallThrowsException("@NotNull method SimpleReturn.test must not return null", instance, method);
|
verifyCallThrowsException("@NotNull method SimpleReturn.test must not return null", instance, method);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testSimpleReturnWithMessage() throws Exception {
|
public void testSimpleReturnWithMessage() throws Exception {
|
||||||
Class testClass = prepareTest();
|
Class<?> testClass = prepareTest();
|
||||||
Object instance = testClass.newInstance();
|
Object instance = testClass.newInstance();
|
||||||
Method method = testClass.getMethod("test");
|
Method method = testClass.getMethod("test");
|
||||||
verifyCallThrowsException("This method cannot return null", instance, method);
|
verifyCallThrowsException("This method cannot return null", instance, method);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testMultipleReturns() throws Exception {
|
public void testMultipleReturns() throws Exception {
|
||||||
Class testClass = prepareTest();
|
Class<?> testClass = prepareTest();
|
||||||
Object instance = testClass.newInstance();
|
Object instance = testClass.newInstance();
|
||||||
Method method = testClass.getMethod("test", int.class);
|
Method method = testClass.getMethod("test", int.class);
|
||||||
verifyCallThrowsException("@NotNull method MultipleReturns.test must not return null", instance, method, 1);
|
verifyCallThrowsException("@NotNull method MultipleReturns.test must not return null", instance, method, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testSimpleParam() throws Exception {
|
public void testSimpleParam() throws Exception {
|
||||||
Class testClass = prepareTest();
|
Class<?> testClass = prepareTest();
|
||||||
Object instance = testClass.newInstance();
|
Object instance = testClass.newInstance();
|
||||||
Method method = testClass.getMethod("test", Object.class);
|
Method method = testClass.getMethod("test", Object.class);
|
||||||
verifyCallThrowsException("Argument 0 for @NotNull parameter of SimpleParam.test must not be null", instance, method, (Object)null);
|
verifyCallThrowsException("Argument 0 for @NotNull parameter of SimpleParam.test must not be null", instance, method, (Object)null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testSimpleParamWithMessage() throws Exception {
|
public void testSimpleParamWithMessage() throws Exception {
|
||||||
Class testClass = prepareTest();
|
Class<?> testClass = prepareTest();
|
||||||
Object instance = testClass.newInstance();
|
Object instance = testClass.newInstance();
|
||||||
Method method = testClass.getMethod("test", Object.class);
|
Method method = testClass.getMethod("test", Object.class);
|
||||||
verifyCallThrowsException("SimpleParamWithMessage.test(o) cant be null", instance, method, (Object)null);
|
verifyCallThrowsException("SimpleParamWithMessage.test(o) cant be null", instance, method, (Object)null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testConstructorParam() throws Exception {
|
public void testConstructorParam() throws Exception {
|
||||||
Class testClass = prepareTest();
|
Class<?> testClass = prepareTest();
|
||||||
Constructor method = testClass.getConstructor(Object.class);
|
Constructor method = testClass.getConstructor(Object.class);
|
||||||
verifyCallThrowsException("Argument 0 for @NotNull parameter of ConstructorParam.<init> must not be null", null, method, (Object)null);
|
verifyCallThrowsException("Argument 0 for @NotNull parameter of ConstructorParam.<init> must not be null", null, method, (Object)null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testConstructorParamWithMessage() throws Exception {
|
public void testConstructorParamWithMessage() throws Exception {
|
||||||
Class testClass = prepareTest();
|
Class<?> testClass = prepareTest();
|
||||||
Constructor method = testClass.getConstructor(Object.class);
|
Constructor method = testClass.getConstructor(Object.class);
|
||||||
verifyCallThrowsException("ConstructorParam.ConstructorParam.o cant be null", null, method, (Object)null);
|
verifyCallThrowsException("ConstructorParam.ConstructorParam.o cant be null", null, method, (Object)null);
|
||||||
}
|
}
|
||||||
@@ -132,6 +132,19 @@ public class NotNullVerifyingInstrumenterTest extends UsefulTestCase {
|
|||||||
assertNotNull(field);
|
assertNotNull(field);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testCustomExceptionType() throws Exception {
|
||||||
|
Class<?> testClass = prepareTest();
|
||||||
|
try {
|
||||||
|
testClass.getMethod("foo", Object.class, Object.class).invoke(testClass.newInstance(), null, null);
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
catch (InvocationTargetException e) {
|
||||||
|
//noinspection ThrowableResultOfMethodCallIgnored
|
||||||
|
assertInstanceOf(e.getCause(), NullPointerException.class);
|
||||||
|
assertEquals("Argument 1 for @NotNull parameter of CustomExceptionType.foo must not be null", e.getCause().getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void testEnumConstructorSecondParam() throws Exception {
|
public void testEnumConstructorSecondParam() throws Exception {
|
||||||
Class testClass = prepareTest();
|
Class testClass = prepareTest();
|
||||||
Object field = testClass.getField("Value");
|
Object field = testClass.getField("Value");
|
||||||
|
|||||||
Binary file not shown.
@@ -30,5 +30,17 @@ import java.lang.annotation.*;
|
|||||||
@Retention(RetentionPolicy.CLASS)
|
@Retention(RetentionPolicy.CLASS)
|
||||||
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE})
|
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE})
|
||||||
public @interface NotNull {
|
public @interface NotNull {
|
||||||
|
/**
|
||||||
|
* @return Custom exception message
|
||||||
|
*/
|
||||||
String value() default "";
|
String value() default "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Custom exception type that should be thrown when not-nullity contract is violated.
|
||||||
|
* The exception class should have a constructor with one String argument (message).
|
||||||
|
*
|
||||||
|
* By default, {@link java.lang.IllegalArgumentException} is thrown on null method arguments and
|
||||||
|
* {@link java.lang.IllegalStateException} — on null return value.
|
||||||
|
*/
|
||||||
|
Class<? extends Exception> exception() default Exception.class;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user