package com.sap.ide.cts.parser.incremental; import java.lang.reflect.Field; import java.util.Collection; import java.util.List; import org.antlr.runtime.ANTLRStringStream; import org.antlr.runtime.CharStream; import org.antlr.runtime.CommonTokenStream; import org.antlr.runtime.Lexer; import org.antlr.runtime.TokenSource; import org.antlr.runtime.TokenStream; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.ocl.ecore.opposites.OppositeEndFinder; import com.sap.furcas.metamodel.FURCAS.textblocks.AbstractToken; import com.sap.furcas.metamodel.FURCAS.textblocks.TextBlock; import com.sap.furcas.metamodel.FURCAS.textblocks.Version; import com.sap.furcas.modeladaptation.emf.adaptation.EMFModelAdapter; import com.sap.furcas.runtime.common.exceptions.ParserInstantiationException; import com.sap.furcas.runtime.common.interfaces.IBareModelAdapter; import com.sap.furcas.runtime.common.interfaces.IModelElementInvestigator; import com.sap.furcas.runtime.common.util.EcoreHelper; import com.sap.furcas.runtime.common.util.TCSSpecificOCLEvaluator; import com.sap.furcas.runtime.parser.IModelAdapter; import com.sap.furcas.runtime.parser.ParserFactory; import com.sap.furcas.runtime.parser.ParsingError; import com.sap.furcas.runtime.parser.PartitionAssignmentHandler; import com.sap.furcas.runtime.parser.impl.DefaultTextAwareModelAdapter; import com.sap.furcas.runtime.parser.impl.ModelInjector; import com.sap.furcas.runtime.parser.impl.ObservableInjectingParser; import com.sap.furcas.runtime.parser.impl.ParserScope; import com.sap.furcas.runtime.parser.textblocks.ITextBlocksTokenStream; import com.sap.furcas.runtime.parser.textblocks.TextBlocksAwareModelAdapter; import com.sap.furcas.runtime.parser.textblocks.observer.ParserTextBlocksHandler; import com.sap.furcas.runtime.textblocks.TbDebugUtil; import com.sap.furcas.runtime.textblocks.modifcation.TbVersionUtil; import com.sap.ide.cts.parser.Activator; import com.sap.ide.cts.parser.errorhandling.SemanticParserException; import com.sap.ide.cts.parser.errorhandling.SemanticParserException.Component; import com.sap.ide.cts.parser.incremental.antlr.ANTLRIncrementalLexerAdapter; import com.sap.ide.cts.parser.incremental.antlr.ANTLRLexerAdapter; import com.sap.ocl.oppositefinder.query2.Query2OppositeEndFinder; import de.hpi.sam.bp2009.solution.queryContextScopeProvider.QueryContextProvider; /** * Facade for handling incremental parser and lexer construction as well as * calling of parsing methods. Only completely configured components are exposed. * * @author C5106462 * @author Stephan Erb * */ public class IncrementalParserFacade { private final ParserFactory<? extends ObservableInjectingParser, ? extends Lexer> parserFactory; private final ANTLRIncrementalLexerAdapter incrementalLexer; private final ITextBlocksTokenStream tbTokenStream; private final ObservableInjectingParser domainParser; private final IncrementalParser incrementalParser; private final ModelInjector injector; private final ParserTextBlocksHandler observer; private final ParserScope parserScope; private final PartitionAssignmentHandler partitionAssignmentHandler; private final OppositeEndFinder oppositeEndFinder; private final TCSSpecificOCLEvaluator oclEvaluator; private final IBareModelAdapter bareModelAdapter; public IncrementalParserFacade(ParserFactory<? extends ObservableInjectingParser, ? extends Lexer> parserFactory, ResourceSet resourceSet, PartitionAssignmentHandler partitionAssignmentHandler) throws ParserInstantiationException { this.parserFactory = parserFactory; this.partitionAssignmentHandler = partitionAssignmentHandler; this.parserScope = new ParserScope(resourceSet, parserFactory); // Build a scope encompassing all resources in the resource set, // the additional queryScope, and all other resources visible via // Eclipse bundle dependencies. QueryContextProvider queryContext = EcoreHelper.createProjectDependencyQueryContextProvider( resourceSet, parserScope.getExplicitQueryScope()); // Has to be consistent to the definition in the SyntaxProviderImpl this.oppositeEndFinder = new Query2OppositeEndFinder(queryContext); this.oclEvaluator = new TCSSpecificOCLEvaluator(oppositeEndFinder); this.bareModelAdapter = new EMFModelAdapter(resourceSet, partitionAssignmentHandler, parserScope.getMetamodelLookup(), parserScope.getExplicitQueryScope(), oclEvaluator, oppositeEndFinder); IModelAdapter modelAdapter = new TextBlocksAwareModelAdapter(bareModelAdapter); // TODO use token wrapper factory here TextBlockReuseStrategyImpl reuseStrategy = new TextBlockReuseStrategyImpl(parserFactory.createLexer(null), modelAdapter); Lexer domainLexer = parserFactory.createLexer(null); ANTLRLexerAdapter lexerAdapter = new ANTLRLexerAdapter(domainLexer, reuseStrategy); this.incrementalLexer = new ANTLRIncrementalLexerAdapter(lexerAdapter, modelAdapter); this.tbTokenStream = parserFactory.createIncrementalTokenStream(incrementalLexer); this.domainParser = parserFactory.createParser(tbTokenStream, modelAdapter); this.incrementalParser = new IncrementalParser(domainParser, parserScope, reuseStrategy, partitionAssignmentHandler); // TODO use token wrapper factory here this.injector = new ModelInjector(domainParser.getTokenNames()); this.injector.setModelAdapter(modelAdapter); setInjector(domainLexer, domainParser, injector); this.observer = createParserTextBlocksHandler(); this.domainParser.setObserver(observer); } private static void setInjector(Lexer domainLexer, ObservableInjectingParser domainParser, ModelInjector injector) { domainParser.setInjector(injector); try { Field f = domainLexer.getClass().getField("ei"); f.set(domainLexer, injector); } catch (IllegalArgumentException e) { throw new RuntimeException(e); } catch (NoSuchFieldException nsfe) { Activator.logWarning("Lexer has no ModelInjector field ei, lexer errors will only be reported to System.err"); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } /** * Uses the {@link IncrementalParser} to parse only the necessary parts of * the given root {@link TextBlock}. If lexing fails, the same old * TextBlock is returned. * * @param rootBlock * @return */ public TextBlock parseIncrementally(TextBlock oldBlock) throws SemanticParserException { return parseIncrementally(oldBlock, new NullProgressMonitor()); } /** * Uses the {@link IncrementalParser} to parse only the necessary parts of * the given root {@link TextBlock}. If lexing fails, the same old * TextBlock is returned. * * @param rootBlock * @param monitor * @return */ public TextBlock parseIncrementally(TextBlock rootBlock, IProgressMonitor monitor) throws SemanticParserException { setDefaultPartitionFromRoot(rootBlock); if (lexAndPrepareParsing(rootBlock)) { TextBlock preparedTextBlock = getCurrentVersion(rootBlock); incrementalLexer.setCurrentTokenForParser((AbstractToken) preparedTextBlock.getSubNodes().get(0)); observer.setRootBlock(preparedTextBlock); if (monitor.isCanceled()) { // Canceld during lexing. Safe to return here. return preparedTextBlock; } String preParseContent = TbDebugUtil.getDocumentNodeAsPlainString(preparedTextBlock); TextBlock resultBlock = incrementalParser.incrementalParse(preparedTextBlock, /*error mode*/ false, monitor); String postParseContent = TbDebugUtil.getDocumentNodeAsPlainString(resultBlock); Assert.isTrue(preParseContent.equals(postParseContent), "Content differs after parsing. \n" + "Before:\n "+ preParseContent + "\nAfter:\n " + postParseContent); return resultBlock; } else { throw new SemanticParserException(getErrors(), Component.LEXICAL_ANALYSIS); } } /** * Sets the default partition that is used to assign all created model * elements. In this case the same partition as the one from the root blocks * corresponding model element is used. */ private void setDefaultPartitionFromRoot(TextBlock root) { Resource defaultPartition = null; if (root.getCorrespondingModelElements().size() != 0) { defaultPartition = getParsingResult(root).eResource(); } else { defaultPartition = root.eResource(); } partitionAssignmentHandler.setDefaultPartition(defaultPartition); partitionAssignmentHandler.assignToDefaultTextBlocksPartition(root); } public List<ParsingError> dryParse(TextBlock rootBlock) throws ParserInstantiationException { CharStream inputStream = new ANTLRStringStream(rootBlock.getCachedString()); TokenSource lexer = parserFactory.createLexer(inputStream); TokenStream tokenStream = new CommonTokenStream(lexer); IModelAdapter modelAdapter = new DefaultTextAwareModelAdapter(bareModelAdapter); ObservableInjectingParser p = parserFactory.createParser(tokenStream, modelAdapter); return p.checkSyntaxWithoutInjecting(); } public static EObject getParsingResult(TextBlock rootBlock) { Collection<EObject> result = rootBlock.getCorrespondingModelElements(); if (result.size() == 0) { return null; } else { return result.iterator().next(); } } /** * Preparse all components for a new parse run, such as resetting the batch parser and setting the source for the * lexer. Operates on the {@link Version#PREVIOUS} version of <code>rootBlock</code>. * * @param rootBlock * the block that should be used to prepare parsing on * @return <code>true</code> if and only if lexing was successful */ private boolean lexAndPrepareParsing(TextBlock rootBlock) { // reset parser states to beginning domainParser.reset(); // go back to beginning of stream tbTokenStream.reset(); TextBlock previousVersionTb = TbVersionUtil.getOtherVersion(rootBlock, Version.PREVIOUS); String preLexContent = TbDebugUtil.getDocumentNodeAsPlainString(previousVersionTb); incrementalLexer.setSource((AbstractToken) previousVersionTb.getSubNodes().get(0)); boolean lexingSuccessful = incrementalLexer.lex(previousVersionTb); if (lexingSuccessful) { String postLexContent = TbDebugUtil.getDocumentNodeAsPlainString(getCurrentVersion(previousVersionTb)); Assert.isTrue(preLexContent.equals(postLexContent), "Content differs after lexing. \n" + "Before:\n "+ preLexContent + "\nAfter:\n " + postLexContent); } return lexingSuccessful; } private TextBlock getCurrentVersion(TextBlock rootBlock) { TextBlock currentVersionTb = TbVersionUtil.getOtherVersion(rootBlock, Version.CURRENT); return currentVersionTb; } /** * Returns the errors that where collected during the incremental parse run. * * @return the list of {@link ParsingError} that occured during parsing. */ public List<ParsingError> getErrors() { return injector.getErrorList(); } public ParserFactory<? extends ObservableInjectingParser, ? extends Lexer> getParserFactory() { return parserFactory; } public ParserScope getParserScope() { return parserScope; } public OppositeEndFinder getOppositeEndFinder() { return oppositeEndFinder; } public TCSSpecificOCLEvaluator getOclEvaluator() { return oclEvaluator; } public IModelElementInvestigator getModelElementInvestigator() { return injector.getModelAdapter(); } /* * The methods below are for the use by the {@link MappingRecoveringTextBlocksValidator} * They must be used with care as they directly alter the internal state of this facade. */ /*package*/ ParserTextBlocksHandler createParserTextBlocksHandler() { return new ParserTextBlocksHandler(tbTokenStream, parserScope, partitionAssignmentHandler); } /*package*/ IModelAdapter getModelAdapter() { return injector.getModelAdapter(); } /*package*/ ObservableInjectingParser getParser() { return domainParser; } /*package*/ IncrementalParser getIncrementalParser() { return incrementalParser; } /*package*/ LexerAdapter getLexer() { return incrementalLexer.getBatchLexer(); } /*package*/ ANTLRIncrementalLexerAdapter getIncrementalLexer() { return incrementalLexer; } }