From 490d4e67bdb57fa8282c0e8d9c78664d512edd37 Mon Sep 17 00:00:00 2001 From: peter Date: Mon, 19 Sep 2016 12:03:08 +0200 Subject: [PATCH] IDEA-76782 Generate runtime assertions for all configured not-null annotations --- .../NotNullVerifyingInstrumenter.java | 61 ++++++++++++------- .../javac2/src/com/intellij/ant/Javac2.java | 18 ++++-- .../NullableNotNullManagerImpl.java | 13 ++++ .../codeInsight/NullableNotNullManager.java | 24 +++++--- .../MultipleAnnotations.java | 14 +++++ .../MakeInferredAnnotationExplicitTest.groovy | 2 +- .../NotNullVerifyingInstrumenterTest.java | 22 ++++--- .../codeInsight/NullableNotNullDialog.java | 3 +- .../NotNullInstrumentingBuilder.java | 6 +- .../JpsJavaCompilerConfiguration.java | 4 +- .../JpsJavaCompilerConfigurationImpl.java | 10 +-- .../JpsJavaCompilerNotNullableSerializer.java | 34 +++++++---- 12 files changed, 143 insertions(+), 68 deletions(-) create mode 100644 java/java-tests/testData/compiler/notNullVerification/MultipleAnnotations.java diff --git a/java/compiler/instrumentation-util/src/com/intellij/compiler/notNullVerification/NotNullVerifyingInstrumenter.java b/java/compiler/instrumentation-util/src/com/intellij/compiler/notNullVerification/NotNullVerifyingInstrumenter.java index cd28b26bdb8a..43ffcfe59c68 100644 --- a/java/compiler/instrumentation-util/src/com/intellij/compiler/notNullVerification/NotNullVerifyingInstrumenter.java +++ b/java/compiler/instrumentation-util/src/com/intellij/compiler/notNullVerification/NotNullVerifyingInstrumenter.java @@ -21,14 +21,15 @@ import org.jetbrains.org.objectweb.asm.*; import java.io.ByteArrayOutputStream; import java.io.PrintStream; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Set; /** * @author ven */ public class NotNullVerifyingInstrumenter extends ClassVisitor implements Opcodes { - private static final String NOT_NULL_CLASS_NAME = "org/jetbrains/annotations/NotNull"; private static final String SYNTHETIC_CLASS_NAME = "java/lang/Synthetic"; private static final String SYNTHETIC_TYPE = "L" + SYNTHETIC_CLASS_NAME + ";"; private static final String IAE_CLASS_NAME = "java/lang/IllegalArgumentException"; @@ -36,9 +37,6 @@ public class NotNullVerifyingInstrumenter extends ClassVisitor implements Opcode private static final String ANNOTATION_DEFAULT_METHOD = "value"; - private final String myNullArgMessageIndexed; - private final String myNullArgMessageNamed; - private final String myNullResultMessage; @SuppressWarnings("SSBasedInspection") private static final String[] EMPTY_STRING_ARRAY = new String[0]; private final Map> myMethodParamNames; @@ -46,22 +44,19 @@ public class NotNullVerifyingInstrumenter extends ClassVisitor implements Opcode private boolean myIsModification = false; private RuntimeException myPostponedError; private final AuxiliaryMethodGenerator myAuxGenerator; - private final String myNotNullAnno; + private final Set myNotNullAnnos = new HashSet(); - private NotNullVerifyingInstrumenter(final ClassVisitor classVisitor, ClassReader reader, String notNullAnnotation) { + private NotNullVerifyingInstrumenter(final ClassVisitor classVisitor, ClassReader reader, String[] notNullAnnotations) { super(Opcodes.API_VERSION, classVisitor); - final String fullName = notNullAnnotation != null ? notNullAnnotation.replace('.', '/') : NOT_NULL_CLASS_NAME; - final String shortName = fullName.substring(fullName.lastIndexOf('/') + 1); - myNotNullAnno = "L" + fullName + ";"; - myNullArgMessageIndexed = "Argument %s for @" + shortName + " parameter of %s.%s must not be null"; - myNullArgMessageNamed = "Argument for @" + shortName + " parameter '%s' of %s.%s must not be null"; - myNullResultMessage = "@" + shortName + " method %s.%s must not return null"; + for (String annotation : notNullAnnotations) { + myNotNullAnnos.add("L" + annotation.replace('.', '/') + ";"); + } myMethodParamNames = getAllParameterNames(reader); myAuxGenerator = new AuxiliaryMethodGenerator(reader); } - public static boolean processClassFile(final FailSafeClassReader reader, final ClassVisitor writer, String notNullAnnotation) { - NotNullVerifyingInstrumenter instrumenter = new NotNullVerifyingInstrumenter(writer, reader, notNullAnnotation); + public static boolean processClassFile(final FailSafeClassReader reader, final ClassVisitor writer, String[] notNullAnnotations) { + NotNullVerifyingInstrumenter instrumenter = new NotNullVerifyingInstrumenter(writer, reader, notNullAnnotations); reader.accept(instrumenter, 0); instrumenter.myAuxGenerator.generateReportingMethod(writer); return instrumenter.isModification(); @@ -121,10 +116,32 @@ public class NotNullVerifyingInstrumenter extends ClassVisitor implements Opcode private static class NotNullState { String message; String exceptionType; + final String notNullAnno; - NotNullState(String exceptionType) { + NotNullState(String notNullAnno, String exceptionType) { + this.notNullAnno = notNullAnno; this.exceptionType = exceptionType; } + + String getNullParamMessage(String paramName) { + if (message != null) return message; + + final String shortName = getAnnoShortName(); + if (paramName != null) return "Argument for @" + shortName + " parameter '%s' of %s.%s must not be null"; + return "Argument %s for @" + shortName + " parameter of %s.%s must not be null"; + } + + String getNullResultMessage() { + if (message != null) return message; + + final String shortName = getAnnoShortName(); + return "@" + shortName + " method %s.%s must not return null"; + } + + private String getAnnoShortName() { + String fullName = notNullAnno.substring(1, notNullAnno.length() - 1); // "Lpk/name;" -> "pk/name" + return fullName.substring(fullName.lastIndexOf('/') + 1); + } } @Override @@ -160,8 +177,8 @@ public class NotNullVerifyingInstrumenter extends ClassVisitor implements Opcode public AnnotationVisitor visitParameterAnnotation(final int parameter, final String anno, final boolean visible) { AnnotationVisitor av = mv.visitParameterAnnotation(parameter, anno, visible); - if (isReferenceType(args[parameter]) && anno.equals(myNotNullAnno)) { - NotNullState state = new NotNullState(IAE_CLASS_NAME); + if (isReferenceType(args[parameter]) && myNotNullAnnos.contains(anno)) { + NotNullState state = new NotNullState(anno, IAE_CLASS_NAME); myNotNullParams.put(new Integer(parameter), state); av = collectNotNullArgs(av, state); } @@ -176,8 +193,8 @@ public class NotNullVerifyingInstrumenter extends ClassVisitor implements Opcode @Override public AnnotationVisitor visitAnnotation(String anno, boolean isRuntime) { AnnotationVisitor av = mv.visitAnnotation(anno, isRuntime); - if (isReferenceType(returnType) && anno.equals(myNotNullAnno)) { - myMethodNotNull = new NotNullState(ISE_CLASS_NAME); + if (isReferenceType(returnType) && myNotNullAnnos.contains(anno)) { + myMethodNotNull = new NotNullState(anno, ISE_CLASS_NAME); av = collectNotNullArgs(av, myMethodNotNull); } @@ -203,9 +220,7 @@ public class NotNullVerifyingInstrumenter extends ClassVisitor implements Opcode NotNullState state = entry.getValue(); String paramName = paramNames == null ? null : paramNames.get(param); - String descrPattern = state.message != null - ? state.message - : paramName != null ? myNullArgMessageNamed : myNullArgMessageIndexed; + String descrPattern = state.getNullParamMessage(paramName); String[] args = state.message != null ? EMPTY_STRING_ARRAY : new String[]{paramName != null ? paramName : String.valueOf(param - mySyntheticCount), myClassName, name}; @@ -228,7 +243,7 @@ public class NotNullVerifyingInstrumenter extends ClassVisitor implements Opcode mv.visitInsn(DUP); final Label skipLabel = new Label(); mv.visitJumpInsn(IFNONNULL, skipLabel); - String descrPattern = myMethodNotNull.message != null ? myMethodNotNull.message : myNullResultMessage; + String descrPattern = myMethodNotNull.getNullResultMessage(); String[] args = myMethodNotNull.message != null ? EMPTY_STRING_ARRAY : new String[]{myClassName, name}; reportError(myMethodNotNull.exceptionType, skipLabel, descrPattern, args); } diff --git a/java/compiler/javac2/src/com/intellij/ant/Javac2.java b/java/compiler/javac2/src/com/intellij/ant/Javac2.java index af11c3849a6c..239fe3f3873a 100644 --- a/java/compiler/javac2/src/com/intellij/ant/Javac2.java +++ b/java/compiler/javac2/src/com/intellij/ant/Javac2.java @@ -39,7 +39,7 @@ public class Javac2 extends Javac { private ArrayList myFormFiles; private List myNestedFormPathList; private boolean instrumentNotNull = true; - private String myNotNull; + private String myNotNullAnnotations; private List myClassFilterAnnotationRegexpList = new ArrayList(0); public Javac2() { @@ -76,12 +76,18 @@ public class Javac2 extends Javac { this.instrumentNotNull = instrumentNotNull; } - public String getNotNull() { - return myNotNull; + /** + * @return semicolon-separated names of not-null annotations to be instrumented. Example: "org.jetbrains.annotations.NotNull;javax.annotation.Nonnull" + */ + public String getNotNullAnnotations() { + return myNotNullAnnotations; } - public void setNotNull(String notNull) { - myNotNull = notNull; + /** + * @param notNullAnnotations semicolon-separated names of not-null annotations to be instrumented. Example: "org.jetbrains.annotations.NotNull;javax.annotation.Nonnull" + */ + public void setNotNullAnnotations(String notNullAnnotations) { + myNotNullAnnotations = notNullAnnotations; } /** @@ -447,7 +453,7 @@ public class Javac2 extends Javac { if (version >= Opcodes.V1_5 && !shouldBeSkippedByAnnotationPattern(reader)) { ClassWriter writer = new InstrumenterClassWriter(reader, getAsmClassWriterFlags(version), finder); - if (NotNullVerifyingInstrumenter.processClassFile(reader, writer, myNotNull)) { + if (NotNullVerifyingInstrumenter.processClassFile(reader, writer, myNotNullAnnotations.split(";"))) { final FileOutputStream fileOutputStream = new FileOutputStream(path); try { fileOutputStream.write(writer.toByteArray()); diff --git a/java/java-impl/src/com/intellij/codeInsight/NullableNotNullManagerImpl.java b/java/java-impl/src/com/intellij/codeInsight/NullableNotNullManagerImpl.java index 87016a182efa..91e83f0920af 100644 --- a/java/java-impl/src/com/intellij/codeInsight/NullableNotNullManagerImpl.java +++ b/java/java-impl/src/com/intellij/codeInsight/NullableNotNullManagerImpl.java @@ -18,9 +18,22 @@ package com.intellij.codeInsight; import com.intellij.codeInspection.dataFlow.HardcodedContracts; import com.intellij.openapi.components.State; import com.intellij.psi.PsiElement; +import org.jetbrains.jps.model.serialization.java.compiler.JpsJavaCompilerNotNullableSerializer; + +import java.util.List; @State(name = "NullableNotNullManager") public class NullableNotNullManagerImpl extends NullableNotNullManager { + + public NullableNotNullManagerImpl() { + myNotNulls.addAll(getPredefinedNotNulls()); + } + + @Override + public List getPredefinedNotNulls() { + return JpsJavaCompilerNotNullableSerializer.DEFAULT_NOT_NULLS; + } + protected boolean hasHardcodedContracts(PsiElement element) { return HardcodedContracts.hasHardcodedContracts(element); } diff --git a/java/java-psi-api/src/com/intellij/codeInsight/NullableNotNullManager.java b/java/java-psi-api/src/com/intellij/codeInsight/NullableNotNullManager.java index c3b31f201484..4fe312874149 100644 --- a/java/java-psi-api/src/com/intellij/codeInsight/NullableNotNullManager.java +++ b/java/java-psi-api/src/com/intellij/codeInsight/NullableNotNullManager.java @@ -47,12 +47,8 @@ public abstract class NullableNotNullManager implements PersistentStateComponent public static final String[] DEFAULT_NULLABLES = {AnnotationUtil.NULLABLE, JAVAX_ANNOTATION_NULLABLE, "edu.umd.cs.findbugs.annotations.Nullable", "android.support.annotation.Nullable" }; - public static final String[] DEFAULT_NOT_NULLS = {AnnotationUtil.NOT_NULL, JAVAX_ANNOTATION_NONNULL, - "edu.umd.cs.findbugs.annotations.NonNull", "android.support.annotation.NonNull" - }; public NullableNotNullManager() { - Collections.addAll(myNotNulls, DEFAULT_NOT_NULLS); Collections.addAll(myNullables, DEFAULT_NULLABLES); } @@ -78,7 +74,12 @@ public abstract class NullableNotNullManager implements PersistentStateComponent public void setNotNulls(@NotNull String... annotations) { myNotNulls.clear(); - addAllIfNotPresent(myNotNulls, DEFAULT_NOT_NULLS); + for (String annotation : getPredefinedNotNulls()) { + LOG.assertTrue(annotation != null); + if (!myNotNulls.contains(annotation)) { + myNotNulls.add(annotation); + } + } addAllIfNotPresent(myNotNulls, annotations); } @@ -210,7 +211,7 @@ public abstract class NullableNotNullManager implements PersistentStateComponent if (type == null || TypeConversionUtil.isPrimitiveAndNotNull(type)) return null; // even if javax.annotation.Nullable is not configured, it should still take precedence over ByDefault annotations - if (AnnotationUtil.isAnnotated(owner, Arrays.asList(nullable ? DEFAULT_NOT_NULLS : DEFAULT_NULLABLES), checkBases, false)) { + if (AnnotationUtil.isAnnotated(owner, nullable ? getPredefinedNotNulls() : Arrays.asList(DEFAULT_NULLABLES), checkBases, false)) { return null; } @@ -329,7 +330,8 @@ public abstract class NullableNotNullManager implements PersistentStateComponent } public boolean hasDefaultValues() { - if (DEFAULT_NULLABLES.length != getNullables().size() || DEFAULT_NOT_NULLS.length != getNotNulls().size()) { + List predefinedNotNulls = getPredefinedNotNulls(); + if (DEFAULT_NULLABLES.length != getNullables().size() || predefinedNotNulls.size() != getNotNulls().size()) { return false; } if (!myDefaultNotNull.equals(AnnotationUtil.NOT_NULL) || !myDefaultNullable.equals(AnnotationUtil.NULLABLE)) { @@ -340,8 +342,8 @@ public abstract class NullableNotNullManager implements PersistentStateComponent return false; } } - for (int i = 0; i < DEFAULT_NOT_NULLS.length; i++) { - if (!getNotNulls().get(i).equals(DEFAULT_NOT_NULLS[i])) { + for (int i = 0; i < predefinedNotNulls.size(); i++) { + if (!getNotNulls().get(i).equals(predefinedNotNulls.get(i))) { return false; } } @@ -376,7 +378,7 @@ public abstract class NullableNotNullManager implements PersistentStateComponent Collections.addAll(myNullables, DEFAULT_NULLABLES); } if (myNotNulls.isEmpty()) { - Collections.addAll(myNotNulls, DEFAULT_NOT_NULLS); + myNotNulls.addAll(getPredefinedNotNulls()); } } catch (InvalidDataException e) { @@ -391,4 +393,6 @@ public abstract class NullableNotNullManager implements PersistentStateComponent public static boolean isNotNull(@NotNull PsiModifierListOwner owner) { return getInstance(owner.getProject()).isNotNull(owner, true); } + + public abstract List getPredefinedNotNulls(); } \ No newline at end of file diff --git a/java/java-tests/testData/compiler/notNullVerification/MultipleAnnotations.java b/java/java-tests/testData/compiler/notNullVerification/MultipleAnnotations.java new file mode 100644 index 000000000000..f0b73099b728 --- /dev/null +++ b/java/java-tests/testData/compiler/notNullVerification/MultipleAnnotations.java @@ -0,0 +1,14 @@ +@interface FooAnno {} +@interface BarAnno {} + +public class MultipleAnnotations { + @FooAnno + public Object foo1() { + return null; + } + + @BarAnno + public Object foo2() { + return null; + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/MakeInferredAnnotationExplicitTest.groovy b/java/java-tests/testSrc/com/intellij/codeInsight/MakeInferredAnnotationExplicitTest.groovy index f3c248c59894..d6e16f049590 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/MakeInferredAnnotationExplicitTest.groovy +++ b/java/java-tests/testSrc/com/intellij/codeInsight/MakeInferredAnnotationExplicitTest.groovy @@ -73,7 +73,7 @@ class Foo { @Override protected void tearDown() throws Exception { - NullableNotNullManager.getInstance(project).notNulls = NullableNotNullManager.DEFAULT_NOT_NULLS + NullableNotNullManager.getInstance(project).notNulls = NullableNotNullManager.getInstance(project).predefinedNotNulls as String[] NullableNotNullManager.getInstance(project).defaultNotNull = AnnotationUtil.NOT_NULL super.tearDown() diff --git a/java/java-tests/testSrc/com/intellij/compiler/notNullVerification/NotNullVerifyingInstrumenterTest.java b/java/java-tests/testSrc/com/intellij/compiler/notNullVerification/NotNullVerifyingInstrumenterTest.java index d9c3cc09cfd1..d6a75bceab7b 100644 --- a/java/java-tests/testSrc/com/intellij/compiler/notNullVerification/NotNullVerifyingInstrumenterTest.java +++ b/java/java-tests/testSrc/com/intellij/compiler/notNullVerification/NotNullVerifyingInstrumenterTest.java @@ -16,6 +16,7 @@ package com.intellij.compiler.notNullVerification; import com.intellij.JavaTestUtil; +import com.intellij.codeInsight.AnnotationUtil; import com.intellij.compiler.instrumentation.FailSafeClassReader; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; @@ -88,7 +89,7 @@ public class NotNullVerifyingInstrumenterTest extends UsefulTestCase { } public void testUseParameterNames() throws Exception { - Class testClass = prepareTest(true); + Class testClass = prepareTest(true, AnnotationUtil.NOT_NULL); Constructor constructor = testClass.getConstructor(Object.class, Object.class); verifyCallThrowsException("Argument for @NotNull parameter 'obj2' of UseParameterNames. must not be null", null, constructor, null, null); @@ -101,13 +102,13 @@ public class NotNullVerifyingInstrumenterTest extends UsefulTestCase { } public void testLongParameter() throws Exception { - Class testClass = prepareTest(true); + Class testClass = prepareTest(true, AnnotationUtil.NOT_NULL); Method staticMethod = testClass.getMethod("foo", long.class, String.class, String.class); verifyCallThrowsException("Argument for @NotNull parameter 'c' of LongParameter.foo must not be null", null, staticMethod, new Long(2), "z", null); } public void testDoubleParameter() throws Exception { - Class testClass = prepareTest(true); + Class testClass = prepareTest(true, AnnotationUtil.NOT_NULL); Method staticMethod = testClass.getMethod("foo", double.class, String.class, String.class); verifyCallThrowsException("Argument for @NotNull parameter 'c' of DoubleParameter.foo must not be null", null, staticMethod, new Long(2), "z", null); } @@ -173,8 +174,15 @@ public class NotNullVerifyingInstrumenterTest extends UsefulTestCase { verifyCallThrowsException("@NotNull method MultipleMessages.foo2 must not return null", instance, test.getMethod("foo2")); } + public void testMultipleAnnotations() throws Exception { + Class test = prepareTest(false, "FooAnno", "BarAnno"); + Object instance = test.newInstance(); + verifyCallThrowsException("@FooAnno method MultipleAnnotations.foo1 must not return null", instance, test.getMethod("foo1")); + verifyCallThrowsException("@BarAnno method MultipleAnnotations.foo2 must not return null", instance, test.getMethod("foo2")); + } + public void testMalformedBytecode() throws Exception { - Class testClass = prepareTest(false); + Class testClass = prepareTest(false, AnnotationUtil.NOT_NULL); verifyCallThrowsException("Argument 0 for @NotNull parameter of MalformedBytecode$NullTest2.handle must not be null", null, testClass.getMethod("main")); } @@ -198,10 +206,10 @@ public class NotNullVerifyingInstrumenterTest extends UsefulTestCase { } private Class prepareTest() throws IOException { - return prepareTest(false); + return prepareTest(false, AnnotationUtil.NOT_NULL); } - private Class prepareTest(boolean withDebugInfo) throws IOException { + private Class prepareTest(boolean withDebugInfo, String... notNullAnnos) throws IOException { String base = JavaTestUtil.getJavaTestDataPath() + "/compiler/notNullVerification/"; final String baseClassName = getTestName(false); String path = base + baseClassName; @@ -228,7 +236,7 @@ public class NotNullVerifyingInstrumenterTest extends UsefulTestCase { FailSafeClassReader reader = new FailSafeClassReader(content, 0, content.length); ClassWriter writer = new ClassWriter(reader, ClassWriter.COMPUTE_FRAMES); - modified |= NotNullVerifyingInstrumenter.processClassFile(reader, writer, null); + modified |= NotNullVerifyingInstrumenter.processClassFile(reader, writer, notNullAnnos); byte[] instrumented = writer.toByteArray(); final String className = FileUtil.getNameWithoutExtension(fileName); diff --git a/java/openapi/src/com/intellij/codeInsight/NullableNotNullDialog.java b/java/openapi/src/com/intellij/codeInsight/NullableNotNullDialog.java index cbdb0f160752..65d4ff621791 100644 --- a/java/openapi/src/com/intellij/codeInsight/NullableNotNullDialog.java +++ b/java/openapi/src/com/intellij/codeInsight/NullableNotNullDialog.java @@ -27,6 +27,7 @@ import com.intellij.psi.PsiClass; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.ui.*; import com.intellij.ui.components.JBList; +import com.intellij.util.ArrayUtil; import com.intellij.util.ui.EmptyIcon; import com.intellij.util.ui.JBUI; import org.jetbrains.annotations.NotNull; @@ -64,7 +65,7 @@ public class NullableNotNullDialog extends DialogWrapper { new AnnotationsPanel("Nullable", manager.getDefaultNullable(), manager.getNullables(), NullableNotNullManager.DEFAULT_NULLABLES); splitter.setFirstComponent(myNullablePanel.getComponent()); myNotNullPanel = - new AnnotationsPanel("NotNull", manager.getDefaultNotNull(), manager.getNotNulls(), NullableNotNullManager.DEFAULT_NOT_NULLS); + new AnnotationsPanel("NotNull", manager.getDefaultNotNull(), manager.getNotNulls(), ArrayUtil.toStringArray(manager.getPredefinedNotNulls())); splitter.setSecondComponent(myNotNullPanel.getComponent()); splitter.setHonorComponentsMinimumSize(true); splitter.setPreferredSize(JBUI.size(300, 400)); diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/instrumentation/NotNullInstrumentingBuilder.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/instrumentation/NotNullInstrumentingBuilder.java index 71a090df3927..ba2f72ebaed1 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/incremental/instrumentation/NotNullInstrumentingBuilder.java +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/instrumentation/NotNullInstrumentingBuilder.java @@ -19,6 +19,7 @@ import com.intellij.compiler.instrumentation.FailSafeClassReader; import com.intellij.compiler.instrumentation.InstrumentationClassFinder; import com.intellij.compiler.notNullVerification.NotNullVerifyingInstrumenter; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.util.ArrayUtil; import com.intellij.util.Function; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; @@ -37,6 +38,7 @@ import org.jetbrains.org.objectweb.asm.Opcodes; import java.io.File; import java.util.Collection; +import java.util.List; /** * @author Eugene Zhuravlev @@ -80,8 +82,8 @@ public class NotNullInstrumentingBuilder extends BaseInstrumentingBuilder{ InstrumentationClassFinder finder) { try { final ProjectDescriptor pd = context.getProjectDescriptor(); - final String notNullAnnotation = JpsJavaExtensionService.getInstance().getOrCreateCompilerConfiguration(pd.getProject()).getNotNullAnnotation(); - if (NotNullVerifyingInstrumenter.processClassFile((FailSafeClassReader)reader, writer, notNullAnnotation)) { + final List notNulls = JpsJavaExtensionService.getInstance().getOrCreateCompilerConfiguration(pd.getProject()).getNotNullAnnotations(); + if (NotNullVerifyingInstrumenter.processClassFile((FailSafeClassReader)reader, writer, ArrayUtil.toStringArray(notNulls))) { return new BinaryContent(writer.toByteArray()); } } diff --git a/jps/model-api/src/org/jetbrains/jps/model/java/compiler/JpsJavaCompilerConfiguration.java b/jps/model-api/src/org/jetbrains/jps/model/java/compiler/JpsJavaCompilerConfiguration.java index ac4e30a5b25a..f91f6bfa24bc 100644 --- a/jps/model-api/src/org/jetbrains/jps/model/java/compiler/JpsJavaCompilerConfiguration.java +++ b/jps/model-api/src/org/jetbrains/jps/model/java/compiler/JpsJavaCompilerConfiguration.java @@ -31,8 +31,8 @@ public interface JpsJavaCompilerConfiguration extends JpsElement { boolean isAddNotNullAssertions(); void setAddNotNullAssertions(boolean addNotNullAssertions); - String getNotNullAnnotation(); - void setNotNullAnnotation(String notNullAnnotation); + List getNotNullAnnotations(); + void setNotNullAnnotations(List notNullAnnotations); boolean isClearOutputDirectoryOnRebuild(); void setClearOutputDirectoryOnRebuild(boolean clearOutputDirectoryOnRebuild); diff --git a/jps/model-impl/src/org/jetbrains/jps/model/java/impl/compiler/JpsJavaCompilerConfigurationImpl.java b/jps/model-impl/src/org/jetbrains/jps/model/java/impl/compiler/JpsJavaCompilerConfigurationImpl.java index a7c21f50fa78..46ecc331f4fe 100644 --- a/jps/model-impl/src/org/jetbrains/jps/model/java/impl/compiler/JpsJavaCompilerConfigurationImpl.java +++ b/jps/model-impl/src/org/jetbrains/jps/model/java/impl/compiler/JpsJavaCompilerConfigurationImpl.java @@ -35,7 +35,7 @@ import java.util.*; public class JpsJavaCompilerConfigurationImpl extends JpsCompositeElementBase implements JpsJavaCompilerConfiguration { public static final JpsElementChildRole ROLE = JpsElementChildRoleBase.create("compiler configuration"); private boolean myAddNotNullAssertions = true; - private String myNotNullAnnotation = NotNull.class.getName(); + private List myNotNullAnnotations = Collections.singletonList(NotNull.class.getName()); private boolean myClearOutputDirectoryOnRebuild = true; private final JpsCompilerExcludes myCompilerExcludes = new JpsCompilerExcludesImpl(); private final List myResourcePatterns = new ArrayList(); @@ -67,8 +67,8 @@ public class JpsJavaCompilerConfigurationImpl extends JpsCompositeElementBase getNotNullAnnotations() { + return myNotNullAnnotations; } @Override @@ -82,8 +82,8 @@ public class JpsJavaCompilerConfigurationImpl extends JpsCompositeElementBase notNullAnnotations) { + myNotNullAnnotations = Collections.unmodifiableList(notNullAnnotations); } @Override diff --git a/jps/model-serialization/src/org/jetbrains/jps/model/serialization/java/compiler/JpsJavaCompilerNotNullableSerializer.java b/jps/model-serialization/src/org/jetbrains/jps/model/serialization/java/compiler/JpsJavaCompilerNotNullableSerializer.java index 35663021935f..5b50dbe174c5 100644 --- a/jps/model-serialization/src/org/jetbrains/jps/model/serialization/java/compiler/JpsJavaCompilerNotNullableSerializer.java +++ b/jps/model-serialization/src/org/jetbrains/jps/model/serialization/java/compiler/JpsJavaCompilerNotNullableSerializer.java @@ -15,6 +15,7 @@ */ package org.jetbrains.jps.model.serialization.java.compiler; +import com.intellij.util.containers.ContainerUtil; import org.jdom.Element; import org.jetbrains.annotations.NotNull; import org.jetbrains.jps.model.JpsProject; @@ -22,13 +23,17 @@ import org.jetbrains.jps.model.java.JpsJavaExtensionService; import org.jetbrains.jps.model.java.compiler.JpsJavaCompilerConfiguration; import org.jetbrains.jps.model.serialization.JpsProjectExtensionSerializer; +import java.util.Arrays; +import java.util.List; + /** * @author vladimir.dolzhenko */ public class JpsJavaCompilerNotNullableSerializer extends JpsProjectExtensionSerializer { - private static final String DEFAULT_VALUE = NotNull.class.getName(); - private static final String NOTNULL_ANNOTATION = "myDefaultNotNull"; - private static final String VALUE = "value"; + public static final List DEFAULT_NOT_NULLS = Arrays.asList( + NotNull.class.getName(), "javax.annotation.Nonnull", + "edu.umd.cs.findbugs.annotations.NonNull", "android.support.annotation.NonNull" + ); public JpsJavaCompilerNotNullableSerializer() { super("misc.xml", "NullableNotNullManager"); @@ -37,20 +42,27 @@ public class JpsJavaCompilerNotNullableSerializer extends JpsProjectExtensionSer @Override public void loadExtension(@NotNull JpsProject project, @NotNull Element componentTag) { JpsJavaCompilerConfiguration configuration = JpsJavaExtensionService.getInstance().getOrCreateCompilerConfiguration(project); - String value = DEFAULT_VALUE; - for (Element element : componentTag.getChildren("option")) { - if (NOTNULL_ANNOTATION.equals(element.getAttributeValue("name"))){ - value = element.getAttributeValue(VALUE, DEFAULT_VALUE); - break; + List annoNames = ContainerUtil.newArrayList(); + for (Element option : componentTag.getChildren("option")) { + if ("myNotNulls".equals(option.getAttributeValue("name"))){ + for (Element value : option.getChildren("value")) { + for (Element list : value.getChildren("list")) { + for (Element item : list.getChildren("item")) { + ContainerUtil.addIfNotNull(annoNames, item.getAttributeValue("itemvalue")); + } + } + } } } - configuration.setNotNullAnnotation(value); + if (annoNames.isEmpty()) { + annoNames.addAll(DEFAULT_NOT_NULLS); + } + configuration.setNotNullAnnotations(annoNames); } @Override public void loadExtensionWithDefaultSettings(@NotNull JpsProject project) { - JpsJavaCompilerConfiguration configuration = JpsJavaExtensionService.getInstance().getOrCreateCompilerConfiguration(project); - configuration.setNotNullAnnotation(DEFAULT_VALUE); + JpsJavaExtensionService.getInstance().getOrCreateCompilerConfiguration(project).setNotNullAnnotations(DEFAULT_NOT_NULLS); } @Override