Skip to content

Commit 3d780e8

Browse files
Warn about sets of null annotation types with conflicting @target (eclipse-jdt#2162)
Fixes eclipse-jdt#2141
1 parent d4c3a60 commit 3d780e8

File tree

3 files changed

+129
-0
lines changed

3 files changed

+129
-0
lines changed

org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/PreferencesMessages.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -819,7 +819,13 @@ private PreferencesMessages() {
819819
public static String NameConventionConfigurationBlock_use_override_annotation_label;
820820
public static String NullAnnotationsConfigurationDialog_error_message;
821821
public static String NullAnnotationsConfigurationDialog_error_title;
822+
public static String NullAnnotationsConfigurationDialog_incompatibleAnnotations_dialogMessage;
823+
public static String NullAnnotationsConfigurationDialog_incompatibleAnnotations_dialogTitle;
822824
public static String NullAnnotationsConfigurationDialog_restore_defaults;
825+
public static String NullAnnotationsConfigurationDialog_targetBoth;
826+
public static String NullAnnotationsConfigurationDialog_targetDeclarations;
827+
public static String NullAnnotationsConfigurationDialog_targetTypeuse;
828+
public static String NullAnnotationsConfigurationDialog_targetUnspecified;
823829
public static String NullAnnotationsConfigurationDialog_title;
824830
public static String NullAnnotationsConfigurationDialog_use_default_annotations_for_null;
825831
public static String ProblemSeveritiesConfigurationBlock_pb_unhandled_surpresswarning_tokens;

org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/PreferencesMessages.properties

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1001,7 +1001,13 @@ NativeLibrariesPropertyPage_not_supported=The current class path entry belongs t
10011001
NativeLibrariesPropertyPage_read_only=The current class path entry belongs to container ''{0}'' which does not allow user modifications to native libraries on its entries.
10021002
NullAnnotationsConfigurationDialog_error_message=A problem occurred while collecting types. See the error log for details.
10031003
NullAnnotationsConfigurationDialog_error_title=Annotation Selection
1004+
NullAnnotationsConfigurationDialog_incompatibleAnnotations_dialogMessage=Selected annotations have incompatible targets, which may cause inaccurate analysis results:
1005+
NullAnnotationsConfigurationDialog_incompatibleAnnotations_dialogTitle=Incompatible null annotations
10041006
NullAnnotationsConfigurationDialog_restore_defaults=Restore &Defaults
1007+
NullAnnotationsConfigurationDialog_targetBoth=Annotation supporting both TYPE_USE and declaration application
1008+
NullAnnotationsConfigurationDialog_targetDeclarations=Declaration annotation
1009+
NullAnnotationsConfigurationDialog_targetTypeuse=Annotation for TYPE_USE
1010+
NullAnnotationsConfigurationDialog_targetUnspecified=Annotation with unspecified Target
10051011
NullAnnotationsConfigurationDialog_title=Annotations for Null Specifications
10061012
NullAnnotationsConfigurationDialog_use_default_annotations_for_null=Use default annotations for null specifications (<a>Configure...</a>)
10071013

org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/preferences/ProblemSeveritiesConfigurationBlock.java

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,14 @@
1515
*******************************************************************************/
1616
package org.eclipse.jdt.internal.ui.preferences;
1717

18+
import java.lang.annotation.ElementType;
19+
import java.lang.annotation.Target;
20+
import java.util.ArrayList;
1821
import java.util.Arrays;
22+
import java.util.HashMap;
1923
import java.util.List;
24+
import java.util.Map;
25+
import java.util.Map.Entry;
2026

2127
import org.eclipse.equinox.bidi.StructuredTextTypeHandlerFactory;
2228
import org.eclipse.osgi.util.NLS;
@@ -70,6 +76,9 @@
7076
import org.eclipse.ui.forms.widgets.ExpandableComposite;
7177
import org.eclipse.ui.preferences.IWorkbenchPreferenceContainer;
7278

79+
import org.eclipse.jdt.core.IAnnotation;
80+
import org.eclipse.jdt.core.IMemberValuePair;
81+
import org.eclipse.jdt.core.IType;
7382
import org.eclipse.jdt.core.JavaConventions;
7483
import org.eclipse.jdt.core.JavaCore;
7584
import org.eclipse.jdt.core.JavaModelException;
@@ -560,11 +569,119 @@ protected void buttonPressed(int buttonId) {
560569
fOtherNullableAnnotationsDialogField.removeAllElements();
561570
fOtherNonNullAnnotationsDialogField.removeAllElements();
562571
fOtherNonNullByDefaultAnnotationsDialogField.removeAllElements();
572+
} else if (buttonId == IDialogConstants.OK_ID) {
573+
Map<AnnotationTarget,List<String>> namesPerTarget= new HashMap<>();
574+
detectedAnnotationTargets(fNullableAnnotationDialogField, fOtherNullableAnnotationsDialogField, namesPerTarget);
575+
detectedAnnotationTargets(fNonNullAnnotationDialogField, fOtherNonNullAnnotationsDialogField, namesPerTarget);
576+
if (namesPerTarget.size() > 1) {
577+
if (warnIncompatibleAnnotations(namesPerTarget) != 0) {
578+
return;
579+
}
580+
}
581+
okPressed();
563582
} else {
564583
super.buttonPressed(buttonId);
565584
}
566585
}
567586

587+
enum AnnotationTarget { NONE, DECLARATIONS, TYPE_USE, BOTH;
588+
static AnnotationTarget forMemberValuePairs(IMemberValuePair[] pairs) {
589+
AnnotationTarget result= NONE;
590+
for (IMemberValuePair pair : pairs) {
591+
int kind= pair.getValueKind();
592+
if (kind == IMemberValuePair.K_QUALIFIED_NAME || kind == IMemberValuePair.K_SIMPLE_NAME) {
593+
for (Object val : (Object[]) pair.getValue()) {
594+
AnnotationTarget current= forName((String) val);
595+
if (result == NONE)
596+
result= current;
597+
else if (current != result)
598+
return BOTH;
599+
}
600+
}
601+
}
602+
return result;
603+
}
604+
private static AnnotationTarget forName(String name) {
605+
if (name.equals(ElementType.TYPE_USE.name()) || name.equals(ElementType.class.getName()+'.'+ElementType.TYPE_USE.name()))
606+
return AnnotationTarget.TYPE_USE;
607+
else
608+
return AnnotationTarget.DECLARATIONS;
609+
}
610+
String displayString() {
611+
return switch (this) {
612+
case NONE -> PreferencesMessages.NullAnnotationsConfigurationDialog_targetUnspecified;
613+
case TYPE_USE -> PreferencesMessages.NullAnnotationsConfigurationDialog_targetTypeuse;
614+
case DECLARATIONS -> PreferencesMessages.NullAnnotationsConfigurationDialog_targetDeclarations;
615+
case BOTH -> PreferencesMessages.NullAnnotationsConfigurationDialog_targetBoth;
616+
};
617+
}
618+
}
619+
private void detectedAnnotationTargets(AnnotationDialogField field, AnnotationListDialogField list, Map<AnnotationTarget, List<String>> namesPerTarget) {
620+
String name1= field.getText();
621+
AnnotationTarget target1= getAnnotationKind(name1);
622+
List<String> perTarget= namesPerTarget.computeIfAbsent(target1, k -> new ArrayList<>());
623+
perTarget.add(name1);
624+
for (AnnotationWrapper otherWrapper : list.getElements()) {
625+
String otherName= otherWrapper.annotationName;
626+
if (otherName.isBlank()) continue;
627+
AnnotationTarget other= getAnnotationKind(otherName);
628+
perTarget= namesPerTarget.computeIfAbsent(other, k -> new ArrayList<>());
629+
perTarget.add(otherName);
630+
}
631+
}
632+
private AnnotationTarget getAnnotationKind(String name) {
633+
try {
634+
IType annotationType= JavaCore.create(fProject).findType(name);
635+
if (annotationType != null && annotationType.exists()) {
636+
for (IAnnotation metaAnn : annotationType.getAnnotations()) {
637+
if (isTargetMetaAnnotation(metaAnn, annotationType)) {
638+
return AnnotationTarget.forMemberValuePairs(metaAnn.getMemberValuePairs());
639+
}
640+
}
641+
}
642+
} catch (JavaModelException e) {
643+
// fall through
644+
}
645+
return AnnotationTarget.NONE;
646+
}
647+
private boolean isTargetMetaAnnotation(IAnnotation metaAnn, IType context) {
648+
String name= metaAnn.getElementName();
649+
if (Target.class.getName().equals(name)) {
650+
return true;
651+
} else if (Target.class.getSimpleName().equals(name)) {
652+
try {
653+
String[][] resolved= context.resolveType(name);
654+
if (resolved.length == 1 && resolved[0].length == 2) {
655+
return Target.class.getPackageName().equals(resolved[0][0]);
656+
}
657+
} catch (JavaModelException e) {
658+
return false;
659+
}
660+
}
661+
return false;
662+
}
663+
private int warnIncompatibleAnnotations(Map<AnnotationTarget,List<String>> namesPerTarget) {
664+
StringBuilder message= new StringBuilder(PreferencesMessages.NullAnnotationsConfigurationDialog_incompatibleAnnotations_dialogMessage);
665+
for (Entry<AnnotationTarget, List<String>> entry : namesPerTarget.entrySet()) {
666+
message.append("\n\n\t").append(entry.getKey().displayString()).append(':'); //$NON-NLS-1$
667+
for (String name : entry.getValue()) {
668+
message.append("\n\t\t").append(name); //$NON-NLS-1$
669+
}
670+
}
671+
MessageDialog messageDialog= new MessageDialog(
672+
getShell(),
673+
PreferencesMessages.NullAnnotationsConfigurationDialog_incompatibleAnnotations_dialogTitle,
674+
null,
675+
message.toString(),
676+
MessageDialog.QUESTION,
677+
new String[] { IDialogConstants.YES_LABEL, IDialogConstants.CANCEL_LABEL },
678+
0);
679+
messageDialog.create();
680+
Shell messageShell= messageDialog.getShell();
681+
messageShell.setLocation(messageShell.getLocation().x, getShell().getLocation().y + 40);
682+
return messageDialog.open();
683+
}
684+
568685
public String[] getResult() {
569686
return new String[] {
570687
fNullableAnnotationDialogField.getText(),

0 commit comments

Comments
 (0)