3

Given the source code and the Java version, I need to be able to validate whether or not the code will compile. If the code does not compile, I need to be able to return the errors within the source code.

The following solution works, but only for the Java version currently being used on your machine.

import javax.tools.*;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

public class Validator {
    private final JavaCompiler COMPILER = ToolProvider.getSystemJavaCompiler();
    
    //Assume srcFiles are java files that can be read
    public final boolean compiles(Set<File> srcFiles) throws IOException {

        //Convert files to JavaCompiler API compatible files
        List<JavaFileObject> compilationUnits = new ArrayList<>();
        for (File file : srcFiles) {
            CompilableFile compilableFile = new CompilableFile(file.getName(), Files.readString(file.toPath()));
            compilationUnits.add(compilableFile);
        }

        DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();
        JavaCompiler.CompilationTask task = COMPILER.getTask(null, null, diagnosticCollector, null, null, compilationUnits);
        boolean success = task.call();

        //Displaying the info of each warning, error, etc
        diagnosticCollector.getDiagnostics().forEach(Validator::printDiagnostic);

        return success;
    }

    private static void printDiagnostic(Diagnostic<?> diagnostic) {
        System.out.println(diagnostic.getCode());
        System.out.println(diagnostic.getKind());
        System.out.println(diagnostic.getPosition());
        System.out.println(diagnostic.getStartPosition());
        System.out.println(diagnostic.getEndPosition());
        System.out.println(diagnostic.getSource());
        System.out.println(diagnostic.getMessage(null));
    }

    /**
     * Instances of this class can be compiled with the JavaCompiler API
     */
    private static final class CompilableFile extends SimpleJavaFileObject {
        final String code;

        CompilableFile(String name, String code) {
            super(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
            this.code = code;
        }

        @Override
        public CharSequence getCharContent(boolean ignoreEncodingErrors) {
            return code;
        }
    }
}

Is there anyway I could implement the following function?

public final boolean compiles(Set<File> srcFiles, SourceVersion version) {...}
Lindstorm
  • 890
  • 2
  • 7
  • 22
  • Please see: [Why is “Is it possible to…” a poorly worded question?](https://softwareengineering.meta.stackexchange.com/q/7273). – Progman Sep 25 '20 at 16:22
  • 1
    @Abra I do not see how that is relevant. I am just trying to validate code, I do not necessarily need to compile it, theoretically I could implement a Java 10 compiler using Java 9, and then use that to verify my code. Even so, you can compile Java 10 code using Java 9 if you have a tools.jar file. See [here](https://stackoverflow.com/a/53292431/9372934). Further, if I have a Java 15 compiler, that would not be able to verify all Java code before it because I could give it a valid Java 15 program that would not work in versions previously. – Lindstorm Sep 25 '20 at 16:30
  • @Progman My question isnt in that form? How should I rephrase it then? I dont see any other way to ask how to do something – Lindstorm Sep 25 '20 at 16:32
  • Never tried to implement my own but did you try to set `source` option ? I saw that your compiler options are set to null. Maybe there's a way to set `source` compiler option this way ? – IQbrod Sep 25 '20 at 16:59
  • @Abra - Perhaps I misunderstand your comment, but it seems like nonsense. There is nothing to bar a Java 9 implementation of a compiler for the Java 10 language. For that matter, you could even do it using Java 1. All versions of Java have been _general purpose_ programming languages, meaning they can be used to implement pretty much any well defined algorithm. – Ted Hopp Sep 25 '20 at 17:13
  • 1
    @IQbrod Thank you! Even though passing the `-source` option didn't work, investigating it lead me to try passing the `--release` option, which did work – Lindstorm Sep 26 '20 at 00:18

1 Answers1

1

The range of version that this solution works on seem to be compiler specific, but for OpenJDK 11 and 15, I noticed this solution worked for [7, SYSTEM_COMPILER_VERSION]

The JavaCompiler API allows you to pass command line options as an Iterable when you call the method JavaCompiler.CompilationTask::getTask, so you can pass List.of("--release", "<version>") where <version> is replaced by the version you are verifying

With this in mind, the solution becomes

import javax.tools.*;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

public class Validator {
    private final JavaCompiler COMPILER = ToolProvider.getSystemJavaCompiler();
    
    //Assume srcFiles are java files that can be read
    public final boolean compiles(Set<File> srcFiles, String javaVersion) throws IOException {

        //Convert files to JavaCompiler API compatible files
        List<JavaFileObject> compilationUnits = new ArrayList<>();
        for (File file : srcFiles) {
            CompilableFile compilableFile = new CompilableFile(file.getName(), Files.readString(file.toPath()));
            compilationUnits.add(compilableFile);
        }

        DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<>();
        JavaCompiler.CompilationTask task = COMPILER.getTask(null, null, diagnosticCollector, List.of("--release", javaVersion), null, compilationUnits);
        boolean success = task.call();

        //Displaying the info of each warning, error, etc
        diagnosticCollector.getDiagnostics().forEach(Validator::printDiagnostic);

        return success;
    }

    private static void printDiagnostic(Diagnostic<?> diagnostic) {
        System.out.println(diagnostic.getCode());
        System.out.println(diagnostic.getKind());
        System.out.println(diagnostic.getPosition());
        System.out.println(diagnostic.getStartPosition());
        System.out.println(diagnostic.getEndPosition());
        System.out.println(diagnostic.getSource());
        System.out.println(diagnostic.getMessage(null));
    }

    /**
     * Instances of this class can be compiled with the JavaCompiler API
     */
    private static final class CompilableFile extends SimpleJavaFileObject {
        final String code;

        CompilableFile(String name, String code) {
            super(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE);
            this.code = code;
        }

        @Override
        public CharSequence getCharContent(boolean ignoreEncodingErrors) {
            return code;
        }
    }
}


Lindstorm
  • 890
  • 2
  • 7
  • 22