|
1
|
|
package li.selman.nullmarkeder; |
|
2
|
|
|
|
3
|
|
import com.github.javaparser.JavaParser; |
|
4
|
|
import com.github.javaparser.ast.CompilationUnit; |
|
5
|
|
import com.github.javaparser.ast.NodeList; |
|
6
|
|
import com.github.javaparser.ast.expr.AnnotationExpr; |
|
7
|
|
import com.github.javaparser.ast.expr.MarkerAnnotationExpr; |
|
8
|
|
import com.github.javaparser.ast.expr.Name; |
|
9
|
|
import com.github.javaparser.printer.lexicalpreservation.LexicalPreservingPrinter; |
|
10
|
|
import com.google.errorprone.annotations.Var; |
|
11
|
|
import java.io.IOException; |
|
12
|
|
import java.lang.System.Logger.Level; |
|
13
|
|
import java.nio.file.Files; |
|
14
|
|
import java.nio.file.Path; |
|
15
|
|
import java.nio.file.Paths; |
|
16
|
|
import java.util.List; |
|
17
|
|
import java.util.Optional; |
|
18
|
|
import java.util.stream.Collectors; |
|
19
|
|
import java.util.stream.Stream; |
|
20
|
|
|
|
21
|
|
/** |
|
22
|
|
* Generates or updates {@code package-info.java} files so that every package in a tree is annotated |
|
23
|
|
* with JSpecify's {@code @NullMarked} annotation. |
|
24
|
|
*/ |
|
25
|
|
public final class PackageInfoGenerator { |
|
26
|
|
|
|
27
|
|
private static final System.Logger LOGGER = System.getLogger(PackageInfoGenerator.class.getName()); |
|
28
|
|
|
|
29
|
|
private static final String DEFAULT_ROOT_DIR = "src/main/java"; |
|
30
|
|
|
|
31
|
|
private static final String REQUIRED_ANNOTATION = "NullMarked"; |
|
32
|
|
|
|
33
|
|
private static final String ANNOTATION_IMPORT = "org.jspecify.annotations.NullMarked"; |
|
34
|
|
|
|
35
|
|
private PackageInfoGenerator() {} |
|
36
|
|
|
|
37
|
|
public static void updatePackageInfoFiles(String rootDir, String basePackage) throws IOException { |
|
38
|
|
List<String> packages = findPackages(Paths.get(rootDir), basePackage); |
|
39
|
|
|
|
40
|
|
for (String packageName : packages) { |
|
41
|
|
Path packageInfoPath = Paths.get(rootDir, packageName.replace('.', '/'), "package-info.java"); |
|
42
|
|
|
|
43
|
1
1. updatePackageInfoFiles : negated conditional → KILLED
|
if (Files.exists(packageInfoPath)) { |
|
44
|
1
1. updatePackageInfoFiles : removed call to li/selman/nullmarkeder/PackageInfoGenerator::updateExistingPackageInfo → KILLED
|
updateExistingPackageInfo(packageInfoPath, packageName); |
|
45
|
|
} else { |
|
46
|
1
1. updatePackageInfoFiles : removed call to li/selman/nullmarkeder/PackageInfoGenerator::generateNewPackageInfo → KILLED
|
generateNewPackageInfo(packageInfoPath, packageName); |
|
47
|
|
} |
|
48
|
|
} |
|
49
|
|
} |
|
50
|
|
|
|
51
|
|
private static void updateExistingPackageInfo(Path packageInfoPath, String packageName) throws IOException { |
|
52
|
|
JavaParser parser = new JavaParser(); |
|
53
|
|
String content = Files.readString(packageInfoPath); |
|
54
|
|
Optional<CompilationUnit> result = parser.parse(content).getResult(); |
|
55
|
|
|
|
56
|
1
1. updateExistingPackageInfo : negated conditional → KILLED
|
if (result.isEmpty()) { |
|
57
|
1
1. updateExistingPackageInfo : removed call to java/lang/System$Logger::log → SURVIVED
|
LOGGER.log(Level.WARNING, "Skipping {0}: could not be parsed as valid Java source", packageInfoPath); |
|
58
|
|
return; |
|
59
|
|
} |
|
60
|
|
|
|
61
|
|
CompilationUnit cu = result.get(); |
|
62
|
|
LexicalPreservingPrinter.setup(cu); |
|
63
|
|
@Var boolean modified = false; |
|
64
|
|
|
|
65
|
3
1. lambda$updateExistingPackageInfo$0 : replaced boolean return with false for li/selman/nullmarkeder/PackageInfoGenerator::lambda$updateExistingPackageInfo$0 → SURVIVED
2. updateExistingPackageInfo : negated conditional → KILLED
3. lambda$updateExistingPackageInfo$0 : replaced boolean return with true for li/selman/nullmarkeder/PackageInfoGenerator::lambda$updateExistingPackageInfo$0 → KILLED
|
if (cu.getImports().stream().noneMatch(imp -> imp.getNameAsString().equals(ANNOTATION_IMPORT))) { |
|
66
|
|
cu.addImport(ANNOTATION_IMPORT); |
|
67
|
|
modified = true; |
|
68
|
|
} |
|
69
|
|
|
|
70
|
1
1. updateExistingPackageInfo : negated conditional → KILLED
|
if (cu.getPackageDeclaration().isEmpty()) { |
|
71
|
|
// A package-info.java with no package declaration has nowhere to attach an |
|
72
|
|
// annotation to; give it one matching its location so the fix below actually sticks. |
|
73
|
|
cu.setPackageDeclaration(packageName); |
|
74
|
|
modified = true; |
|
75
|
|
} |
|
76
|
|
NodeList<AnnotationExpr> annotations = |
|
77
|
|
cu.getPackageDeclaration().orElseThrow().getAnnotations(); |
|
78
|
|
|
|
79
|
|
boolean alreadyAnnotated = |
|
80
|
2
1. lambda$updateExistingPackageInfo$1 : replaced boolean return with true for li/selman/nullmarkeder/PackageInfoGenerator::lambda$updateExistingPackageInfo$1 → KILLED
2. lambda$updateExistingPackageInfo$1 : replaced boolean return with false for li/selman/nullmarkeder/PackageInfoGenerator::lambda$updateExistingPackageInfo$1 → KILLED
|
annotations.stream().anyMatch(a -> a.getNameAsString().equals(REQUIRED_ANNOTATION)); |
|
81
|
1
1. updateExistingPackageInfo : negated conditional → KILLED
|
if (!alreadyAnnotated) { |
|
82
|
|
annotations.add(new MarkerAnnotationExpr(new Name(REQUIRED_ANNOTATION))); |
|
83
|
|
modified = true; |
|
84
|
|
} |
|
85
|
|
|
|
86
|
1
1. updateExistingPackageInfo : negated conditional → KILLED
|
if (modified) { |
|
87
|
|
String updatedContent = LexicalPreservingPrinter.print(cu); |
|
88
|
|
Files.writeString(packageInfoPath, updatedContent); |
|
89
|
1
1. updateExistingPackageInfo : removed call to java/lang/System$Logger::log → SURVIVED
|
LOGGER.log(Level.INFO, "Updated {0}", packageInfoPath); |
|
90
|
|
} |
|
91
|
|
} |
|
92
|
|
|
|
93
|
|
private static void generateNewPackageInfo(Path packageInfoPath, String packageName) throws IOException { |
|
94
|
|
StringBuilder content = new StringBuilder(); |
|
95
|
|
content.append("@NullMarked\n"); |
|
96
|
|
content.append("package ").append(packageName).append(";\n\n"); |
|
97
|
|
content.append("import org.jspecify.annotations.NullMarked;\n"); |
|
98
|
|
|
|
99
|
|
Files.writeString(packageInfoPath, content.toString()); |
|
100
|
1
1. generateNewPackageInfo : removed call to java/lang/System$Logger::log → SURVIVED
|
LOGGER.log(Level.INFO, "Generated new package-info.java for {0}", packageName); |
|
101
|
|
} |
|
102
|
|
|
|
103
|
|
private static List<String> findPackages(Path rootDir, String basePackage) throws IOException { |
|
104
|
|
String basePath = basePackage.replace('.', '/'); |
|
105
|
|
Path realRoot = Paths.get(rootDir.toString(), basePath); |
|
106
|
|
|
|
107
|
|
try (Stream<Path> pathStream = Files.walk(realRoot)) { |
|
108
|
1
1. findPackages : replaced return value with Collections.emptyList for li/selman/nullmarkeder/PackageInfoGenerator::findPackages → KILLED
|
return pathStream |
|
109
|
2
1. lambda$findPackages$0 : replaced boolean return with false for li/selman/nullmarkeder/PackageInfoGenerator::lambda$findPackages$0 → KILLED
2. lambda$findPackages$0 : replaced boolean return with true for li/selman/nullmarkeder/PackageInfoGenerator::lambda$findPackages$0 → KILLED
|
.filter(Files::isDirectory) |
|
110
|
1
1. lambda$findPackages$1 : replaced return value with "" for li/selman/nullmarkeder/PackageInfoGenerator::lambda$findPackages$1 → KILLED
|
.map(path -> realRoot.relativize(path).toString()) |
|
111
|
|
// An empty relative path means `path` is realRoot itself - i.e. basePackage - which |
|
112
|
|
// must be included, not just its subpackages (see PackageInfoGeneratorTest for the |
|
113
|
|
// regression this covers: this method used to silently skip basePackage entirely). |
|
114
|
2
1. lambda$findPackages$2 : replaced return value with "" for li/selman/nullmarkeder/PackageInfoGenerator::lambda$findPackages$2 → KILLED
2. lambda$findPackages$2 : negated conditional → KILLED
|
.map(relative -> relative.isEmpty() ? basePackage : basePackage + "." + relative.replace('/', '.')) |
|
115
|
|
.sorted() |
|
116
|
|
.collect(Collectors.toList()); |
|
117
|
|
} |
|
118
|
|
} |
|
119
|
|
|
|
120
|
|
/** |
|
121
|
|
* Usage: {@code PackageInfoGenerator.main(rootPackage)} or |
|
122
|
|
* {@code PackageInfoGenerator.main(rootPackage, rootDir)}. Defaults {@code rootDir} to |
|
123
|
|
* {@value #DEFAULT_ROOT_DIR}. |
|
124
|
|
*/ |
|
125
|
|
public static void main(String... args) throws IOException { |
|
126
|
2
1. main : changed conditional boundary → KILLED
2. main : negated conditional → KILLED
|
if (args.length < 1) { |
|
127
|
|
throw new IllegalArgumentException( |
|
128
|
|
"Usage: PackageInfoGenerator.main(rootPackage) or PackageInfoGenerator.main(rootPackage, rootDir)"); |
|
129
|
|
} |
|
130
|
|
String basePackage = args[0]; |
|
131
|
2
1. main : negated conditional → KILLED
2. main : changed conditional boundary → KILLED
|
String rootDir = args.length > 1 ? args[1] : DEFAULT_ROOT_DIR; |
|
132
|
1
1. main : removed call to li/selman/nullmarkeder/PackageInfoGenerator::updatePackageInfoFiles → KILLED
|
updatePackageInfoFiles(rootDir, basePackage); |
|
133
|
|
} |
|
134
|
|
} |
| | Mutations |
| 43 |
|
1.1 Location : updatePackageInfoFiles Killed by : li.selman.nullmarkeder.PackageInfoGeneratorTest.[engine:junit-jupiter]/[class:li.selman.nullmarkeder.PackageInfoGeneratorTest]/[method:generatesPackageInfoForBasePackageItselfWhenItHasNoSubpackages(java.nio.file.Path)] negated conditional → KILLED
|
| 44 |
|
1.1 Location : updatePackageInfoFiles Killed by : li.selman.nullmarkeder.PackageInfoGeneratorTest.[engine:junit-jupiter]/[class:li.selman.nullmarkeder.PackageInfoGeneratorTest]/[method:addsAnnotationWhenPackageInfoHasNoPackageDeclaration(java.nio.file.Path)] removed call to li/selman/nullmarkeder/PackageInfoGenerator::updateExistingPackageInfo → KILLED
|
| 46 |
|
1.1 Location : updatePackageInfoFiles Killed by : li.selman.nullmarkeder.PackageInfoGeneratorTest.[engine:junit-jupiter]/[class:li.selman.nullmarkeder.PackageInfoGeneratorTest]/[method:generatesPackageInfoForBasePackageItselfWhenItHasNoSubpackages(java.nio.file.Path)] removed call to li/selman/nullmarkeder/PackageInfoGenerator::generateNewPackageInfo → KILLED
|
| 56 |
|
1.1 Location : updateExistingPackageInfo Killed by : li.selman.nullmarkeder.PackageInfoGeneratorTest.[engine:junit-jupiter]/[class:li.selman.nullmarkeder.PackageInfoGeneratorTest]/[method:addsAnnotationWhenPackageInfoHasNoPackageDeclaration(java.nio.file.Path)] negated conditional → KILLED
|
| 57 |
|
1.1 Location : updateExistingPackageInfo Killed by : none removed call to java/lang/System$Logger::log → SURVIVED
Covering tests
Covered by tests:
- li.selman.nullmarkeder.PackageInfoGeneratorTest.[engine:junit-jupiter]/[class:li.selman.nullmarkeder.PackageInfoGeneratorTest]/[method:leavesUnparsablePackageInfoUntouched(java.nio.file.Path)]
|
| 65 |
|
1.1 Location : lambda$updateExistingPackageInfo$0 Killed by : none replaced boolean return with false for li/selman/nullmarkeder/PackageInfoGenerator::lambda$updateExistingPackageInfo$0 → SURVIVED
Covering tests
Covered by tests:
- li.selman.nullmarkeder.PackageInfoGeneratorTest.[engine:junit-jupiter]/[class:li.selman.nullmarkeder.PackageInfoGeneratorTest]/[method:isIdempotentForAlreadyAnnotatedPackage(java.nio.file.Path)]
- li.selman.nullmarkeder.PackageInfoGeneratorTest.[engine:junit-jupiter]/[class:li.selman.nullmarkeder.PackageInfoGeneratorTest]/[method:preservesExistingPackageInfoCommentsAndAnnotationsWhenAddingNullMarked(java.nio.file.Path)]
2.2 Location : updateExistingPackageInfo Killed by : li.selman.nullmarkeder.PackageInfoGeneratorTest.[engine:junit-jupiter]/[class:li.selman.nullmarkeder.PackageInfoGeneratorTest]/[method:addsAnnotationWhenPackageInfoHasNoPackageDeclaration(java.nio.file.Path)] negated conditional → KILLED
3.3 Location : lambda$updateExistingPackageInfo$0 Killed by : li.selman.nullmarkeder.PackageInfoGeneratorTest.[engine:junit-jupiter]/[class:li.selman.nullmarkeder.PackageInfoGeneratorTest]/[method:preservesExistingPackageInfoCommentsAndAnnotationsWhenAddingNullMarked(java.nio.file.Path)] replaced boolean return with true for li/selman/nullmarkeder/PackageInfoGenerator::lambda$updateExistingPackageInfo$0 → KILLED
|
| 70 |
|
1.1 Location : updateExistingPackageInfo Killed by : li.selman.nullmarkeder.PackageInfoGeneratorTest.[engine:junit-jupiter]/[class:li.selman.nullmarkeder.PackageInfoGeneratorTest]/[method:addsAnnotationWhenPackageInfoHasNoPackageDeclaration(java.nio.file.Path)] negated conditional → KILLED
|
| 80 |
|
1.1 Location : lambda$updateExistingPackageInfo$1 Killed by : li.selman.nullmarkeder.PackageInfoGeneratorTest.[engine:junit-jupiter]/[class:li.selman.nullmarkeder.PackageInfoGeneratorTest]/[method:preservesExistingPackageInfoCommentsAndAnnotationsWhenAddingNullMarked(java.nio.file.Path)] replaced boolean return with true for li/selman/nullmarkeder/PackageInfoGenerator::lambda$updateExistingPackageInfo$1 → KILLED
2.2 Location : lambda$updateExistingPackageInfo$1 Killed by : li.selman.nullmarkeder.PackageInfoGeneratorTest.[engine:junit-jupiter]/[class:li.selman.nullmarkeder.PackageInfoGeneratorTest]/[method:isIdempotentForAlreadyAnnotatedPackage(java.nio.file.Path)] replaced boolean return with false for li/selman/nullmarkeder/PackageInfoGenerator::lambda$updateExistingPackageInfo$1 → KILLED
|
| 81 |
|
1.1 Location : updateExistingPackageInfo Killed by : li.selman.nullmarkeder.PackageInfoGeneratorTest.[engine:junit-jupiter]/[class:li.selman.nullmarkeder.PackageInfoGeneratorTest]/[method:isIdempotentForAlreadyAnnotatedPackage(java.nio.file.Path)] negated conditional → KILLED
|
| 86 |
|
1.1 Location : updateExistingPackageInfo Killed by : li.selman.nullmarkeder.PackageInfoGeneratorTest.[engine:junit-jupiter]/[class:li.selman.nullmarkeder.PackageInfoGeneratorTest]/[method:addsAnnotationWhenPackageInfoHasNoPackageDeclaration(java.nio.file.Path)] negated conditional → KILLED
|
| 89 |
|
1.1 Location : updateExistingPackageInfo Killed by : none removed call to java/lang/System$Logger::log → SURVIVED
Covering tests
Covered by tests:
- li.selman.nullmarkeder.PackageInfoGeneratorTest.[engine:junit-jupiter]/[class:li.selman.nullmarkeder.PackageInfoGeneratorTest]/[method:addsAnnotationWhenPackageInfoHasNoPackageDeclaration(java.nio.file.Path)]
- li.selman.nullmarkeder.PackageInfoGeneratorTest.[engine:junit-jupiter]/[class:li.selman.nullmarkeder.PackageInfoGeneratorTest]/[method:preservesExistingPackageInfoCommentsAndAnnotationsWhenAddingNullMarked(java.nio.file.Path)]
- li.selman.nullmarkeder.PackageInfoGeneratorTest.[engine:junit-jupiter]/[class:li.selman.nullmarkeder.PackageInfoGeneratorTest]/[method:addsAnnotationToExistingPackageInfoMissingIt(java.nio.file.Path)]
|
| 100 |
|
1.1 Location : generateNewPackageInfo Killed by : none removed call to java/lang/System$Logger::log → SURVIVED
Covering tests
Covered by tests:
- li.selman.nullmarkeder.PackageInfoGeneratorTest.[engine:junit-jupiter]/[class:li.selman.nullmarkeder.PackageInfoGeneratorTest]/[method:generatesPackageInfoForBasePackageItselfWhenItHasNoSubpackages(java.nio.file.Path)]
- li.selman.nullmarkeder.PackageInfoGeneratorTest.[engine:junit-jupiter]/[class:li.selman.nullmarkeder.PackageInfoGeneratorTest]/[method:mainUsesProvidedRootDir(java.nio.file.Path)]
- li.selman.nullmarkeder.PackageInfoGeneratorTest.[engine:junit-jupiter]/[class:li.selman.nullmarkeder.PackageInfoGeneratorTest]/[method:isIdempotentForAlreadyAnnotatedPackage(java.nio.file.Path)]
- li.selman.nullmarkeder.PackageInfoGeneratorTest.[engine:junit-jupiter]/[class:li.selman.nullmarkeder.PackageInfoGeneratorTest]/[method:addsAnnotationWhenPackageInfoHasNoPackageDeclaration(java.nio.file.Path)]
- li.selman.nullmarkeder.PackageInfoGeneratorTest.[engine:junit-jupiter]/[class:li.selman.nullmarkeder.PackageInfoGeneratorTest]/[method:preservesExistingPackageInfoCommentsAndAnnotationsWhenAddingNullMarked(java.nio.file.Path)]
- li.selman.nullmarkeder.PackageInfoGeneratorTest.[engine:junit-jupiter]/[class:li.selman.nullmarkeder.PackageInfoGeneratorTest]/[method:generatesPackageInfoForPackageMissingOne(java.nio.file.Path)]
- li.selman.nullmarkeder.PackageInfoGeneratorTest.[engine:junit-jupiter]/[class:li.selman.nullmarkeder.PackageInfoGeneratorTest]/[method:addsAnnotationToExistingPackageInfoMissingIt(java.nio.file.Path)]
- li.selman.nullmarkeder.PackageInfoGeneratorTest.[engine:junit-jupiter]/[class:li.selman.nullmarkeder.PackageInfoGeneratorTest]/[method:leavesUnparsablePackageInfoUntouched(java.nio.file.Path)]
|
| 108 |
|
1.1 Location : findPackages Killed by : li.selman.nullmarkeder.PackageInfoGeneratorTest.[engine:junit-jupiter]/[class:li.selman.nullmarkeder.PackageInfoGeneratorTest]/[method:generatesPackageInfoForBasePackageItselfWhenItHasNoSubpackages(java.nio.file.Path)] replaced return value with Collections.emptyList for li/selman/nullmarkeder/PackageInfoGenerator::findPackages → KILLED
|
| 109 |
|
1.1 Location : lambda$findPackages$0 Killed by : li.selman.nullmarkeder.PackageInfoGeneratorTest.[engine:junit-jupiter]/[class:li.selman.nullmarkeder.PackageInfoGeneratorTest]/[method:generatesPackageInfoForBasePackageItselfWhenItHasNoSubpackages(java.nio.file.Path)] replaced boolean return with false for li/selman/nullmarkeder/PackageInfoGenerator::lambda$findPackages$0 → KILLED
2.2 Location : lambda$findPackages$0 Killed by : li.selman.nullmarkeder.PackageInfoGeneratorTest.[engine:junit-jupiter]/[class:li.selman.nullmarkeder.PackageInfoGeneratorTest]/[method:isIdempotentForAlreadyAnnotatedPackage(java.nio.file.Path)] replaced boolean return with true for li/selman/nullmarkeder/PackageInfoGenerator::lambda$findPackages$0 → KILLED
|
| 110 |
|
1.1 Location : lambda$findPackages$1 Killed by : li.selman.nullmarkeder.PackageInfoGeneratorTest.[engine:junit-jupiter]/[class:li.selman.nullmarkeder.PackageInfoGeneratorTest]/[method:mainUsesProvidedRootDir(java.nio.file.Path)] replaced return value with "" for li/selman/nullmarkeder/PackageInfoGenerator::lambda$findPackages$1 → KILLED
|
| 114 |
|
1.1 Location : lambda$findPackages$2 Killed by : li.selman.nullmarkeder.PackageInfoGeneratorTest.[engine:junit-jupiter]/[class:li.selman.nullmarkeder.PackageInfoGeneratorTest]/[method:generatesPackageInfoForBasePackageItselfWhenItHasNoSubpackages(java.nio.file.Path)] replaced return value with "" for li/selman/nullmarkeder/PackageInfoGenerator::lambda$findPackages$2 → KILLED
2.2 Location : lambda$findPackages$2 Killed by : li.selman.nullmarkeder.PackageInfoGeneratorTest.[engine:junit-jupiter]/[class:li.selman.nullmarkeder.PackageInfoGeneratorTest]/[method:generatesPackageInfoForBasePackageItselfWhenItHasNoSubpackages(java.nio.file.Path)] negated conditional → KILLED
|
| 126 |
|
1.1 Location : main Killed by : li.selman.nullmarkeder.PackageInfoGeneratorTest.[engine:junit-jupiter]/[class:li.selman.nullmarkeder.PackageInfoGeneratorTest]/[method:mainDefaultsRootDirToSrcMainJava()] changed conditional boundary → KILLED
2.2 Location : main Killed by : li.selman.nullmarkeder.PackageInfoGeneratorTest.[engine:junit-jupiter]/[class:li.selman.nullmarkeder.PackageInfoGeneratorTest]/[method:mainDefaultsRootDirToSrcMainJava()] negated conditional → KILLED
|
| 131 |
|
1.1 Location : main Killed by : li.selman.nullmarkeder.PackageInfoGeneratorTest.[engine:junit-jupiter]/[class:li.selman.nullmarkeder.PackageInfoGeneratorTest]/[method:mainDefaultsRootDirToSrcMainJava()] negated conditional → KILLED
2.2 Location : main Killed by : li.selman.nullmarkeder.PackageInfoGeneratorTest.[engine:junit-jupiter]/[class:li.selman.nullmarkeder.PackageInfoGeneratorTest]/[method:mainDefaultsRootDirToSrcMainJava()] changed conditional boundary → KILLED
|
| 132 |
|
1.1 Location : main Killed by : li.selman.nullmarkeder.PackageInfoGeneratorTest.[engine:junit-jupiter]/[class:li.selman.nullmarkeder.PackageInfoGeneratorTest]/[method:mainDefaultsRootDirToSrcMainJava()] removed call to li/selman/nullmarkeder/PackageInfoGenerator::updatePackageInfoFiles → KILLED
|