/*******************************************************************************
* Copyright (c) 2011 SAP AG and others.
* 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:
* SAP AG - initial API and implementation
******************************************************************************/
package com.sap.furcas.ide.editor.imp;
import java.util.Collections;
import java.util.Iterator;
import org.antlr.runtime.Lexer;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.emf.common.command.BasicCommandStack;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.transaction.TransactionalEditingDomain;
import org.eclipse.imp.parser.IParseController;
import org.eclipse.imp.parser.ISourcePositionLocator;
import org.eclipse.imp.parser.ParseControllerBase;
import org.eclipse.imp.parser.SimpleAnnotationTypeInfo;
import org.eclipse.imp.services.IAnnotationTypeInfo;
import org.eclipse.imp.services.ILanguageSyntaxProperties;
import org.eclipse.jface.text.IRegion;
import org.eclipse.swt.widgets.Display;
import com.sap.furcas.ide.editor.CtsActivator;
import com.sap.furcas.ide.editor.commands.ParseCommand;
import com.sap.furcas.ide.editor.imp.AbstractFurcasEditor.ContentProvider;
import com.sap.furcas.ide.editor.imp.services.FurcasSourcePositionLocator;
import com.sap.furcas.ide.parserfactory.AbstractParserFactory;
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.runtime.parser.impl.ObservableInjectingParser;
import com.sap.furcas.runtime.textblocks.TbNavigationUtil;
import com.sap.furcas.runtime.textblocks.TbUtil;
import com.sap.furcas.runtime.textblocks.model.VersionedTextBlockNavigator;
import com.sap.ide.cts.parser.incremental.IncrementalParserFacade;
/**
* {@link IParseController} implementation for generated FURCAS parsers.
* This allows the IMP framework to call a FURCAS parser whenever needed.<p>
*
* This class is generic and does not need a language specific implementation.
*
* @author Stephan Erb
*
*/
public abstract class FurcasParseController extends ParseControllerBase {
protected TransactionalEditingDomain editingDomain;
protected ContentProvider contentProvider;
protected IncrementalParserFacade parserFacade;
protected final IAnnotationTypeInfo annotationTypeInfo;
protected final FurcasSourcePositionLocator sourcePositionLocator;
protected final ILanguageSyntaxProperties languageSyntaxProperties;
protected boolean completelyItitialized = false;
public FurcasParseController(AbstractParserFactory<? extends ObservableInjectingParser, ? extends Lexer> parserFactory, ILanguageSyntaxProperties languageSyntaxProperties) {
super(parserFactory.getLanguageId());
this.sourcePositionLocator = new FurcasSourcePositionLocator();
this.annotationTypeInfo = new SimpleAnnotationTypeInfo();
this.languageSyntaxProperties = languageSyntaxProperties;
}
@SuppressWarnings("hiding")
public void completeInit(TransactionalEditingDomain editingDomain, ContentProvider contentProvider, IncrementalParserFacade parserFacade) {
this.editingDomain = editingDomain;
this.contentProvider = contentProvider;
this.parserFacade = parserFacade;
setCurrentAst(contentProvider.getDocument().getRootBlock());
completelyItitialized = true;
}
/**
* Incrementally parse the content of the linked {@link AbstractFurcasEditor}. The given input argument,
* representing the entire document content, is always ignored. The content for incremental parsing
* is retrieved through a side channel which the caller does not need to know about.
*
* @return the root TextBlock
*/
@Override
public TextBlock parse(String input, IProgressMonitor monitor) {
if (!completelyItitialized) {
monitor.setCanceled(true);
return null; // IMP will call us before we are done initializing
}
boolean saveNeeded = ((BasicCommandStack) editingDomain.getCommandStack()).isSaveNeeded();
ParseCommand command = new ParseCommand(editingDomain, contentProvider.getDocument(), parserFacade, handler, monitor);
try {
editingDomain.getCommandStack().execute(command);
} catch (Exception e) {
handleRuntimeException(e);
}
if (!saveNeeded && !command.wasEffective()) {
// the command changed nothing. Reset dirty state.
((BasicCommandStack) editingDomain.getCommandStack()).saveIsDone();
contentProvider.notifyDirtyPropertyChanged();
}
if (command.wasEffective()) {
// only use the result if parsing was not aborted (most commonly if the user typed something new)
setCurrentAst(command.getParsingResult());
}
return getCurrentAst();
}
private void handleRuntimeException(Exception e) {
CtsActivator.logger.logError("Parser failed with internal error", e);
Display.getDefault().syncExec(new Runnable() {
@Override
public void run() {
contentProvider.getDocument().resetAfterError();
}
});
}
@Override
public Iterator<AbstractToken> getTokenIterator(final IRegion region) {
if (!completelyItitialized || getCurrentAst() == null) {
return Collections.<AbstractToken>emptyList().iterator();
}
final VersionedTextBlockNavigator navigator = new VersionedTextBlockNavigator(Version.REFERENCE);
final int regionEnd = region.getOffset() + region.getLength();
/* We assume that our caller sticks to the protocol and only calls next() after having called hasNext().
* That way the next token only has to be calculated once.
*/
return new Iterator<AbstractToken>() {
private AbstractToken nextToken;
@Override
public boolean hasNext() {
if (nextToken == null) {
nextToken = navigator.getFloorToken(getCurrentAst(), region.getOffset());
} else {
nextToken = TbNavigationUtil.nextToken(nextToken);
}
return nextToken != null && TbUtil.getAbsoluteOffset(nextToken) + nextToken.getLength() <= regionEnd;
}
@Override
public AbstractToken next() {
if (nextToken == null) {
throw new IndexOutOfBoundsException();
}
return nextToken;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
@Override
public TextBlock getCurrentAst() {
if (!completelyItitialized) {
return null; // IMP will call us before we are done initializing
}
return (TextBlock) fCurrentAst;
}
private void setCurrentAst(TextBlock rootBlock) {
fCurrentAst = rootBlock;
}
public EObject getCurrentRootObject() {
return IncrementalParserFacade.getParsingResult(getCurrentAst());
}
@Override
public ISourcePositionLocator getSourcePositionLocator() {
return sourcePositionLocator;
}
@Override
public ILanguageSyntaxProperties getSyntaxProperties() {
return languageSyntaxProperties;
}
@Override
public IAnnotationTypeInfo getAnnotationTypeInfo() {
return annotationTypeInfo;
}
public IncrementalParserFacade getParserFacade() {
return parserFacade;
}
}