/******************************************************************************* * 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.EventObject; import org.antlr.runtime.Lexer; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.emf.common.command.BasicCommandStack; import org.eclipse.emf.common.command.CommandStackListener; import org.eclipse.emf.common.notify.AdapterFactory; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; import org.eclipse.emf.ecore.util.ECrossReferenceAdapter; import org.eclipse.emf.edit.provider.ComposedAdapterFactory; import org.eclipse.emf.edit.provider.ReflectiveItemProviderAdapterFactory; import org.eclipse.emf.edit.provider.resource.ResourceItemProviderAdapterFactory; import org.eclipse.emf.edit.ui.provider.AdapterFactoryLabelProvider; import org.eclipse.emf.transaction.TransactionalCommandStack; import org.eclipse.emf.transaction.TransactionalEditingDomain; import org.eclipse.emf.workspace.WorkspaceEditingDomainFactory; import org.eclipse.imp.editor.UniversalEditor; import org.eclipse.imp.language.Language; import org.eclipse.imp.services.ITokenColorer; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IEditorSite; import org.eclipse.ui.PartInitException; import com.sap.furcas.ide.editor.commands.SetupTextBlocksModelCommand; import com.sap.furcas.ide.editor.document.CtsDocument; import com.sap.furcas.ide.editor.document.CtsDocumentProvider; import com.sap.furcas.ide.editor.document.ModelEditorInput; import com.sap.furcas.ide.editor.document.ModelEditorInputLoader; import com.sap.furcas.ide.editor.imp.services.FurcasContentProposer; import com.sap.furcas.ide.editor.imp.services.FurcasLabelProvider; import com.sap.furcas.ide.editor.imp.services.FurcasTreeModelBuilder; import com.sap.furcas.ide.parserfactory.AbstractParserFactory; import com.sap.furcas.metamodel.FURCAS.TCS.ConcreteSyntax; import com.sap.furcas.runtime.common.exceptions.ParserInstantiationException; import com.sap.furcas.runtime.parser.PartitionAssignmentHandler; import com.sap.furcas.runtime.parser.impl.DefaultPartitionAssignmentHandlerImpl; import com.sap.furcas.runtime.parser.impl.ObservableInjectingParser; import com.sap.furcas.runtime.referenceresolving.SyntaxRegistry; import com.sap.ide.cts.parser.incremental.IncrementalParserFacade; /** * Base class for language specific FURCAS editors. <p> * * Clients that want to use this editor <b>must</b> register: * <ul> * <li>an IMP {@link Language} descriptor</li> * <li>the {@link FurcasParseController}</li> * <li>the editor itself</li> * </ul><br> * * Clients that want to use this editor <b>should</b> inherit from the <tt>FURCAS_BASE</tt> * language. It provides the following generic implementations: * <ul> * <li>the {@link FurcasContentProposer}</li> * <li>the {@link FurcasTreeModelBuilder}</li> * <li>the {@link FurcasLabelProvider}</li> * </ul><br> * * In addition, clients <b>can</b> implement and register other IMP services * such as the {@link ITokenColorer}. * * @author Stephan Erb * */ public class AbstractFurcasEditor extends UniversalEditor { /* Implementation notes: * * The editor is backed by the TextBlocks model and supports incremental parsing. * It is based on IMP and extends it by injecting a {@link CtsDocument} * via a custom {@link CtsDocumentProvider}. * * The {@link CtsDocument} contains the text presented to the user. * It is edited directly within the UI thread. The {@link FurcasParserController} * serves as a reconciler for this document. It is started automatically when document * has been altered. It reparses the document and creates/modifies the domain model * and the TextBlocks model accordingly. Reconciling happens in a background thread. * * All support classes, such as the Outline View ({@link FurcasTreeModelBuilder} and * {@link FurcasLabelProvider}) or the {@link FurcasSourcePositionLocator} operate on * the TextBlocks model. When required those navigate into the domain model. * */ /** * Make it easy to extract the editor content without directly depending on the editor. */ public class ContentProvider { public CtsDocument getDocument() { return getDocumentProvider().getDocument(getEditorInput()); } public void notifyDirtyPropertyChanged() { Display.getDefault().asyncExec(new Runnable() { @Override public void run() { firePropertyChange(IEditorPart.PROP_DIRTY); } }); } } private final TransactionalEditingDomain editingDomain; private final ConcreteSyntax syntax; private final AdapterFactory adapterFactory; private CtsDocumentProvider documentProvoider; protected final AbstractParserFactory<? extends ObservableInjectingParser, ? extends Lexer> parserFactory; private final SyntaxRegistry syntaxRegistry; private IncrementalParserFacade parserFacade; public AbstractFurcasEditor(AbstractParserFactory<? extends ObservableInjectingParser, ? extends Lexer> parserFactory) { this.parserFactory = parserFactory; this.editingDomain = createEditingDomain(); configureEditingDomain(editingDomain); this.syntax = (ConcreteSyntax) editingDomain.getResourceSet().getEObject(URI.createURI(parserFactory.getSyntaxUUID()), true); validateEditorState(syntax, parserFactory); this.adapterFactory = createAdapterFactory(); this.syntaxRegistry = new SyntaxRegistry(); } /** * Initialize the editor by transform the editor input into a {@link ModelEditorInput}. * The IMP editor will never see the original one, but only the one built here. * * This method is called <b>before</b> {@link #createPartControl(Composite)}. */ @Override public void init(IEditorSite site, IEditorInput input) throws PartInitException { ModelEditorInputLoader loader = new ModelEditorInputLoader(syntax, editingDomain.getResourceSet(), adapterFactory); ModelEditorInput modelEditorInput = loader.loadEditorInput(input); PartitionAssignmentHandler partitionHandler = createPartititionAssignmentHandler(modelEditorInput); parserFacade = createParserFacade(modelEditorInput, partitionHandler); SetupTextBlocksModelCommand command = new SetupTextBlocksModelCommand(editingDomain, modelEditorInput.getRootObject(), modelEditorInput.getRootBlock(), parserFacade, partitionHandler); editingDomain.getCommandStack().execute(command); modelEditorInput.setRootBlock(command.getResultBlock()); documentProvoider = new CtsDocumentProvider(modelEditorInput, editingDomain, partitionHandler); super.init(site, modelEditorInput.asLightWeightEditorInput()); syntaxRegistry.registerSyntaxForIncrementalEvaluation(syntax, parserFacade.getOppositeEndFinder(), new NullProgressMonitor(), parserFactory); syntaxRegistry.registerAllLoadedSyntaxesTriggerManagers(editingDomain.getResourceSet()); // Reset dirty state. It was changed by the initializing commands. ((BasicCommandStack) editingDomain.getCommandStack()).saveIsDone(); } protected IncrementalParserFacade createParserFacade(ModelEditorInput modelEditorInput, PartitionAssignmentHandler partitionHandler) { try { return new IncrementalParserFacade(parserFactory, editingDomain.getResourceSet(), partitionHandler); } catch (ParserInstantiationException e) { throw new RuntimeException("Failed to create the parser facade", e); } } /** * IMP uses this to initialize all services. We proceed with a deferred initialization to * inject FURCAS specific information into our parser service. * * This method is called <b>after</b> {@link #init(IEditorSite, IEditorInput)}. */ @Override public void createPartControl(Composite parent) { super.createPartControl(parent); // We can retrigger the parse controller via the parser scheduler. // The initialization of most other serivices cannot be delayed because // those are already required during super.createPartControl. getParseController().completeInit(editingDomain, new ContentProvider(), parserFacade); ((FurcasLabelProvider) fLanguageServiceManager.getLabelProvider()).plugProvider( new AdapterFactoryLabelProvider(adapterFactory)); // re-run IMP setup procedure with our fully configured services fParserScheduler.cancel(); fParserScheduler.setSystem(false); fParserScheduler.schedule(); getDocumentProvider().consumeModelEditorInput(); updateVisuals(); } private void updateVisuals() { EObject rootObject = getParseController().getCurrentRootObject(); if (rootObject != null) { // better safe than sorry AdapterFactoryLabelProvider provider = new AdapterFactoryLabelProvider(adapterFactory); setPartName(provider.getText(rootObject)); setTitleImage(provider.getImage(rootObject)); } } @Override public void doSave(IProgressMonitor progressMonitor) { super.doSave(progressMonitor); updateVisuals(); } /** * Can be overwritten by subclasses if needed. */ protected AdapterFactory createAdapterFactory() { @SuppressWarnings("hiding") ComposedAdapterFactory adapterFactory = new ComposedAdapterFactory(); adapterFactory.addAdapterFactory(new ResourceItemProviderAdapterFactory()); adapterFactory.addAdapterFactory(new ReflectiveItemProviderAdapterFactory()); return adapterFactory; } /** * Can be overwritten by subclasses if needed. * @param modelEditorInput */ protected PartitionAssignmentHandler createPartititionAssignmentHandler(ModelEditorInput modelEditorInput) { return new DefaultPartitionAssignmentHandlerImpl(); } /** * Can be overwritten by subclasses if needed (e.g., to use a shared editing domain); * If overwritten, consider to also overwrite {@link #disposeEditingDomain()}. * */ protected TransactionalEditingDomain createEditingDomain() { ResourceSet resourceSet = new ResourceSetImpl(); return WorkspaceEditingDomainFactory.INSTANCE.createEditingDomain(resourceSet); } protected void disposeEditingDomain() { editingDomain.dispose(); } @Override public void dispose() { super.dispose(); if (editingDomain != null) { syntaxRegistry.unregisterAllLoadedSyntaxesTriggerManagers(editingDomain.getResourceSet()); syntaxRegistry.unregisterSyntax(syntax); disposeEditingDomain(); } } private void configureEditingDomain(TransactionalEditingDomain domain) { TransactionalCommandStack commandStack = (TransactionalCommandStack) domain.getCommandStack(); commandStack.setExceptionHandler(new CommandStackExceptionHandler()); commandStack.addCommandStackListener(new CommandStackListener() { @Override public void commandStackChanged(EventObject event) { Display.getDefault().asyncExec(new Runnable() { @Override public void run() { firePropertyChange(IEditorPart.PROP_DIRTY); } }); } }); ECrossReferenceAdapter crossRefAdapter = new ECrossReferenceAdapter(); editingDomain.getResourceSet().eAdapters().add(crossRefAdapter); crossRefAdapter.setTarget(editingDomain.getResourceSet()); } private static void validateEditorState(ConcreteSyntax syntax, AbstractParserFactory<? extends ObservableInjectingParser, ? extends Lexer> parserFactory) { if (syntax == null) { String message = "Error loading syntax definition: No syntax definition for language \"" + parserFactory.getLanguageId() + "\" found. Make sure the editor project" + "is correctly referenced and the mapping model is available."; throw new RuntimeException(message); } } @Override public CtsDocumentProvider getDocumentProvider() { return documentProvoider; } @Override public FurcasParseController getParseController() { return (FurcasParseController) super.getParseController(); } }