Java tutorial
/******************************************************************************* * Copyright (c) 2008 Scott Stanchfield, based on ANTLR-Eclipse plugin * by Torsten Juergeleit. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors * Torsten Juergeleit - original ANTLR Eclipse plugin * Scott Stanchfield - modifications for ANTXR *******************************************************************************/ package com.javadude.antxr.eclipse.core.builder; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.PrintStream; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.resources.IResourceDeltaVisitor; import org.eclipse.core.resources.IResourceVisitor; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.IncrementalProjectBuilder; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.QualifiedName; import org.eclipse.jdt.core.IClasspathEntry; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.JavaCore; import com.javadude.antxr.AntxrTool; import com.javadude.antxr.eclipse.core.AntxrCorePlugin; import com.javadude.antxr.eclipse.core.AntxrNature; import com.javadude.antxr.eclipse.core.properties.SettingsPersister; /** * An eclipse builder to compile ANTXR grammars */ public class AntxrBuilder extends IncrementalProjectBuilder implements IStreamListener { /** the builder id */ public static final String BUILDER_ID = AntxrCorePlugin.PLUGIN_ID + ".antxrbuilder"; /** the builder debug id */ public static final String DEBUG_OPTION = AntxrCorePlugin.PLUGIN_ID + "/builder/debug"; /** are we debugging the builder? */ public static boolean DEBUG = false; private IFile fFile; private String fOutput; private PrintStream fOriginalOut; private PrintStream fOriginalErr; /** the persistent option that indicates that a generated file should have warnings cleared */ public static final QualifiedName CLEAN_WARNINGS = new QualifiedName(AntxrCorePlugin.PLUGIN_ID, SettingsPersister.CLEAN_WARNINGS); /** the persistent option that indicates that a generated file should have warnings cleared */ public static final QualifiedName INSTALL_SMAP = new QualifiedName(AntxrCorePlugin.PLUGIN_ID, SettingsPersister.SMAP_PROPERTY); /** the persistent option that attaches a grammar name to a file it generated */ public static final QualifiedName GRAMMAR_ECLIPSE_PROPERTY = new QualifiedName(AntxrCorePlugin.PLUGIN_ID, SettingsPersister.GRAMMAR_PROPERTY); /** the persistent option that attaches command line options to generated file */ public static final QualifiedName COMMAND_LINE_OPTIONS_PROPERTY = new QualifiedName(AntxrCorePlugin.PLUGIN_ID, "commandLineOptions"); /** * Create a builder */ public AntxrBuilder() { AntxrBuilder.DEBUG = AntxrCorePlugin.isDebug(AntxrBuilder.DEBUG_OPTION); } /** {@inheritDoc} */ protected void clean(IProgressMonitor monitor) throws CoreException { // delete all derived resources that have a GRAMMAR option on them getProject().accept(new CleaningVisitor(monitor, null)); } /** {@inheritDoc} */ protected IProject[] build(int aKind, Map anArgs, IProgressMonitor aMonitor) throws CoreException { IResourceDelta delta = (aKind != IncrementalProjectBuilder.FULL_BUILD ? getDelta(getProject()) : null); if (delta == null || aKind == IncrementalProjectBuilder.FULL_BUILD) { IProject project = getProject(); if (AntxrCorePlugin.getUtil().hasNature(project, AntxrNature.NATURE_ID)) { project.accept(new Visitor(aMonitor)); } } else { delta.accept(new DeltaVisitor(aMonitor)); } return null; } /** {@inheritDoc} */ public void streamAppended(String aText, Object aStream) { if (AntxrBuilder.DEBUG) { fOriginalOut.println("ANTXR output: " + aText); } int line = 0; String message = null; int severity = 0; // First check for messages with header "<file>|<line>|<column>" StringTokenizer st = new StringTokenizer(aText, "|"); if (st.countTokens() > 3) { st.nextToken(); // file line = Integer.parseInt(st.nextToken()); Integer.parseInt(st.nextToken()); // column message = st.nextToken(); while (st.hasMoreTokens()) { message += st.nextToken(); } if (message.startsWith("warning:")) { severity = IMarker.SEVERITY_WARNING; message = message.substring(8); } else { severity = IMarker.SEVERITY_ERROR; } message = message.replace('\t', ' ').trim(); // Then check for messages without header } else if (aText.startsWith("panic: ")) { message = aText.substring(7); severity = IMarker.SEVERITY_ERROR; } else if (aText.startsWith("error: ")) { message = aText.substring(7); severity = IMarker.SEVERITY_ERROR; } else if (aText.startsWith("warning: ")) { severity = IMarker.SEVERITY_WARNING; message = aText.substring(9); } else { if (AntxrBuilder.DEBUG) { fOriginalOut.println("Unhandled ANTXR output: " + aText); } } // If valid error/warning message found then create problem marker if (message != null) { createProblemMarker(line, message, severity); } } private void createProblemMarker(int aLine, String aMessage, int aSeverity) { try { IMarker marker = fFile.createMarker(IMarker.PROBLEM); marker.setAttribute(IMarker.MESSAGE, aMessage); marker.setAttribute(IMarker.SEVERITY, aSeverity); if (aLine > 0) { marker.setAttribute(IMarker.LINE_NUMBER, aLine); } } catch (CoreException e) { AntxrCorePlugin.log(e); } } private class Visitor implements IResourceVisitor { private IProgressMonitor fMonitor; /** * Create a full-build visitor * * @param aMonitor * a progress monitor */ public Visitor(IProgressMonitor aMonitor) { fMonitor = aMonitor; } /** {@inheritDoc} */ public boolean visit(IResource aResource) { if (aResource instanceof IFile) { // see if this is it IFile file = (IFile) aResource; if (file.getName().endsWith(".antxr")) { // if we're in a java project, check to see if the file is in // a source directory... compileFile(file, fMonitor); } } return true; } } private class DeltaVisitor implements IResourceDeltaVisitor { private IProgressMonitor fMonitor; /** * Create a delta visitor * * @param aMonitor * A progress monitor */ public DeltaVisitor(IProgressMonitor aMonitor) { fMonitor = aMonitor; } /** {@inheritDoc} */ public boolean visit(IResourceDelta aDelta) { boolean visitChildren = false; IResource resource = aDelta.getResource(); if (resource instanceof IProject) { // Only check projects with ANTXR nature IProject project = (IProject) resource; visitChildren = AntxrCorePlugin.getUtil().hasNature(project, AntxrNature.NATURE_ID); } else if (resource instanceof IFolder) { visitChildren = true; } else if (resource instanceof IFile) { // Only check ANTXR grammar files IFile file = (IFile) resource; String ext = file.getFileExtension(); if (file.exists() && ext != null && ext.equals("antxr")) { switch (aDelta.getKind()) { case IResourceDelta.ADDED: case IResourceDelta.CHANGED: compileFile(file, fMonitor); visitChildren = true; break; case IResourceDelta.REMOVED: // delete the old generated files for this grammar try { String grammarFileName = file.getProjectRelativePath().toString(); file.getProject().accept(new CleaningVisitor(fMonitor, grammarFileName)); } catch (CoreException e) { AntxrCorePlugin.log(e); } visitChildren = true; break; } } } return visitChildren; } } /** * Compile a grammar * * @param aFile * The grammar file to compile * @param aMonitor * A progress monitor */ public void compileFile(IFile aFile, IProgressMonitor aMonitor) { String grammarFileName = aFile.getProjectRelativePath().toString(); try { // delete the old generated files for this grammar aFile.getProject().accept(new CleaningVisitor(aMonitor, grammarFileName)); // if it's in a java project, only build it if it's in a source dir if (AntxrCorePlugin.getUtil().hasNature(aFile.getProject(), AntxrNature.NATURE_ID)) { IProject project = aFile.getProject(); IJavaProject javaProject = JavaCore.create(project); IPath path = aFile.getFullPath(); boolean ok = false; IClasspathEntry[] resolvedClasspath = javaProject.getResolvedClasspath(true); for (int i = 0; i < resolvedClasspath.length; i++) { IClasspathEntry entry = resolvedClasspath[i]; if (entry.getEntryKind() != IClasspathEntry.CPE_SOURCE) { continue; } IPath entryPath = entry.getPath(); if (entryPath.isPrefixOf(path)) { ok = true; break; } } if (!ok) { return; } } } catch (CoreException e1) { e1.printStackTrace(); } fFile = aFile; aMonitor.beginTask( AntxrCorePlugin.getFormattedMessage("AntxrBuilder.compiling", aFile.getFullPath().toString()), 4); // Remove all markers from this file try { aFile.deleteMarkers(null, true, IResource.DEPTH_INFINITE); } catch (CoreException e) { AntxrCorePlugin.log(e); } // Prepare arguments for ANTXR compiler // read the settings for this grammar Map<String, Map<String, String>> map = SettingsPersister.readSettings(aFile.getProject()); List<String> args = createArguments(map, aFile); if (AntxrBuilder.DEBUG) { System.out.println("Compiling ANTXR grammar '" + aFile.getName() + "': arguments=" + args); } // Monitor system out and err fOriginalOut = System.out; fOriginalErr = System.err; System.setOut(new PrintStream(new MonitoredOutputStream(this))); System.setErr(new PrintStream(new MonitoredOutputStream(this))); try { // Compile ANTXR grammar file AntxrTool tool = new AntxrTool(); if (!tool.preprocess(args.toArray(new String[args.size()]))) { try { aMonitor.worked(1); if (!tool.parse()) { aMonitor.worked(1); if (!tool.generate()) { aMonitor.worked(1); refreshFolder(map, tool, args, aFile, grammarFileName, ResourcesPlugin.getWorkspace().getRoot().findMember(fOutput), aMonitor, tool.getSourceMaps()); } else { // If errors during generate then delete all // generated files deleteFiles(tool, aFile.getParent(), aMonitor); } aMonitor.worked(1); } } catch (CoreException e) { AntxrCorePlugin.log(e); } } } catch (Throwable e) { if (!(e instanceof SecurityException)) { AntxrCorePlugin.log(e); } } finally { System.setOut(fOriginalOut); System.setErr(fOriginalErr); aMonitor.done(); } } /** * Returns list of commandline arguments for ANTXR compiler. * * @param map * the saved antxr-eclipse settings for this project * @param file * The grammar file to parse * @return a list of command-line arguments */ private List<String> createArguments(Map<String, Map<String, String>> map, IFile file) { List<String> args = new ArrayList<String>(); AntxrCorePlugin.getDefault().upgradeOldSettings(file, map); fOutput = SettingsPersister.get(map, file, SettingsPersister.OUTPUT_PROPERTY); // Prepare absolute output path (-o) String outputPath = null; if (fOutput != null && fOutput.trim().length() > 0) { outputPath = convertRootRelatedPath(fOutput); } if (outputPath == null) { try { IJavaProject javaProject = JavaCore.create(file.getProject()); IClasspathEntry rawClasspath[] = javaProject.getRawClasspath(); String longestSourceDirPrefix = ""; String grammarFilePath = file.getFullPath().toString(); for (IClasspathEntry entry : rawClasspath) { if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) { String sourceDirString = entry.getPath().toString(); if (grammarFilePath.startsWith(sourceDirString) && sourceDirString.length() > longestSourceDirPrefix.length()) { longestSourceDirPrefix = sourceDirString; } } } if ("".equals(longestSourceDirPrefix)) { return null; } String basePath = file.getFullPath().removeLastSegments(1).toString(); String packageName = basePath.substring(longestSourceDirPrefix.length()); IFolder targetDirFolder = file.getProject().getFolder("antxr-generated/" + packageName); if (!targetDirFolder.exists()) { targetDirFolder.getRawLocation().toFile().mkdirs(); file.getProject().refreshLocal(IResource.DEPTH_INFINITE, null); } fOutput = targetDirFolder.getFullPath().toString(); outputPath = targetDirFolder.getLocation().toString(); } catch (Exception e) { throw new RuntimeException("Could not set up antxr-generated as output dir", e); } } args.add("-o"); args.add(outputPath); // Get boolean options from grammar properties addBooleanGrammarProperty(map, file, SettingsPersister.DEBUG_PROPERTY, "-debug", null, args, false); addBooleanGrammarProperty(map, file, SettingsPersister.HTML_PROPERTY, "-html", null, args, false); addBooleanGrammarProperty(map, file, SettingsPersister.DOCBOOK_PROPERTY, "-docbook", null, args, false); addBooleanGrammarProperty(map, file, SettingsPersister.DIAGNOSTIC_PROPERTY, "-diagnostic", null, args, false); addBooleanGrammarProperty(map, file, SettingsPersister.TRACE_PROPERTY, "-trace", null, args, false); addBooleanGrammarProperty(map, file, SettingsPersister.TRACE_PARSER_PROPERTY, "-traceParser", null, args, false); addBooleanGrammarProperty(map, file, SettingsPersister.TRACE_LEXER_PROPERTY, "-traceLexer", null, args, false); addBooleanGrammarProperty(map, file, SettingsPersister.TRACE_TREE_PARSER_PROPERTY, "-traceTreeParser", null, args, false); addBooleanGrammarProperty(map, file, SettingsPersister.SMAP_PROPERTY, "-smap", null, args, true); // Get super grammars from grammar properties String superGrammars = SettingsPersister.get(map, file, SettingsPersister.SUPER_GRAMMARS_PROPERTY); // Prepare optional super grammar(s) (-glib) // Can be defined in two ways : in a property dialog OR // inside the grammar using a comment "// -glib <file in same folder>" if (superGrammars != null && superGrammars.trim().length() > 0) { superGrammars = convertRootRelatedPathList(superGrammars); if (superGrammars != null) { args.add("-glib"); args.add(superGrammars); } } else { // Try to get // -glib parameter from comment in .g file. This // enables sharing in a team project without local reconfiguration String localSuperGrammar = extractGlibComment(fFile); if (localSuperGrammar != null) { localSuperGrammar = convertFolderRelatedPath(localSuperGrammar); if (localSuperGrammar != null) { args.add("-glib"); args.add(localSuperGrammar); } } } // Prepare ANTXR grammar file which needs to be compiled args.add(fFile.getLocation().toOSString()); return args; } private void addBooleanGrammarProperty(Map map, IResource grammar, String propertyName, String option, String option2, List<String> args, boolean defaultValue) { boolean value; String stringValue = SettingsPersister.get(map, grammar, propertyName); if (stringValue == null) { value = defaultValue; } else { value = "true".equalsIgnoreCase(stringValue); } if (value) { args.add(option); if (option2 != null) { args.add(option2); } } } /** * Converts given path (related to workspace root) to an absolute path. * * @param aPath * The path to convert * @return The converted path */ private String convertRootRelatedPath(String aPath) { IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); IResource resource = root.findMember(aPath); if (resource == null) { createProblemMarker(0, AntxrCorePlugin.getFormattedMessage("AntxrTool.error.noGrammarFile", aPath), IMarker.SEVERITY_ERROR); } return (resource != null ? resource.getLocation().toOSString() : null); } /** * Converts given list of paths (related to workspace root) to a new list with absolute paths (delimited by ';'). * * @param aPathList * The paths to convert * @return The converted paths */ private String convertRootRelatedPathList(String aPathList) { StringBuffer list = new StringBuffer(); IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); StringTokenizer tokenizer = new StringTokenizer(aPathList, ";"); while (tokenizer.hasMoreTokens()) { String grammar = tokenizer.nextToken(); IResource resource = root.findMember(grammar); if (resource != null) { list.append(resource.getLocation().toOSString()); if (tokenizer.hasMoreTokens()) { list.append(';'); } } else { createProblemMarker(0, AntxrCorePlugin.getFormattedMessage("AntxrTool.error.noGrammarFile", grammar), IMarker.SEVERITY_ERROR); } } return list.toString(); } /** * Converts given path (related to folder of grammar file) to an absolute path. * * @param aPath * The path to convert * @return The converted paths */ private String convertFolderRelatedPath(String aPath) { IResource resource = fFile.getParent().findMember(aPath.toString()); if (resource == null) { createProblemMarker(0, AntxrCorePlugin.getFormattedMessage("AntxrTool.error.noGrammarFile", aPath), IMarker.SEVERITY_ERROR); } return (resource != null ? resource.getLocation().toOSString() : null); } private void deleteFiles(AntxrTool aTool, IContainer aFolder, IProgressMonitor aMonitor) { Iterator files = aTool.files(); while (files.hasNext()) { String fileName = (String) files.next(); if (AntxrBuilder.DEBUG) { fOriginalOut.println("Deleting ANTXR generated file '" + fileName + "'"); } IResource file = aFolder.findMember(fileName); if (file != null) { aMonitor.subTask(AntxrCorePlugin.getFormattedMessage("AntxrBuilder.deleting", fileName)); try { file.delete(true, aMonitor); } catch (CoreException e) { AntxrCorePlugin.log(e); } } } } private void refreshFolder(Map map, AntxrTool aTool, List args, IFile grammarFile, String grammarFileName, IResource aFolder, IProgressMonitor aMonitor, Map sourceMaps) { aMonitor.subTask( AntxrCorePlugin.getFormattedMessage("AntxrBuilder.refreshing", aFolder.getFullPath().toString())); IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); IContainer folder = root.getContainerForLocation(aFolder.getLocation()); if (AntxrBuilder.DEBUG) { fOriginalOut.println("Refreshing output folder '" + folder.getFullPath() + "'"); } try { StringBuffer argString = new StringBuffer(); for (Iterator i = args.iterator(); i.hasNext();) { String arg = (String) i.next(); argString.append(arg); argString.append("|"); } List<IResource> compilationUnitFiles = new ArrayList<IResource>(); Iterator files = aTool.files(); while (files.hasNext()) { String fileName = (String) files.next(); if (AntxrBuilder.DEBUG) { fOriginalOut.println("ANTXR generated file '" + fileName + "'"); } IResource file = folder.getFile(new Path(fileName)); file.refreshLocal(IResource.DEPTH_ZERO, aMonitor); // SASSAS check if file is being edited - if so, refresh file.setDerived(true); String cleanWarnings = SettingsPersister.get(map, grammarFile, SettingsPersister.CLEAN_WARNINGS); String installSmap = SettingsPersister.get(map, grammarFile, SettingsPersister.SMAP_PROPERTY); file.setPersistentProperty(AntxrBuilder.CLEAN_WARNINGS, cleanWarnings); file.setPersistentProperty(AntxrBuilder.INSTALL_SMAP, installSmap); file.setPersistentProperty(AntxrBuilder.GRAMMAR_ECLIPSE_PROPERTY, grammarFileName); file.setPersistentProperty(AntxrBuilder.COMMAND_LINE_OPTIONS_PROPERTY, argString.toString()); if (fileName.endsWith(".java")) { compilationUnitFiles.add(file); Map sourceMap = (Map) sourceMaps.get(file.getName()); // add source mappings to the generated file if (sourceMap != null) { for (Iterator i = sourceMap.keySet().iterator(); i.hasNext();) { Integer sourceLine = (Integer) i.next(); List targetLines = (List) sourceMap.get(sourceLine); for (Iterator j = targetLines.iterator(); j.hasNext();) { Integer targetLine = (Integer) j.next(); IMarker marker = file.createMarker(AntxrCorePlugin.SOURCE_MAPPING_MARKER); marker.setAttribute(AntxrCorePlugin.GRAMMAR_LINE_ATTRIBUTE, sourceLine); marker.setAttribute(AntxrCorePlugin.GENERATED_LINE_ATTRIBUTE, targetLine); } } } } } // WORK-IN-PROGRESS -- NON FUNCTIONAL // if (!compilationUnitFiles.isEmpty()) { // // find "end of imports" marker in generated code: // // $$END-OF-ANTXR-GENERATED-IMPORTS$$ // int[] oldImportEnds = findEndOfImports(compilationUnitFiles); // // MultiStatus status = new MultiStatus(AntxrCorePlugin.PLUGIN_ID, 0, "", null); // new ImportOrganizer().organizeImports(compilationUnitFiles, status, aMonitor); // if (status.getSeverity() != IStatus.OK) // AntxrCorePlugin.log(status); // for (Iterator i = compilationUnitFiles.iterator(); i.hasNext();) { // IFile file = (IFile)i.next(); // file.setDerived(true); // } // // // find "end of imports" markers after the organize imports // int[] newImportEnds = findEndOfImports(compilationUnitFiles); // // // adjust the smap files based on the offsets // adjustSmapFiles(compilationUnitFiles, oldImportEnds, newImportEnds); // } } catch (OperationCanceledException e) { // TODO what to do with cancel? } catch (Exception e) { AntxrCorePlugin.log(e); } } // WORK-IN-PROGRESS -- NON FUNCTIONAL // /** // * Adjust the smap line definitions based on line offsets // * @param compilationUnitFiles the generated java files // * @param oldImportEnds the old location of the "end of imports" marker // * @param newImportEnds the new location of the "end of imports" marker // */ // private void adjustSmapFiles(List compilationUnitFiles, int[] oldImportEnds, int[] newImportEnds) { // int n = 0; // for (Iterator i = compilationUnitFiles.iterator(); i.hasNext(); n++) { // IFile file = (IFile)i.next(); // // skip token type definition files // if (file.getName().endsWith("TokenTypes.java")) // continue; // // IPath smapLocation = file.getLocation().removeFileExtension().addFileExtension(".smap"); // // strip ".java" from the file name and add ".smap" // System.out.println(smapLocation.toFile()); // } // } // // /** // * Determine where the "end of imports" marker in the generated code is // * @param compilationUnitFiles the file to process // * @return an array of line numbers // */ // private int[] findEndOfImports(List compilationUnitFiles) { // int[] endOfImports = new int[compilationUnitFiles.size()]; // int n = 0; // for (Iterator i = compilationUnitFiles.iterator(); i.hasNext(); n++) { // IFile file = (IFile)i.next(); // // skip the token type definitions -- they don't have smaps // if (file.getName().endsWith("TokenTypes.java")) // continue; // // try { // BufferedReader reader = new BufferedReader(new InputStreamReader(file.getContents())); // String line; // while((line = reader.readLine()) != null) { // if (line.startsWith("// $$END-OF-ANTXR-GENERATED-IMPORTS$$")) // break; // endOfImports[n]++; // } // } // catch (Exception e) { // AntxrCorePlugin.log(e); // } // } // return endOfImports; // } /** * Try to get -glib parameter from the first single line comment in given grammar file (// -glib <super grammar>). * This enables sharing in a team project without local reconfiguration. * * @param aFile * The grammar file * @return The glib comment */ private String extractGlibComment(IFile aFile) { try { BufferedReader reader = new BufferedReader(new InputStreamReader(aFile.getContents())); // Skip leading white spaces int c = reader.read(); while (c == ' ' || c == '\t' || c == '\n' || c == '\r') { c = reader.read(); } // Match leading "//" if (c == '/' && reader.read() == '/') { // Skip white spaces c = reader.read(); while (c == ' ' || c == '\t') { c = reader.read(); } // Match "-glib" if (c == '-' && reader.read() == 'g' && reader.read() == 'l' && reader.read() == 'i' && reader.read() == 'b' && reader.read() == ' ') { // Skip white spaces c = reader.read(); while (c == ' ' || c == '\t') { c = reader.read(); } // Use rest of line as relative path to super grammar StringBuffer grammar = new StringBuffer(); while (c != '\n' && c != '\r') { grammar.append((char) c); c = reader.read(); } return grammar.toString(); } } } catch (Exception e) { AntxrCorePlugin.log(e); } return null; } }