/* * Copyright (c) 2012 Sam Harwell, Tunnel Vision Laboratories LLC * All rights reserved. * * The source code of this document is proprietary work, and is not licensed for * distribution. For information about licensing, contact Sam Harwell at: * sam@tunnelvisionlabs.com */ package org.antlr.works.editor.grammar.actions; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.io.PrintStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import javax.swing.text.StyledDocument; import javax.tools.JavaCompiler; import javax.tools.JavaFileObject; import javax.tools.StandardJavaFileManager; import javax.tools.ToolProvider; import org.antlr.netbeans.editor.text.DocumentSnapshot; import org.antlr.netbeans.editor.text.VersionedDocument; import org.antlr.netbeans.editor.text.VersionedDocumentUtilities; import org.antlr.netbeans.parsing.spi.ParserData; import org.antlr.netbeans.parsing.spi.ParserTaskManager; import org.antlr.netbeans.util.NotificationIcons; import org.antlr.v4.runtime.misc.TestRig; import org.antlr.works.editor.grammar.GrammarDataObject; import org.antlr.works.editor.grammar.GrammarEditorKit; import org.antlr.works.editor.grammar.GrammarParserDataDefinitions; import org.antlr.works.editor.grammar.codegen.CodeGenerator; import org.antlr.works.editor.grammar.codegen.CodeGenerator.OutputWriterStream; import org.antlr.works.editor.grammar.codemodel.CodeElementPositionRegion; import org.antlr.works.editor.grammar.codemodel.FileModel; import org.antlr.works.editor.grammar.codemodel.RuleKind; import org.antlr.works.editor.grammar.codemodel.RuleModel; import org.antlr.works.editor.grammar.codemodel.TokenVocabDeclarationModel; import org.antlr.works.editor.grammar.codemodel.TokenVocabModel; import org.antlr.works.editor.grammar.codemodel.impl.FileVocabModelImpl; import org.netbeans.api.annotations.common.CheckForNull; import org.netbeans.api.annotations.common.NonNull; import org.netbeans.api.annotations.common.NullAllowed; import org.openide.DialogDisplayer; import org.openide.WizardDescriptor; import org.openide.awt.ActionID; import org.openide.awt.ActionReference; import org.openide.awt.ActionRegistration; import org.openide.awt.NotificationDisplayer; import org.openide.cookies.EditorCookie; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; import org.openide.loaders.DataObject; import org.openide.util.Exceptions; import org.openide.util.Lookup; import org.openide.util.NbBundle.Messages; import org.openide.util.Task; import org.openide.util.Utilities; import org.openide.windows.IOProvider; import org.openide.windows.InputOutput; import org.openide.windows.OutputWriter; @ActionID( category = "Debug", id = "org.antlr.works.editor.grammar.actions.RunInTestRigAction") @ActionRegistration( displayName = "#CTL_RunInTestRigAction") @ActionReference(path = "Menu/BuildProject", position = -5) @Messages("CTL_RunInTestRigAction=Run in TestRig...") public final class RunInTestRigAction implements ActionListener { private final DataObject context; public RunInTestRigAction(DataObject context) { this.context = context; } @Override public void actionPerformed(ActionEvent ev) { if (context == null) { displayError("This command is only valid in the context of a file."); return; } if (!(context instanceof GrammarDataObject)) { displayError("This command is only valid for ANTLR grammar files."); return; } EditorCookie editorCookie = context.getLookup().lookup(EditorCookie.class); if (editorCookie != null) { StyledDocument document = editorCookie.getDocument(); if (document != null && GrammarEditorKit.isLegacyMode(document)) { displayError("This command is not valid in legacy (ANTLR 3) mode."); return; } } FileObject fileObject = context.getPrimaryFile(); if (fileObject == null) { displayError("No FileObject is available for the DataObject"); return; } if (fileObject.hasExt("g") || fileObject.hasExt("g3")) { displayError("ANTLR grammar files ending in *.g and *.g3 default to legacy (ANTLR 3) mode. If this is an ANTLR 4 grammar, open the file and uncheck the Legacy Mode button on the toolbar before running TestRig."); return; } WizardDescriptor wizard = new WizardDescriptor(new RunInTestRigWizardIterator()); wizard.setTitle("{0} ({1})"); wizard.setTitle("Run in TestRig"); FileModel fileModel = getFileModel(fileObject); List<String> availableRules = getAvailableRules(fileModel); if (fileModel != null) { String initialStartRule = RunInTestRigAction.getFirstRule(fileModel, true); wizard.putProperty(RunInTestRigWizardPanel.START_RULE, initialStartRule); } wizard.putProperty(RunInTestRigWizardPanel.AVAILABLE_RULES, availableRules.toArray(new String[availableRules.size()])); if (DialogDisplayer.getDefault().notify(wizard) == WizardDescriptor.FINISH_OPTION) { File inputFile = new File(RunInTestRigWizardOptions.getInputFile(wizard)); if (!inputFile.isFile()) { return; } String baseGrammarName = fileObject.getName(); if (fileModel != null) { if (baseGrammarName.endsWith("Parser")) { boolean containsLexerRule = false; for (RuleModel rule : fileModel.getRules()) { if (rule.getRuleKind() == RuleKind.LEXER) { containsLexerRule = true; break; } } if (!containsLexerRule) { baseGrammarName = baseGrammarName.substring(0, baseGrammarName.length() - "Parser".length()); } } else if (baseGrammarName.endsWith("Lexer")) { boolean containsParserRule = false; for (RuleModel rule : fileModel.getRules()) { if (rule.getRuleKind() == RuleKind.PARSER) { containsParserRule = true; break; } } if (!containsParserRule) { baseGrammarName = baseGrammarName.substring(0, baseGrammarName.length() - "Lexer".length()); } } } List<FileObject> dependencies = getDependencies(fileModel); TestRigTask task = new TestRigTask(baseGrammarName, fileObject, dependencies, inputFile); task.startRule = RunInTestRigWizardOptions.getStartRule(wizard); task.encodingSpecified = RunInTestRigWizardOptions.isEncodingSpecified(wizard); task.encoding = RunInTestRigWizardOptions.getEncoding(wizard); task.showTokens = RunInTestRigWizardOptions.isShowTokens(wizard); task.showTree = RunInTestRigWizardOptions.isShowTree(wizard); task.showTreeInGUI = RunInTestRigWizardOptions.isShowTreeInGUI(wizard); CodeGenerator.REFERENCE_RP.post(task); } } private void displayError(String message) { NotificationDisplayer.getDefault().notify("Run in TestRig", NotificationIcons.ERROR, message, null); } @CheckForNull public static FileModel getFileModel(FileObject fileObject) { VersionedDocument versionedDocument = VersionedDocumentUtilities.getVersionedDocument(fileObject); DocumentSnapshot snapshot = versionedDocument.getCurrentSnapshot(); return getFileModel(snapshot); } @CheckForNull public static FileModel getFileModel(DocumentSnapshot snapshot) { ParserTaskManager parserTaskManager = Lookup.getDefault().lookup(ParserTaskManager.class); Future<ParserData<FileModel>> futureData = parserTaskManager.getData(snapshot, GrammarParserDataDefinitions.FILE_MODEL); if (futureData == null) { return null; } ParserData<FileModel> data; try { data = futureData.get(); } catch (InterruptedException | ExecutionException ex) { return null; } if (data == null) { return null; } return data.getData(); } @CheckForNull public static String getFirstRule(@NonNull FileModel fileModel, boolean preferExplicitEof) { List<RuleModel> rules = new ArrayList<>(fileModel.getRules()); Collections.sort(rules, new Comparator<RuleModel>() { @Override public int compare(RuleModel o1, RuleModel o2) { return Integer.compare(getStart(o1), getStart(o2)); } private int getStart(RuleModel ruleModel) { if (ruleModel == null) { return Integer.MAX_VALUE; } CodeElementPositionRegion seek = ruleModel.getSeek(); if (seek == null) { return Integer.MAX_VALUE; } return seek.getOffsetRegion().getStart(); } }); RuleModel firstRule = null; for (RuleModel ruleModel : rules) { if (ruleModel.getRuleKind() != RuleKind.PARSER) { continue; } if (firstRule == null) { firstRule = ruleModel; } if (ruleModel.hasExplicitEof()) { // no need to keep looking after we get to an explicit EOF rule return ruleModel.getName(); } if (!preferExplicitEof) { break; } } if (firstRule != null) { return firstRule.getName(); } return null; } @NonNull public static List<String> getAvailableRules(@NullAllowed FileModel fileModel) { if (fileModel == null) { return Collections.emptyList(); } Set<String> set = new HashSet<>(); for (RuleModel rule : fileModel.getRules()) { if (rule.getRuleKind() != RuleKind.PARSER) { continue; } set.add(rule.getName()); } List<String> result = new ArrayList<>(set); Collections.sort(result); return result; } @NonNull private static List<FileObject> getDependencies(@NullAllowed FileModel fileModel) { if (fileModel == null) { return Collections.emptyList(); } List<FileObject> result = new ArrayList<>(); for (TokenVocabDeclarationModel tokenVocabDeclarationModel : fileModel.getTokenVocabDeclaration()) { for (TokenVocabModel tokenVocabModel : tokenVocabDeclarationModel.resolve()) { if (!(tokenVocabModel instanceof FileVocabModelImpl)) { continue; } FileVocabModelImpl fileVocabModelImpl = (FileVocabModelImpl)tokenVocabModel; result.add(fileVocabModelImpl.getFile().getFileObject()); } } return result; } private static class TestRigTask implements Runnable { public String startRule; public boolean encodingSpecified; public String encoding; public boolean showTokens; public boolean showTree; public boolean showTreeInGUI; private final String baseGrammarName; private final FileObject grammarFile; private final List<FileObject> dependencies; private final File inputFile; public TestRigTask(String baseGrammarName, FileObject grammarFile, List<FileObject> dependencies, File inputFile) { this.baseGrammarName = baseGrammarName; this.grammarFile = grammarFile; this.dependencies = dependencies; this.inputFile = inputFile; } @Override public void run() { try { File tmpdir = new File(System.getProperty("java.io.tmpdir"), getClass().getSimpleName() + "-" + System.currentTimeMillis()); tmpdir.mkdir(); List<FileObject> compiled = new ArrayList<>(); compiled.add(grammarFile); compiled.addAll(dependencies); CodeGenerator codeGenerator = new CodeGenerator("Java", compiled.toArray(new FileObject[compiled.size()])); codeGenerator.outputDirectory = FileUtil.toFileObject(tmpdir); codeGenerator.libDirectory = grammarFile.getParent(); Task codeGenerationTask = codeGenerator.run(); codeGenerationTask.waitFinished(); InputOutput inputOutput = IOProvider.getDefault().getIO("ANTLR TestRig (Java)", false); inputOutput.select(); try (OutputWriter outputWriter = inputOutput.getOut(); OutputWriter errorWriter = inputOutput.getErr()) { outputWriter.println("Compiling grammar files..."); File[] files = tmpdir.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.endsWith(".java"); } }); JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null); Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromFiles(Arrays.asList(files)); List<String> compileOptions = new ArrayList<>(); compileOptions.add("-g"); compileOptions.add("-d"); compileOptions.add(tmpdir.getAbsolutePath()); compileOptions.add("-cp"); compileOptions.add(CodeGenerator.getReferenceLibrary().getAbsolutePath()); compileOptions.add("-Xlint"); compileOptions.add("-Xlint:-serial"); JavaCompiler.CompilationTask task = compiler.getTask(errorWriter, fileManager, null, compileOptions, null, compilationUnits); task.call(); try { fileManager.close(); } catch (IOException ioe) { Exceptions.printStackTrace(ioe); return; } final ClassLoader loader = new URLClassLoader(new URL[]{Utilities.toURI(tmpdir).toURL()}, CodeGenerator.getReferenceClassLoader()); Class<?> testRig = loader.loadClass(TestRig.class.getName()); Method mainMethod = testRig.getMethod("main", String[].class); List<String> testRigArguments = new ArrayList<>(); testRigArguments.add(baseGrammarName); testRigArguments.add(startRule); if (encodingSpecified && encoding != null && !encoding.isEmpty()) { testRigArguments.add("-encoding"); testRigArguments.add(encoding); } if (showTokens) { testRigArguments.add("-tokens"); } if (showTree) { testRigArguments.add("-tree"); } if (showTreeInGUI) { testRigArguments.add("-gui"); } testRigArguments.add(inputFile.getAbsolutePath()); outputWriter.println("Arguments: " + testRigArguments); ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(loader); try { PrintStream originalOut = System.out; System.setOut(new PrintStream(new OutputWriterStream(outputWriter))); try { PrintStream originalErr = System.err; System.setErr(new PrintStream(new OutputWriterStream(errorWriter))); try { mainMethod.invoke(null, (Object)testRigArguments.toArray(new String[testRigArguments.size()])); } finally { System.setErr(originalErr); } } finally { System.setOut(originalOut); } } finally { Thread.currentThread().setContextClassLoader(contextClassLoader); } } } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | MalformedURLException ex) { Exceptions.printStackTrace(ex); } } } }