/** * BSD-style license; for more info see http://pmd.sourceforge.net/license.html */ package net.sourceforge.pmd.lang.apex.ast; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.List; import java.util.stream.Collectors; import org.apache.commons.lang3.reflect.FieldUtils; import apex.jorje.semantic.ast.visitor.AdditionalPassScope; import apex.jorje.semantic.ast.visitor.AstVisitor; import apex.jorje.semantic.compiler.ApexCompiler; import apex.jorje.semantic.compiler.CodeUnit; import apex.jorje.semantic.compiler.CompilationInput; import apex.jorje.semantic.compiler.CompilerContext; import apex.jorje.semantic.compiler.CompilerOperation; import apex.jorje.semantic.compiler.CompilerStage; import apex.jorje.semantic.compiler.SourceFile; import apex.jorje.semantic.compiler.sfdc.AccessEvaluator; import apex.jorje.semantic.compiler.sfdc.QueryValidator; import apex.jorje.semantic.compiler.sfdc.SymbolProvider; import apex.jorje.semantic.tester.EmptySymbolProvider; import apex.jorje.semantic.tester.TestAccessEvaluator; import apex.jorje.semantic.tester.TestQueryValidators; import com.google.common.collect.ImmutableList; /** * Central point for interfacing with the compiler. Based on <a href= * "https://github.com/forcedotcom/idecore/blob/master/com.salesforce.ide.apex.core/src/com/salesforce/ide/apex/internal/core/CompilerService.java" * > CompilerService</a> but with Eclipse dependencies removed. * * @author nchen * */ public class CompilerService { public static final CompilerService INSTANCE = new CompilerService(); private final SymbolProvider symbolProvider; private final AccessEvaluator accessEvaluator; private QueryValidator queryValidator; /** * Configure a compiler with the default configurations: * * @param symbolProvider * EmptySymbolProvider, doesn't provide any symbols that are not * part of source. * @param accessEvaluator * TestAccessEvaluator, doesn't provide any validation. * @param queryValidator * TestQueryValidators.Noop, no validation of queries. */ CompilerService() { this(EmptySymbolProvider.get(), new TestAccessEvaluator(), new TestQueryValidators.Noop()); } /** * Configure a compiler with the following configurations: * * @param symbolProvider * A way to retrieve symbols, where symbols are names of types. * @param accessEvaluator * A way to check for accesses to certain fields in types. * @param queryValidator * A way to validate your queries. */ public CompilerService(SymbolProvider symbolProvider, AccessEvaluator accessEvaluator, QueryValidator queryValidator) { this.symbolProvider = symbolProvider; this.accessEvaluator = accessEvaluator; this.queryValidator = queryValidator; } public ApexCompiler visitAstFromString(String source, AstVisitor<AdditionalPassScope> visitor) { return visitAstsFromStrings(ImmutableList.of(source), visitor, CompilerStage.POST_TYPE_RESOLVE); } public ApexCompiler visitAstsFromStrings(List<String> sources, AstVisitor<AdditionalPassScope> visitor) { return visitAstsFromStrings(sources, visitor, CompilerStage.POST_TYPE_RESOLVE); } public ApexCompiler visitAstsFromStrings(List<String> sources, AstVisitor<AdditionalPassScope> visitor, CompilerStage compilerStage) { List<SourceFile> sourceFiles = sources.stream().map(s -> SourceFile.builder().setBody(s).build()) .collect(Collectors.toList()); CompilationInput compilationUnit = createCompilationInput(sourceFiles, visitor); return compile(compilationUnit, visitor, compilerStage); } private ApexCompiler compile(CompilationInput compilationInput, AstVisitor<AdditionalPassScope> visitor, CompilerStage compilerStage) { ApexCompiler compiler = ApexCompiler.builder().setInput(compilationInput).build(); compiler.compile(compilerStage); callAdditionalPassVisitor(compiler); return compiler; } private CompilationInput createCompilationInput(List<SourceFile> sourceFiles, AstVisitor<AdditionalPassScope> visitor) { return new CompilationInput(sourceFiles, symbolProvider, accessEvaluator, queryValidator, visitor); } /** * This is temporary workaround to bypass the validation stage of the * compiler while *still* doing the additional_validate stage. We are * bypassing the validation stage because it does a deep validation that we * don't have all the parts for yet in the offline compiler. Rather than * stop all work on that, we bypass it so that we can still do useful things * like find all your types, find all your methods, etc. * */ @SuppressWarnings("unchecked") private void callAdditionalPassVisitor(ApexCompiler compiler) { try { List<CodeUnit> allUnits = (List<CodeUnit>) FieldUtils.readDeclaredField(compiler, "allUnits", true); CompilerContext compilerContext = (CompilerContext) FieldUtils.readDeclaredField(compiler, "compilerContext", true); for (CodeUnit unit : allUnits) { Method getOperation = CompilerStage.ADDITIONAL_VALIDATE.getDeclaringClass() .getDeclaredMethod("getOperation"); getOperation.setAccessible(true); CompilerOperation operation = (CompilerOperation) getOperation .invoke(CompilerStage.ADDITIONAL_VALIDATE); operation.invoke(compilerContext, unit); } } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { } } }