org.isandlatech.plugins.rest.editor.outline.OutlineUtil.java Source code

Java tutorial

Introduction

Here is the source code for org.isandlatech.plugins.rest.editor.outline.OutlineUtil.java

Source

/*******************************************************************************
 * Copyright (c) 2011 isandlaTech, Thomas Calmant
 * 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:
 *    Thomas Calmant (isandlaTech) - initial API and implementation
 *******************************************************************************/

package org.isandlatech.plugins.rest.editor.outline;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DocumentRewriteSession;
import org.eclipse.jface.text.DocumentRewriteSessionType;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension4;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.jface.viewers.TreeSelection;
import org.isandlatech.plugins.rest.RestPlugin;
import org.isandlatech.plugins.rest.parser.RestLanguage;
import org.isandlatech.plugins.rest.prefs.IEditorPreferenceConstants;

/**
 * Utility class for outline operations
 * 
 * @author Thomas Calmant
 */
public final class OutlineUtil {

    /**
     * Retrieves the region corresponding to the given section content with all
     * its children
     * 
     * @param aSectionNode
     *            Section to select
     * @return The section content region
     */
    public static IRegion getCompleteSection(final TreeData aSectionNode) {

        // Compute the section start
        int offset = getCompleteSectionOffset(aSectionNode);

        // Compute the length
        int length = getCompleteSectionLength(aSectionNode);

        return new Region(offset, length);
    }

    /**
     * Computes the length of the complete section content, including title
     * upper line.
     * 
     * @param aSectionNode
     *            The section to handle
     * @return The section length, 0 on error
     */
    public static int getCompleteSectionLength(final TreeData aSectionNode) {

        if (aSectionNode == null) {
            return 0;
        }

        IDocument document = aSectionNode.getDocument();
        TreeData nextNode = aSectionNode.getNext();

        int offset = getCompleteSectionOffset(aSectionNode);
        int length = 0;

        if (nextNode == null) {
            // Select everything until the end of document
            length = document.getLength() - offset;

        } else {
            // Select everything until the beginning of the next section
            length = getCompleteSectionOffset(nextNode) - offset;
        }

        return Math.max(length, 0);
    }

    /**
     * Returns the real offset of the given section : the first character offset
     * of the title or the decorative upper line
     * 
     * @param aSectionNode
     *            Section to handle
     * @return The given section real offset, upper line included, 0 on error
     */
    public static int getCompleteSectionOffset(final TreeData aSectionNode) {

        if (aSectionNode == null) {
            return 0;
        }

        IDocument document = aSectionNode.getDocument();
        int offset = aSectionNode.getLineOffset();

        if (document != null && aSectionNode.isUpperlined()) {
            // Line - 1, 1 based
            int line = aSectionNode.getLine() - 2;

            try {
                offset = document.getLineOffset(line);

            } catch (BadLocationException e) {
                offset = aSectionNode.getLineOffset();
            }
        }

        return Math.max(offset, 0);
    }

    /**
     * Retrieves the region corresponding to the given section title, decorating
     * lines included
     * 
     * @param aSectionNode
     *            Section to select
     * @return The section title block, null on error
     */
    public static IRegion getSectionTitleBlock(final TreeData aSectionNode) {

        IDocument document = aSectionNode.getDocument();
        if (document == null) {
            return null;
        }

        // Do not handle logical nodes
        if (aSectionNode.getLevel() <= 0) {
            return null;
        }

        int blockOffset = getCompleteSectionOffset(aSectionNode);

        // Compute block length
        int blockLength = 0;
        int line = aSectionNode.getLine();

        // Upper line
        try {
            if (aSectionNode.isUpperlined()) {
                blockLength += document.getLineLength(line - 2);
            }

            // Title
            blockLength += document.getLineLength(line - 1);

            // Under line
            blockLength += document.getLineLength(line);

        } catch (BadLocationException e) {
            RestPlugin.logError("Error retrieving title block", e);
            return null;
        }

        return new Region(blockOffset, blockLength);
    }

    /**
     * Rewrites section and subsections titles blocks to use the preferred
     * marker for its level. Reads the preferred markers from the editor
     * preferences.
     * 
     * It is recommended to have a least 6 preferred markers.
     * 
     * @param aSectionNode
     *            Base node to modify (its children will be modified to)
     */
    public static void normalizeSectionsMarker(final TreeData aSectionNode) {

        if (aSectionNode == null) {
            return;
        }

        // Get preferred markers
        IPreferenceStore preferenceStore = RestPlugin.getDefault().getPreferenceStore();
        char[] preferredMarkersArray;

        // Try current preferences
        String preferredMarkers = preferenceStore.getString(IEditorPreferenceConstants.EDITOR_SECTION_MARKERS);

        if (preferredMarkers == null || preferredMarkers.isEmpty()) {
            // Else, try default preferences
            preferredMarkers = preferenceStore.getDefaultString(IEditorPreferenceConstants.EDITOR_SECTION_MARKERS);
        }

        if (preferredMarkers == null || preferredMarkers.isEmpty()) {
            // Else, use language definitions
            preferredMarkersArray = RestLanguage.SECTION_DECORATIONS;
        } else {

            preferredMarkersArray = preferredMarkers.toCharArray();
        }

        IDocument document = aSectionNode.getDocument();

        // Indicate that we will perform multiple replacements on the document
        DocumentRewriteSession rewriteSession = null;
        if (document instanceof IDocumentExtension4) {
            rewriteSession = ((IDocumentExtension4) document)
                    .startRewriteSession(DocumentRewriteSessionType.SEQUENTIAL);
        }

        // Convert simple marker into section decorators objects
        SectionDecoration[] preferredDecoratorsArray = new SectionDecoration[preferredMarkersArray.length];

        int i = 0;
        for (char marker : preferredMarkersArray) {
            preferredDecoratorsArray[i++] = new SectionDecoration(marker, false);
        }

        normalizeSectionsMarker(aSectionNode, preferredDecoratorsArray);

        // Stop rewrite session
        if (rewriteSession != null) {
            ((IDocumentExtension4) document).stopRewriteSession(rewriteSession);
        }
    }

    /**
     * Rewrites section and subsections titles blocks to use the preferred
     * marker for its level.
     * 
     * It is recommended to have a least 6 preferred markers.
     * 
     * @param aSectionNode
     *            Base node to modify (its children will be modified to)
     * @param aMarkers
     *            Preferred markers array.
     */
    private static void normalizeSectionsMarker(final TreeData aSectionNode, final SectionDecoration[] aMarkers) {

        int sectionLevel = aSectionNode.getLevel();

        // Treat children
        TreeData[] subSections = aSectionNode.getChildrenArray();

        for (int i = subSections.length - 1; i >= 0; i--) {
            normalizeSectionsMarker(subSections[i], aMarkers);
        }

        // Ignore logical nodes (level <= 0)
        if (sectionLevel > 0) {

            // Rebase to 0
            sectionLevel--;

            // Don't use more than the given amount of markers
            if (sectionLevel >= aMarkers.length) {
                sectionLevel = aMarkers.length - 1;
            }

            replaceSectionMarker(aSectionNode, aMarkers[sectionLevel]);
        }
    }

    /**
     * Selects the outline nodes corresponding to the given previous selection
     * 
     * @param aOutline
     *            Outline page
     * @param aPreviousSelection
     *            Previous selection state
     */
    public static void postUpdateSelection(final RestContentOutlinePage aOutline,
            final TreeSelection aPreviousSelection) {

        List<TreePath> newSelectedPaths = new ArrayList<TreePath>(aPreviousSelection.size());

        TreeData treeRoot = aOutline.getContentProvider().getRoot();

        Iterator<?> iterator = aPreviousSelection.iterator();
        while (iterator.hasNext()) {
            TreeData nodeData = (TreeData) iterator.next();

            // Find the new node corresponding to the old one
            TreeData newNodedata = treeRoot.find(nodeData);
            if (newNodedata != null) {
                newSelectedPaths.add(newNodedata.getTreePath());
            }
        }

        TreeSelection newSelection = new TreeSelection(newSelectedPaths.toArray(new TreePath[0]));

        aOutline.setSelection(newSelection);
    }

    /**
     * Replaces section title decoration lines
     * 
     * @param aSectionNode
     *            The section to be modified
     * @param aNewMarker
     *            The new marker to use
     */
    public static void replaceSectionMarker(final TreeData aSectionNode, final SectionDecoration aNewMarker) {

        final IDocument document = aSectionNode.getDocument();
        if (document == null) {
            return;
        }

        // Find old section block bounds
        IRegion sectionBlock = getSectionTitleBlock(aSectionNode);
        if (sectionBlock == null) {
            return;
        }

        // Use document line delimiter
        final String endOfLine = TextUtilities.getDefaultLineDelimiter(document);

        final String sectionTitle = aSectionNode.getText();

        // Prepare the decoration line
        char[] decorationArray = new char[sectionTitle.length()];
        Arrays.fill(decorationArray, aNewMarker.getMarker());

        StringBuilder newSectionBlock = new StringBuilder(sectionTitle.length() + endOfLine.length());

        // Add upperline, if needed
        if (aNewMarker.isUpperlined()) {
            newSectionBlock.append(decorationArray);
            newSectionBlock.append(endOfLine);
        }

        // Section title
        newSectionBlock.append(sectionTitle);
        newSectionBlock.append(endOfLine);

        // Underline
        newSectionBlock.append(decorationArray);
        newSectionBlock.append(endOfLine);

        // Replace section block in document
        try {
            document.replace(sectionBlock.getOffset(), sectionBlock.getLength(), newSectionBlock.toString());
        } catch (BadLocationException e) {
            RestPlugin.logError("Error replacing section markers", e);
        }
    }

    /**
     * Hidden constructor
     */
    private OutlineUtil() {
        // Hide constructor
    }
}