Skip to content

Commit

Permalink
Checkpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
mernst committed Mar 7, 2024
1 parent 9b266ec commit e32c4f2
Show file tree
Hide file tree
Showing 2 changed files with 216 additions and 22 deletions.
25 changes: 25 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ repositories {
dependencies {
implementation 'com.github.javaparser:javaparser-core:3.25.8'
implementation 'org.plumelib:options:2.0.3'
implementation 'org.plumelib:plume-util:1.9.0'
implementation 'com.google.code.gson:gson:2.10.1'
}

Expand Down Expand Up @@ -70,13 +71,21 @@ dependencies {
tasks.withType(JavaCompile).configureEach {
// "-processing" avoids javac warning "No processor claimed any of these annotations".
options.compilerArgs << '-Xlint:all,-processing' << '-Werror'
// Only needed when debugging.
options.compilerArgs << '-g'
options.errorprone {
// disable('ReferenceEquality') // Use Interning Checker instead.
// disable('StringSplitter') // Obscure case isn't likely.
// disable('AnnotateFormatMethod') // Error Prone doesn't know about Checker Framework @FormatMethod
}
}

compileJava {
options.compilerArgs += '--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED'
options.compilerArgs += '--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED'
options.compilerArgs += '--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED'
}

/// Checker Framework pluggable type-checking

apply plugin: 'org.checkerframework'
Expand Down Expand Up @@ -129,6 +138,22 @@ javadoc {
options.addStringOption('Xwerror', '-Xdoclint:all')
options.addStringOption('private', '-quiet')
options.addStringOption('source', '11')
// Buggy per https://github.com/java9-modularity/gradle-modules-plugin/issues/170
// moduleOptions {
// addExports = [
// 'jdk.compiler/com.sun.tools.javac.parser' : 'ALL-UNNAMED',
// 'jdk.compiler/com.sun.tools.javac.tree' : ' ALL-UNNAMED' ,
// 'jdk.compiler/com.sun.tools.javac.util':'ALL-UNNAMED'
// ]
// }
// Workaround until bug is fixed.
options {
addMultilineStringsOption("-add-exports").setValue([
'jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED',
'jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED',
'jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED'
])
}
doLast {
ant.replaceregexp(match:"@import url\\('resources/fonts/dejavu.css'\\);\\s*", replace:'',
flags:'g', byline:true) {
Expand Down
213 changes: 191 additions & 22 deletions src/main/java/org/plumelib/javadoc/LinesInChangedMethods.java
Original file line number Diff line number Diff line change
@@ -1,23 +1,53 @@
package org.plumelib.javadoc;

import static com.sun.tools.javac.tree.JCTree.JCClassDecl;
import static com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
import static com.sun.tools.javac.tree.JCTree.JCMethodDecl;
import static com.sun.tools.javac.util.Log.DiscardDiagnosticHandler;
import static java.nio.charset.StandardCharsets.UTF_8;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.parser.JavacParser;
import com.sun.tools.javac.parser.ParserFactory;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.DiagnosticSource;
import com.sun.tools.javac.util.Log;
import com.sun.tools.javac.util.Options;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Type;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.tools.DiagnosticCollector;
import javax.tools.DiagnosticListener;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.signedness.qual.Signed;
import org.plumelib.util.CollectionsPlume;
import org.plumelib.util.FilesPlume;

/**
* This program takes as input a map (filename &rarr; changed lines) for lines that were modified in
* This program takes as input a map (filename &rarr; changed lines) for lines that were changed in
* an edit. It returns a map (filename &rarr; changed lines) for lines that implement changed
* methods.
*
Expand All @@ -36,6 +66,9 @@
*/
public class LinesInChangedMethods {

/** The log. */
static Log log;

/** Creates a LinesInChangedMethods. */
public LinesInChangedMethods() {
throw new Error("do not instantiate");
Expand All @@ -50,14 +83,19 @@ public LinesInChangedMethods() {
/**
* Implements the logic of the class; see class Javadoc.
*
* @param args command-line arguments: input filename and output filename
* @param args command-line arguments: input filename and output filename. Each one is a JSON
* file.
* @throws IOException if there is IO trouble
*/
public static void main(String[] args) {
public static void main(String[] args)
// temporary, for debugging
throws IOException {

if (args.length != 2) {
System.err.printf(
"LinesInChangedMethods received %d arguments: %s%n", args.length, Arrays.toString(args));
"LinesInChangedMethods expects two arguments: input filename and output filename.%n");
System.err.printf(
"LinesInChangedMethods expects two arguments: input filename and output filename.");
"LinesInChangedMethods received %d arguments: %s%n", args.length, Arrays.toString(args));
System.exit(1);
}

Expand All @@ -68,33 +106,164 @@ public static void main(String[] args) {
System.exit(1);
}

@NonNull Map<String, Set<Integer>> map;
// A map from filename to all the lines that are within the method.
@NonNull Map<String, List<Integer>> changedLines;
try (BufferedReader bufferedReader =
Files.newBufferedReader(Paths.get(infileName), StandardCharsets.UTF_8);
JsonReader jsonReader = new JsonReader(bufferedReader)) {
Type mapType = new TypeToken<Map<String, Set<Integer>>>() {}.getType();
Type mapType = new TypeToken<Map<String, List<Integer>>>() {}.getType();
@SuppressWarnings("nullness") // Gson is not annotated
@NonNull Map<String, Set<Integer>> son = new Gson().fromJson(jsonReader, mapType);
map = son;
@NonNull Map<String, List<Integer>> changedLinesTmp =
new Gson().fromJson(jsonReader, mapType);
changedLines = changedLinesTmp;
} catch (Throwable t) {
throw new Error("Problem reading " + infileName, t);
}

Map<String, List<Integer>> methodLines = new HashMap<>();
for (Map.Entry<String, List<Integer>> entry : changedLines.entrySet()) {
String filename = entry.getKey();
List<Integer> fileChangedLines = entry.getValue();

if (!new File(filename).exists()) {
System.err.printf("File %s mentioned in %s does not exist.%n", filename, infileName);
System.exit(1);
}
if (!new File(filename).canRead()) {
System.err.printf(
"File %s mentioned in %s exists but cannot be read.%n", filename, infileName);
System.exit(1);
}

if (filename.endsWith(".java")) {
methodLines.put(filename, changedLinesToMethodLines(filename, fileChangedLines));
} else {
methodLines.put(filename, fileChangedLines);
}
}

// For debugging
System.out.println(outfileName);
System.out.println(mapToString(map));
System.out.println(CollectionsPlume.mapToString(changedLines));

String json = new Gson().toJson(methodLines);

try (Writer fw = Files.newBufferedWriter(Paths.get(outfileName), UTF_8);
BufferedWriter writer = new BufferedWriter(fw)) {
writer.write(json);
} catch (Throwable t) {
throw new Error("Problem writing " + outfileName, t);
}
}

/**
* For each input line that is in a method, put all the method's lines in the output. Otherwise,
* put the input line in the output directly.
*
* @param filename a file
* @param changedLines a set of lines in the file
* @return all the lines of all the methods that contain a changed line
* @throws IOException if there is IO trouble
*/
static List<Integer> changedLinesToMethodLines(String filename, List<Integer> changedLines)
// temporary, for debugging
throws IOException {

List<Integer> result = (List<Integer>) new ArrayList<Integer>();

Collections.sort(changedLines);

JCCompilationUnit cu = parseJavaFile(filename);
// TODO: I should use a visitor and override visitMethod, which will also find (for example)
// classes nested within methods.
for (JCTree def : cu.defs) {
JavaFileObject jfo = cu.getSourceFile();
DiscardDiagnosticHandler ddh = new DiscardDiagnosticHandler(null);
DiagnosticSource ds = new DiagnosticSource(jfo, log);
if (def.getKind() == Tree.Kind.CLASS) {
JCClassDecl classDecl = (JCClassDecl) def;
for (JCTree member : classDecl.getMembers()) {
if (member.getKind() == Tree.Kind.METHOD) {
JCMethodDecl methodDecl = (JCMethodDecl) member;
int startLine = ds.getLineNumber(methodDecl.getStartPosition());
// TODO: I need this here: private final SourcePositions sourcePositions;
// I can get it from a Trees, but how do I get that?

int endLine = ds.getLineNumber(methodDecl.getEndPosition(cu.endPositions));

while (changedLines.get(0) < startLine) {
result.add(changedLines.get(0));
changedLines.remove(0);
}
while (changedLines.get(0) <= endLine) {
for (int i = startLine; i <= endLine; i++) {
result.add(i);
}
}
while (changedLines.get(0) <= endLine) {
changedLines.remove(0);
}
}
}
}
}
for (int i : changedLines) {
result.add(i);
}
return result;
}

/**
* Convert a map to a string.
* Parse a Java file.
*
* @param map a map
* @return the string version of the map
* @param javaFilename the Java file to parse
* @return the compilation unit for the file
* @throws IOException if there is IO trouble
*/
public static String mapToString(Map<String, @Signed ?> map) {
String mapAsString =
map.keySet().stream()
.map(key -> key + "=" + map.get(key))
.collect(Collectors.joining(", ", "{", "}"));
return mapAsString;
@SuppressWarnings("mustcall:type.arguments.not.inferred") // context.put()
static JCCompilationUnit parseJavaFile(String javaFilename)
// temporary, for debugging
throws IOException {

Context context = new Context();

// TODO: Log has protected access.
log = new Log(context);
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
context.put(DiagnosticListener.class, diagnostics);

// These two variables are only used when constructing `fm`.
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
@SuppressWarnings("builder:required.method.not.called") // Don't close the standard file manager
StandardJavaFileManager fm = compiler.getStandardFileManager(diagnostics, null, null);
context.put(JavaFileManager.class, fm);

Options.instance(context).put("allowStringFolding", "false");
Options.instance(context).put("--enable-preview", "true");

/* The contents of the file. */
String fileContent = FilesPlume.readFile(new File(javaFilename));
// Cannot just call `new SimpleJavaFileObject()` because it has protected access.
Path javaFilePath = Paths.get(javaFilename).toAbsolutePath();
SimpleJavaFileObject source =
new SimpleJavaFileObject(URI.create("file://" + javaFilePath), JavaFileObject.Kind.SOURCE) {

@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
return fileContent;
}
};
Log.instance(context).useSource(source);

JavacParser parser =
ParserFactory.instance(context)
.newParser(
javaFilename,
/* keepDocComments= */ true,
/* keepEndPos= */ true,
/* keepLineMap= */ true);
JCCompilationUnit result = parser.parseCompilationUnit();
result.sourcefile = source;
return result;
}
}

0 comments on commit e32c4f2

Please sign in to comment.