如何以编程方式验证Java代码?

问题描述

鉴于源代码和Java版本,我需要能够验证代码是否可以编译。如果代码无法编译,则需要能够在源代码中返回错误

以下解决方案有效,但仅适用于计算机上当前正在使用的Java版本。

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,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;
        }
    }
}

反正我可以实现以下功能吗?

public final boolean compiles(Set<File> srcFiles,SourceVersion version) {...}

解决方法

此解决方案适用的版本范围似乎是特定于编译器的,但是对于OpenJDK 11和15,我注意到该解决方案适用于[7,SYSTEM_COMPILER_VERSION]

JavaCompiler API允许您在调用方法Iterable时将命令行选项作为JavaCompiler.CompilationTask::getTask传递,因此您可以传递List.of("--release","<version>"),其中<version>被您正在验证的版本

考虑到这一点,解决方案变为

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),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;
        }
    }
}