package com.sap.furcas.ide.dslproject.builder; import java.io.File; import java.io.IOException; import java.util.Map; import org.eclipse.core.resources.IFile; 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.IncrementalProjectBuilder; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Path; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.resource.Resource; import com.sap.furcas.ide.dslproject.Activator; import com.sap.furcas.ide.dslproject.Constants; import com.sap.furcas.ide.dslproject.conf.IProjectMetaRefConf; import com.sap.furcas.ide.dslproject.conf.ReferenceScopeBean; import com.sap.furcas.metamodel.FURCAS.TCS.ConcreteSyntax; import com.sap.furcas.parsergenerator.GenerationErrorHandler; import com.sap.furcas.parsergenerator.GrammarGenerationException; import com.sap.furcas.parsergenerator.GrammarGenerationSourceConfiguration; import com.sap.furcas.parsergenerator.GrammarGenerationTargetConfiguration; import com.sap.furcas.parsergenerator.TCSParserGenerator; import com.sap.furcas.parsergenerator.TCSParserGeneratorFactory; import com.sap.furcas.parsergenerator.TCSSyntaxContainerBean; import com.sap.furcas.runtime.common.exceptions.ParserGeneratorInvocationException; import com.sap.furcas.runtime.common.exceptions.ParserInvokationException; import com.sap.furcas.utils.exceptions.EclipseExceptionHelper; /** * An incremental project builder for .tcs-files. It generates lexer, parser and mapping according to .tcs-files found within a * project. */ public class SyntaxBuilder extends IncrementalProjectBuilder { /** The Constant BUILDER_ID. */ public static final String BUILDER_ID = Activator.class.getPackage().getName() + ".syntaxBuilder"; private static final String GRAMMAR_ANTLR_POSTFIX = ".g"; /** * Runs a full build */ private class TCSBuildVisitor implements IResourceVisitor { private final IProgressMonitor mymonitor; public TCSBuildVisitor(IProgressMonitor monitor) { mymonitor = monitor; } @Override public boolean visit(IResource resource) { try { doFullBuild(resource, mymonitor); } catch (CoreException e) { Activator.logger.logError("Failed to build grammar from resource " + resource, e); } // return true to continue visiting children. return true; } } private class TCSCleanVisitor implements IResourceVisitor { @SuppressWarnings("unused") private final IProgressMonitor mymonitor; public TCSCleanVisitor(IProgressMonitor monitor) { mymonitor = monitor; } @Override public boolean visit(IResource resource) { // TODO implement cleaning. For now we do simply overwrite // try { // cleanResource(resource, mymonitor); // } catch (CoreException e) { // Activator.logger.logError("Failed to clean resource " + resource); // } // return true to continue visiting children. return true; } } /** * Runs a build according to a given resource delta. */ private class TCSProjectDeltaVisitor implements IResourceDeltaVisitor { private final IProgressMonitor mymonitor; public TCSProjectDeltaVisitor(IProgressMonitor mymonitor) { this.mymonitor = mymonitor; } @Override public boolean visit(IResourceDelta delta) throws CoreException { IResource resource = delta.getResource(); if (delta.getFlags() == IResourceDelta.MARKERS) { return true; } switch (delta.getKind()) { case IResourceDelta.ADDED: // handle added resource doFullBuild(resource, mymonitor); break; case IResourceDelta.REMOVED: // handle removed resource break; case IResourceDelta.CHANGED: // handle changed resource doFullBuild(resource, mymonitor); break; default: break; } // return true to continue visiting children. return true; } } @Override @SuppressWarnings("rawtypes") // Map is defined without generic in supertype protected IProject[] build(int kind, Map args, IProgressMonitor pm) throws CoreException { try { if (kind == FULL_BUILD) { getProject().accept(new TCSBuildVisitor(pm)); } else { IResourceDelta delta = getDelta(getProject()); if (delta == null) { // full build getProject().accept(new TCSBuildVisitor(pm)); } else { // incremental build delta.accept(new TCSProjectDeltaVisitor(pm)); } } } catch (CoreException e) { Activator.logger.logError("Failed during build of " + getProject(), e); throw e; } return null; } /** * Builds a new parsers. Works on plaintext TCS files and also TCS syntax models. * When run on a plaintext file a model is created and stored. * * FIXME: hardcoded project relative paths are used. We have to make them configurable. */ public void doFullBuild(IResource resource, IProgressMonitor monitor) throws CoreException { if (!resource.getName().endsWith(Constants.TCS_EXTENSION) || resource.isDerived()) { return; // nothing to build } monitor.beginTask("Building FURCAS Parser", 110); SyntaxGenerationNature nature = SyntaxGenerationNature.getNatureFromProject(resource.getProject()); IProjectMetaRefConf conf = nature.getMetaModelReferenceConf(); if (conf == null) { String message = "Build failed: Project " + resource.getProject().getName() + " has DSL Syntax Definition Nature but no metamodel reference configured."; EclipseMarkerUtil.addMarker(resource, message, -1, IMarker.SEVERITY_ERROR); return; } EclipseMarkerUtil.deleteMarkers(resource); ReferenceScopeBean refScopeBean = conf.getMetaLookUpForProject(); try { GrammarGenerationSourceConfiguration sourceConfig = new GrammarGenerationSourceConfiguration( refScopeBean.getResourceSet(), refScopeBean.getReferenceScope()); IFile grammarFile = getGrammarFile(resource); GrammarGenerationTargetConfiguration targetConfig = new GrammarGenerationTargetConfiguration( getPackageName(grammarFile), convertIFileToFile(grammarFile)); TCSSyntaxContainerBean syntaxBean; if (!hasModelContent(resource, refScopeBean)) { monitor.subTask("Parsing Syntax " + resource.getName()); syntaxBean = parseSyntax(resource, sourceConfig); if (syntaxBean != null) { IResource modelResource = resource.getProject().getFile("mapping" + File.separator + getFileNameBase(resource) + "." + "furcas"); if (modelResource.getLocalTimeStamp() > resource.getLocalTimeStamp()) { // The plaintext file is older than the model. We do not want to overwrite the model file. // (happens when the project is cleaned and a rebuilt is triggered) return; } saveSyntaxAsModel(modelResource, sourceConfig, syntaxBean); } } else { syntaxBean = loadSyntaxFromModelFile(resource, sourceConfig); } if (syntaxBean == null) { return; } GenerationErrorHandler errorHandler = new ResourceMarkingGenerationErrorHandler(resource); monitor.subTask("Generating Parser for " + resource.getName()); buildParser(syntaxBean, grammarFile, sourceConfig, targetConfig, errorHandler); } catch (GrammarGenerationException e) { throw new CoreException(EclipseExceptionHelper.getErrorStatus(e, Activator.PLUGIN_ID)); } catch (ParserGeneratorInvocationException e) { throw new CoreException(EclipseExceptionHelper.getErrorStatus(e, Activator.PLUGIN_ID)); } catch (ParserInvokationException e) { throw new CoreException(EclipseExceptionHelper.getErrorStatus(e, Activator.PLUGIN_ID)); } catch (IOException e) { throw new CoreException(EclipseExceptionHelper.getErrorStatus(e, Activator.PLUGIN_ID)); } finally { monitor.done(); } } private TCSSyntaxContainerBean parseSyntax(IResource resource, GrammarGenerationSourceConfiguration sourceConfig) throws ParserInvokationException, ParserGeneratorInvocationException { TCSParserGenerator generator = TCSParserGeneratorFactory.INSTANCE.createTCSParserGenerator(); TCSSyntaxContainerBean syntaxBean = generator.parseSyntax(sourceConfig, convertIFileToFile(resource), new ResourceMarkingGenerationErrorHandler(resource)); if (syntaxBean.getSyntax() == null) { return null; // failed with errors. Markers have been created. } return syntaxBean; } private void saveSyntaxAsModel(IResource resource, GrammarGenerationSourceConfiguration sourceConfig, TCSSyntaxContainerBean syntaxBean) throws IOException { URI modelFileURI = URI.createPlatformResourceURI(resource.getFullPath().toString(), true); Resource mappingResource; try { mappingResource = sourceConfig.getResourceSet().getResource(modelFileURI, true); } catch (Exception e){ mappingResource = sourceConfig.getResourceSet().createResource(modelFileURI); } mappingResource.getContents().clear(); mappingResource.getContents().add(syntaxBean.getSyntax()); // FIXME: We have to replace replace all /resource/ uris with /plugin/ // See https://bugzilla.furcas.org/cgi-bin/bugzilla3/show_bug.cgi?id=88 mappingResource.save(null); } private TCSSyntaxContainerBean loadSyntaxFromModelFile(IResource resource, GrammarGenerationSourceConfiguration sourceConfig) { URI modelFileURI = URI.createPlatformResourceURI(resource.getFullPath().toString(), true); Resource mappingResource = sourceConfig.getResourceSet().getResource(modelFileURI, true); EObject concreteSyntax = mappingResource.getContents().iterator().next(); if (!(concreteSyntax instanceof ConcreteSyntax)) { Activator.logger.displayError("Expected mapping resource: " + mappingResource.getURI() + " to contain Concrete Sytnax element but found: " + concreteSyntax.eClass().getName()); return null; } TCSSyntaxContainerBean syntaxBean = new TCSSyntaxContainerBean(); syntaxBean.setSyntax((ConcreteSyntax) concreteSyntax); return syntaxBean; } private void buildParser(TCSSyntaxContainerBean syntaxBean, IFile grammarFile, GrammarGenerationSourceConfiguration sourceConfig, GrammarGenerationTargetConfiguration targetConfig, GenerationErrorHandler errorHandler) throws GrammarGenerationException, CoreException, ParserGeneratorInvocationException { TCSParserGenerator generator = TCSParserGeneratorFactory.INSTANCE.createTCSParserGenerator(); generator.generateGrammarFromSyntax(syntaxBean, sourceConfig, targetConfig, errorHandler); grammarFile.getParent().refreshLocal(1, new NullProgressMonitor()); if (targetConfig.getGrammarTargetFile().exists()) { generator.generateParserFromGrammar(targetConfig, new ResourceMarkingGenerationErrorHandler(grammarFile)); } // refresh dir where java was generated so that Java builder can compile grammarFile.getParent().refreshLocal(1, new NullProgressMonitor()); } private static boolean hasModelContent(IResource resource, ReferenceScopeBean refScopeBean) { URI modelFileURI = URI.createPlatformResourceURI(resource.getFullPath().toString(), true); try { refScopeBean.getResourceSet().getResource(modelFileURI, true); return true; } catch (Exception ex) { // load failed, so it seems to be a plain text file return false; } } /** * Returns the files path in package notation except for the filename, * or returns "generated" if no such path exists */ private static String getPackageName(IFile file) { String targetPackage = null; IPath path = file.getParent().getProjectRelativePath(); if (path != null && path.segmentCount() >= 2) { // source folder / // packagefolder1 targetPackage = path.segment(1); for (int i = 2; i < path.segmentCount(); i++) { String segment = path.segment(i); targetPackage += '.' + segment; } } else { targetPackage = "generated"; } return targetPackage; } private static IFile getGrammarFile(IResource resource) { String newFileName = "generated" + File.separator + "generated" + File.separator + getFileNameBase(resource) + GRAMMAR_ANTLR_POSTFIX; return resource.getProject().getFile(new Path(IPath.SEPARATOR + newFileName)); } private static File convertIFileToFile(IResource file) { return new File(file.getRawLocation().toOSString()); } private static String getFileNameBase(IResource file) { String fileName = file.getName(); // "s m i l e s".substring(1, 5) returns "mile" // 0 1 2 3 4 5 String newFileName = fileName.substring(0, (fileName.length() - file.getFileExtension().length() - 1)); // -1 return newFileName; } @Override protected void clean(IProgressMonitor monitor) throws CoreException { super.clean(monitor); try { getProject().accept(new TCSCleanVisitor(monitor)); } catch (CoreException e) { Activator.logger.logError("Failed cleaning project " + getProject(), e); } } }