org.eclipse.xtext.xbase.ui.editor.XbaseEditor.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.xtext.xbase.ui.editor.XbaseEditor.java

Source

/*******************************************************************************
 * Copyright (c) 2012 itemis AG (http://www.itemis.eu) 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
 *******************************************************************************/
package org.eclipse.xtext.xbase.ui.editor;

import java.util.ResourceBundle;
import java.util.Set;

import org.apache.log4j.Logger;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IStorage;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.ITypeRoot;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextOperationTarget;
import org.eclipse.jface.text.source.projection.ProjectionViewer;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.eclipse.ui.texteditor.ITextEditorActionConstants;
import org.eclipse.ui.texteditor.TextEditorAction;
import org.eclipse.xtext.builder.EclipseOutputConfigurationProvider;
import org.eclipse.xtext.common.types.ui.trace.ITraceForTypeRootProvider;
import org.eclipse.xtext.generator.OutputConfiguration;
import org.eclipse.xtext.generator.trace.ILocationInResource;
import org.eclipse.xtext.generator.trace.ITrace;
import org.eclipse.xtext.ui.editor.XtextEditor;
import org.eclipse.xtext.util.Files;
import org.eclipse.xtext.util.ITextRegion;
import org.eclipse.xtext.util.ITextRegionWithLineInformation;
import org.eclipse.xtext.util.TextRegion;
import org.eclipse.xtext.xbase.ui.editor.actions.IClipboardActionFactory;

import com.google.inject.Inject;

/**
 * This enhances the Xtext editor with functionality that allows to reveal the originating offsets for selected java
 * elements. Thus the XbaseEditor is capable to work with {@link IEditorInput input} that points to
 * {@link JavaCore#isJavaLikeFileName(String) java resources}.
 * 
 * @author Sebastian Zarnekow - Initial contribution and API
 */
public class XbaseEditor extends XtextEditor {

    private final static Logger log = Logger.getLogger(XbaseEditor.class);

    protected static final String HANDLER_IDENTIFIER = "HANDLER_IDENTIFIER"; //$NON-NLS-1$

    @Inject
    private ITraceForTypeRootProvider traceInformation;

    @Inject
    private StacktraceBasedEditorDecider calleeAnalyzer;

    @Inject
    private EclipseOutputConfigurationProvider outputConfigurationProvider;

    @Inject
    private XbaseEditorInputRedirector editorInputRedirector;
    /**
     * {@link IClipboardActionFactory} may be optional. If so, the default CCP {@link TextEditorAction}'s added in
     * {@link org.eclipse.ui.texteditor.AbstractTextEditor} will be used.
     * 
     * @see #createClipboardActions()
     * @see org.eclipse.xtext.xbase.ui.editor.actions.ImportsAwareClipboardAction.Factory
     */
    @Inject(optional = true)
    private IClipboardActionFactory clipboardActionFactory;
    private ITypeRoot typeRoot = null;

    private int expectJavaSelection = 0;
    private boolean expectLineSelection = false;
    private boolean isIgnoreCall = false;
    private @Inject FoldingPreferences foldingPreferences;

    @Override
    public void saveState(IMemento memento) {
        super.saveState(memento);
        if (typeRoot != null) {
            memento.putString(HANDLER_IDENTIFIER, typeRoot.getHandleIdentifier());
        }
    }

    @Override
    protected void doRestoreState(IMemento memento) {
        super.doRestoreState(memento);
        String handleIdentifier = memento.getString(HANDLER_IDENTIFIER);
        if (handleIdentifier != null) {
            IJavaElement handle = JavaCore.create(handleIdentifier);
            if (handle instanceof ITypeRoot && handle.exists()) {
                typeRoot = (ITypeRoot) handle;
            }
        }
    }

    @Override
    protected boolean containsSavedState(IMemento memento) {
        boolean result = super.containsSavedState(memento);
        if (!result) {
            return memento.getString(HANDLER_IDENTIFIER) != null;
        }
        return result;
    }

    @Override
    protected void doSetInput(IEditorInput input) throws CoreException {
        try {
            // TODO set javaResource to null if input is Xbase file that does not match the Java case (needs reversed trace data)
            IEditorInput inputToUse = editorInputRedirector.findOriginalSource(input);
            super.doSetInput(inputToUse);
            ITypeRoot typeRoot = editorInputRedirector.getTypeRoot(input);
            if (typeRoot != null) {
                markNextSelectionAsJavaOffset(typeRoot);
            }
            return;
        } catch (CoreException e) {
            // ignore
        }
        super.doSetInput(input);
    }

    private Exception lastCall = null;

    public void markNextSelectionAsJavaOffset(ITypeRoot typeRoot) {
        if (expectJavaSelection > 0) {
            if (!isIgnoreCall) {
                if (lastCall != null) {
                    log.warn("The editor is already awaiting a select and reveal call from : ", lastCall);
                    log.warn("Now called from : ", new Exception());
                }
                this.expectJavaSelection = 0;
            } else {
                this.expectJavaSelection = 0;
            }
        }

        lastCall = new Exception();
        this.expectJavaSelection++;
        if (calleeAnalyzer.isEditorUtilityIsOpenInEditor() || calleeAnalyzer.isOpenEditorAction())
            isIgnoreCall = true;
        else
            isIgnoreCall = false;
        if (calleeAnalyzer.isCalledFromFindReferences())
            this.expectJavaSelection++;
        this.typeRoot = typeRoot;
    }

    int reentrantCallFromSelf = 0;

    @Override
    protected void safelySanityCheckState(IEditorInput input) {
        try {
            reentrantCallFromSelf++;
            super.safelySanityCheckState(input);
        } finally {
            reentrantCallFromSelf--;
        }
    }

    @Override
    public boolean isDirty() {
        try {
            reentrantCallFromSelf++;
            return super.isDirty();
        } finally {
            reentrantCallFromSelf--;
        }
    }

    @Override
    public IDocumentProvider getDocumentProvider() {
        if (expectJavaSelection > 0 && reentrantCallFromSelf == 0) {
            if (calleeAnalyzer.isLineBasedOpenEditorAction()) {
                expectLineSelection = true;
                if (isCompiledWithJSR45()) {
                    return new DocumentProviderStub() {
                        @Override
                        public IDocument getDocument(Object element) {
                            if (typeRoot == null) {
                                return XbaseEditor.super.getDocumentProvider().getDocument(element);
                            }
                            IResource javaResource = typeRoot.getResource();
                            if (!(javaResource instanceof IStorage)) {
                                return XbaseEditor.super.getDocumentProvider().getDocument(element);
                            }
                            try {
                                String string = Files.readStreamIntoString(((IStorage) javaResource).getContents());
                                final Document document = new Document(string);
                                return document;
                            } catch (CoreException e) {
                                return XbaseEditor.super.getDocumentProvider().getDocument(element);
                            }
                        }

                        @Override
                        public void connect(Object element) throws CoreException {
                            // do nothing
                        }

                        @Override
                        public void disconnect(Object element) {
                            // do nothing
                        }
                    };
                }
            }
        }
        return super.getDocumentProvider();
    }

    @Override
    protected void selectAndReveal(final int selectionStart, final int selectionLength, final int revealStart,
            final int revealLength) {
        try {
            reentrantCallFromSelf++;
            if (expectJavaSelection > 0) {
                try {
                    ITrace traceToSource = getTraceStorage();
                    if (traceToSource != null) {
                        IResource javaResource = typeRoot.getResource();
                        if (expectLineSelection && javaResource instanceof IStorage) {
                            if (isCompiledWithJSR45()) {
                                try {
                                    String string = Files
                                            .readStreamIntoString(((IStorage) javaResource).getContents());
                                    Document javaDocument = new Document(string);
                                    int line = getLineInJavaDocument(javaDocument, selectionStart, selectionLength);
                                    if (line != -1) {
                                        int startOffsetOfContents = getStartOffsetOfContentsInJava(javaDocument,
                                                line);
                                        if (startOffsetOfContents != -1) {
                                            ILocationInResource bestSelection = traceToSource
                                                    .getBestAssociatedLocation(
                                                            new TextRegion(startOffsetOfContents, 0));
                                            if (bestSelection != null) {
                                                final ITextRegionWithLineInformation textRegion = bestSelection
                                                        .getTextRegion();
                                                if (textRegion != null) {
                                                    int lineToSelect = textRegion.getLineNumber();
                                                    try {
                                                        IRegion lineInfo = getDocument()
                                                                .getLineInformation(lineToSelect);
                                                        super.selectAndReveal(lineInfo.getOffset(),
                                                                lineInfo.getLength(), lineInfo.getOffset(),
                                                                lineInfo.getLength());
                                                        return;
                                                    } catch (BadLocationException e) {
                                                        log.error(e.getMessage(), e);
                                                    }
                                                }
                                            }
                                        }
                                    }
                                } catch (BadLocationException e) {
                                    // do nothing
                                } catch (CoreException e) {
                                    // do nothing
                                }
                            }
                        } else if (selectionStart >= 0 && selectionLength >= 0) {
                            ILocationInResource bestSelection = traceToSource
                                    .getBestAssociatedLocation(new TextRegion(selectionStart, selectionLength));
                            if (bestSelection != null) {
                                ILocationInResource bestReveal = bestSelection;
                                if (selectionStart != revealStart || selectionLength != revealLength) {
                                    bestReveal = traceToSource
                                            .getBestAssociatedLocation(new TextRegion(revealStart, revealLength));
                                    if (bestReveal == null) {
                                        bestReveal = bestSelection;
                                    }
                                }
                                ITextRegion fixedSelection = bestSelection.getTextRegion();
                                if (fixedSelection != null) {
                                    ITextRegion fixedReveal = bestReveal.getTextRegion();
                                    if (fixedReveal == null) {
                                        fixedReveal = fixedSelection;
                                    }
                                    super.selectAndReveal(fixedSelection.getOffset(), fixedSelection.getLength(),
                                            fixedReveal.getOffset(), fixedReveal.getLength());
                                    return;
                                }
                            }
                        }
                    }
                } finally {
                    expectLineSelection = false;
                    expectJavaSelection--;
                }
            }
            super.selectAndReveal(selectionStart, selectionLength, revealStart, revealLength);
        } finally {
            reentrantCallFromSelf--;
        }
    }

    protected ITrace getTraceStorage() {
        if (typeRoot == null) {
            return null;
        }
        return traceInformation.getTraceToSource(typeRoot);
    }

    protected ILocationInResource getLocationInResource(ITrace traceToSource) {
        if (traceToSource == null) {
            return null;
        }
        for (ILocationInResource locationInResource : traceToSource.getAllAssociatedLocations()) {
            return locationInResource;
        }
        return null;
    }

    protected boolean isCompiledWithJSR45() {
        //TODO the information whether this was compiled with JSR-45, needs to be done on a per resource base, since a project might 
        // have a different configuration than its jars. Storing it in the trace file (together with other compilation options and version information) seems appropriate.
        // is this editor is not based on a resource it doesn't live in a project. We don't know whether it was compiled with JSR-45.
        if (getResource() == null)
            return true;
        Set<OutputConfiguration> configurations = outputConfigurationProvider
                .getOutputConfigurations(getResource().getProject());
        for (OutputConfiguration config : configurations) {
            if (config.isInstallDslAsPrimarySource()) {
                return false;
            }
        }
        return true;
    }

    /**
     * Returns the line of the given selection. It is assumed that it covers an entire line in the Java file.
     * 
     * @return the line in the Java file (zero based) or <code>-1</code> if the selection does not cover a complete
     *         line.
     */
    protected int getLineInJavaDocument(Document document, int selectionStart, int selectionLength)
            throws BadLocationException {
        int line = document.getLineOfOffset(selectionStart);
        int length = document.getLineLength(line);
        int lineOffset = document.getLineOffset(line);
        if (lineOffset == selectionStart && length == selectionLength) {
            return line;
        }
        IRegion region = document.getLineInformation(line);
        if (region.getOffset() == selectionStart || region.getLength() == selectionLength) {
            return line;
        }
        return -1;
    }

    protected int getStartOffsetOfContentsInJava(Document document, int line) throws BadLocationException {
        IRegion lineInformation = document.getLineInformation(line);
        String lineText = document.get(lineInformation.getOffset(), lineInformation.getLength());
        String contents = lineText.trim();
        if (contents.length() == 0) {
            log.warn("selection points to an empty line!", new IllegalStateException());
            return -1;
        }
        int contentsStarts = lineText.indexOf(contents);
        return lineInformation.getOffset() + contentsStarts;
    }

    @Override
    public void reveal(int offset, int length) {
        /* 
         * This is only overwritten for documentation purpose.
         * It is not necessary to fix the offset and length for #reveal since this API is 
         * private to Xtext and never used from JDT thus the given offset and length are
         * always relative the our own objects and not relative the the derived java files.
         */
        super.reveal(offset, length);
    }

    @Override
    protected void createActions() {
        super.createActions();
        createClipboardActions();
    }

    /**
     * replace default cut/copy/paste actions with a version that provided by the factory, if one is injected
     */
    protected void createClipboardActions() {
        IClipboardActionFactory actionsFactory = getClipboardActionFactory();
        if (actionsFactory != null) {
            ResourceBundle bundle = XbaseEditorMessages.getBundleForConstructedKeys();
            TextEditorAction action = actionsFactory.create(bundle, "Editor.Cut.", this, ITextOperationTarget.CUT); //$NON-NLS-1$
            setAction(ITextEditorActionConstants.CUT, action);
            action = actionsFactory.create(bundle, "Editor.Copy.", this, ITextOperationTarget.COPY); //$NON-NLS-1$
            setAction(ITextEditorActionConstants.COPY, action);
            action = actionsFactory.create(bundle, "Editor.Paste.", this, ITextOperationTarget.PASTE); //$NON-NLS-1$
            setAction(ITextEditorActionConstants.PASTE, action);
        }
    }

    protected IClipboardActionFactory getClipboardActionFactory() {
        return clipboardActionFactory;
    }

    @Override
    protected void installFoldingSupport(ProjectionViewer projectionViewer) {
        if (foldingPreferences.isEnabled()) {
            super.installFoldingSupport(projectionViewer);
        }
    }

    @Override
    protected void handlePreferenceStoreChanged(PropertyChangeEvent event) {
        super.handlePreferenceStoreChanged(event);
        String property = event.getProperty();
        if (property.equals(org.eclipse.jdt.ui.PreferenceConstants.EDITOR_FOLDING_ENABLED)) {
            ProjectionViewer projectionViewer = (ProjectionViewer) getSourceViewer();
            if (event.getNewValue() != null && Boolean.valueOf(event.getNewValue().toString())) {
                if (!projectionViewer.isProjectionMode()) {
                    installFoldingSupport(projectionViewer);
                }
            } else {
                if (projectionViewer.isProjectionMode()) {
                    uninstallFoldingSupport();
                    projectionViewer.disableProjection();
                }
            }
        }
    }
}