org.eclipse.xtext.formatting2.debug.TextRegionAccessToString.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.xtext.formatting2.debug.TextRegionAccessToString.java

Source

/*******************************************************************************
 * Copyright (c) 2014 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.formatting2.debug;

import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.xtext.AbstractElement;
import org.eclipse.xtext.AbstractRule;
import org.eclipse.xtext.EcoreUtil2;
import org.eclipse.xtext.formatting2.regionaccess.IAstRegion;
import org.eclipse.xtext.formatting2.regionaccess.IComment;
import org.eclipse.xtext.formatting2.regionaccess.IEObjectRegion;
import org.eclipse.xtext.formatting2.regionaccess.IHiddenRegion;
import org.eclipse.xtext.formatting2.regionaccess.IHiddenRegionPart;
import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegion;
import org.eclipse.xtext.formatting2.regionaccess.ISequentialRegion;
import org.eclipse.xtext.formatting2.regionaccess.ITextRegionAccess;
import org.eclipse.xtext.formatting2.regionaccess.ITextSegment;
import org.eclipse.xtext.formatting2.regionaccess.IWhitespace;
import org.eclipse.xtext.grammaranalysis.impl.GrammarElementTitleSwitch;
import org.eclipse.xtext.util.EmfFormatter;

import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Multimap;

/**
 * @author Moritz Eysholdt - Initial contribution and API
 */
public class TextRegionAccessToString {

    protected static enum AstRegionComparator implements Comparator<IAstRegion> {
        CHILDREN_FIRST {
            @Override
            public int compare(IAstRegion o1, IAstRegion o2) {
                EObject e1 = o1.getSemanticElement();
                EObject e2 = o2.getSemanticElement();
                if (e1 == e2)
                    return 0;
                if (EcoreUtil.isAncestor(e1, e2))
                    return 1;
                return -1;
            }
        },
        CONTAINER_FIRST {
            @Override
            public int compare(IAstRegion o1, IAstRegion o2) {
                EObject e1 = o1.getSemanticElement();
                EObject e2 = o2.getSemanticElement();
                if (e1 == e2)
                    return 0;
                if (EcoreUtil.isAncestor(e1, e2))
                    return -1;
                return 1;
            }
        };
    }

    private static final int TITLE_WIDTH = 2;
    private static final String EMPTY_TITLE = Strings.repeat(" ", TITLE_WIDTH);
    private static final String EOBJECT_BEGIN_PADDED = Strings.padEnd("B", TITLE_WIDTH, ' ');
    private static final String EOBJECT_END_PADDED = Strings.padEnd("E", TITLE_WIDTH, ' ');
    private static final String HIDDEN = "H";
    private static final String HIDDEN_PADDED = Strings.padEnd(HIDDEN, TITLE_WIDTH, ' ');
    private static final String SEMANTIC_PADDED = Strings.padEnd("S", TITLE_WIDTH, ' ');

    private Function<AbstractElement, String> grammarToString = new GrammarElementTitleSwitch().showRule()
            .showAssignments().showQualified();

    private boolean hideColumnExplanation = false;

    private boolean hideIndentation = false;

    private boolean hightlightOrigin = false;

    private ITextSegment origin;

    private int textWidth = 20;

    public int getTextWidth() {
        return textWidth;
    }

    public TextRegionAccessToString hideColumnExplanation() {
        this.hideColumnExplanation = true;
        return this;
    }

    public TextRegionAccessToString hideIndentation() {
        this.hideIndentation = true;
        return this;
    }

    public TextRegionAccessToString hightlightOrigin() {
        this.hightlightOrigin = true;
        return this;
    }

    public boolean isHideColumnExplanation() {
        return hideColumnExplanation;
    }

    public boolean isHideIndentation() {
        return hideIndentation;
    }

    public boolean isHightlightOrigin() {
        return hightlightOrigin;
    }

    protected String quote(String string) {
        if (string == null)
            return "null";
        string = string.replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t");
        int textLen = textWidth - 2;
        if (string.length() > textLen)
            return "\"" + string.substring(0, textLen - 3) + "...\"";
        else
            return Strings.padEnd("\"" + string + "\"", textWidth, ' ');
    }

    protected String toClassWithName(EObject obj) {
        String className = obj.eClass().getName();
        EStructuralFeature nameFeature = obj.eClass().getEStructuralFeature("name");
        if (nameFeature != null) {
            Object name = obj.eGet(nameFeature);
            if (name != null)
                return className + "'" + name + "'";
        }
        return className;
    }

    @Override
    public String toString() {
        List<ITextSegment> list = toTokenAndGapList();
        if (list.isEmpty())
            return "(empty)";
        Multimap<IHiddenRegion, IEObjectRegion> hiddens = LinkedListMultimap.create();
        List<String> errors = Lists.newArrayList();
        ITextRegionAccess access = list.get(0).getTextRegionAccess();
        TreeIterator<EObject> all = EcoreUtil2.eAll(access.regionForRootEObject().getSemanticElement());
        while (all.hasNext()) {
            EObject element = all.next();
            IEObjectRegion obj = access.regionForEObject(element);
            if (obj == null)
                continue;
            IHiddenRegion previous = obj.getPreviousHiddenRegion();
            IHiddenRegion next = obj.getNextHiddenRegion();
            if (previous == null)
                errors.add("ERROR: " + EmfFormatter.objPath(element) + " has no leading HiddenRegion.");
            else
                hiddens.put(previous, obj);
            if (previous != next) {
                if (next == null)
                    errors.add("ERROR: " + EmfFormatter.objPath(element) + " has no trailing HiddenRegion.");
                else
                    hiddens.put(next, obj);
            }
        }
        TextRegionListToString result = new TextRegionListToString();
        if (!hideColumnExplanation) {
            result.add("Columns: 1:offset 2:length 3:kind 4: text 5:grammarElement", false);
            result.add("Kind: H=IHiddenRegion S=ISemanticRegion B/E=IEObjectRegion", false);
            result.add("", false);
        }
        for (String error : errors)
            result.add(error, false);
        int indentation = 0, min = 0;
        for (ITextSegment region : list) {
            if (region instanceof IHiddenRegion) {
                Collection<IEObjectRegion> found = hiddens.get((IHiddenRegion) region);
                for (IEObjectRegion obj : found) {
                    boolean p = obj.getNextHiddenRegion().equals(region);
                    boolean n = obj.getPreviousHiddenRegion().equals(region);
                    if (p)
                        indentation--;
                    else if (n)
                        indentation++;
                    if (indentation < min)
                        min = indentation;
                }
            }
        }
        indentation = min < 0 ? min * -1 : 0;
        for (ITextSegment region : list) {
            List<IEObjectRegion> previous = Lists.newArrayList();
            List<IEObjectRegion> next = Lists.newArrayList();
            List<String> middle = Lists.newArrayList(toString(region));
            if (region instanceof IHiddenRegion) {
                Collection<IEObjectRegion> found = hiddens.get((IHiddenRegion) region);
                for (IEObjectRegion obj : found) {
                    boolean p = obj.getNextHiddenRegion().equals(region);
                    boolean n = obj.getPreviousHiddenRegion().equals(region);
                    if (p && n)
                        middle.add(EMPTY_TITLE + "Semantic " + toString(obj));
                    else if (p)
                        previous.add(obj);
                    else if (n)
                        next.add(obj);
                }
                Collections.sort(previous, AstRegionComparator.CHILDREN_FIRST);
                Collections.sort(next, AstRegionComparator.CONTAINER_FIRST);
            }
            for (IEObjectRegion obj : previous) {
                indentation--;
                result.add(indent(indentation) + EOBJECT_END_PADDED + toString(obj));
            }
            String indent = indent(indentation);
            result.add(region, indent + Joiner.on("\n").join(middle).replace("\n", "\n" + indent));
            for (IEObjectRegion obj : next) {
                result.add(indent(indentation) + EOBJECT_BEGIN_PADDED + toString(obj));
                indentation++;
            }
        }
        return result.toString();
    }

    protected String indent(int indentation) {
        if (hideIndentation)
            return "";
        return Strings.repeat(" ", indentation);
    }

    protected String toString(AbstractRule rule) {
        return rule == null ? "null" : rule.getName();
    }

    protected String toString(EObject ele) {
        if (ele instanceof AbstractElement)
            return grammarToString.apply((AbstractElement) ele);
        if (ele instanceof AbstractRule)
            return ele.eClass().getName() + "'" + ((AbstractRule) ele).getName() + "'";
        return ele.toString();
    }

    protected String toString(IComment comment) {
        String text = quote(comment.getText());
        String gammar = toString(comment.getGrammarElement());
        return String.format("%s Comment:%s", text, gammar);
    }

    protected String toString(IEObjectRegion region) {
        EObject obj = region.getSemanticElement();
        StringBuilder builder = new StringBuilder(Strings.padEnd(toClassWithName(obj), textWidth, ' ') + " ");
        EObject element = region.getGrammarElement();
        if (element instanceof AbstractElement)
            builder.append(grammarToString.apply((AbstractElement) element));
        else if (element instanceof AbstractRule)
            builder.append(((AbstractRule) element).getName());
        else
            builder.append(": ERROR: No grammar element.");
        List<String> segments = Lists.newArrayList();
        EObject current = obj;
        while (current.eContainer() != null) {
            EObject container = current.eContainer();
            EStructuralFeature containingFeature = current.eContainingFeature();
            StringBuilder segment = new StringBuilder();
            segment.append(toClassWithName(container));
            segment.append("/");
            segment.append(containingFeature.getName());
            if (containingFeature.isMany()) {
                int index = ((List<?>) container.eGet(containingFeature)).indexOf(current);
                segment.append("[" + index + "]");
            }
            current = container;
            segments.add(segment.toString());
        }
        if (!segments.isEmpty()) {
            builder.append(" path:");
            builder.append(Joiner.on("=").join(segments));
        }
        return builder.toString();
    }

    protected String toString(IHiddenRegion hiddens) {
        List<IHiddenRegionPart> parts = hiddens.getParts();
        if (parts.isEmpty())
            return HIDDEN;
        List<String> children = Lists.newArrayListWithExpectedSize(parts.size());
        children.add(HIDDEN_PADDED + toString(parts.get(0)));
        for (int i = 1; i < parts.size(); i++)
            children.add(EMPTY_TITLE + toString(parts.get(i)));
        return Joiner.on("\n").join(children);
    }

    protected String toString(ISemanticRegion token) {
        String text = quote(token.getText());
        return String.format("%s%s %s", SEMANTIC_PADDED, text, toString(token.getGrammarElement()));
    }

    protected String toString(ITextSegment region) {
        String result;
        if (region instanceof IEObjectRegion)
            result = toString((IEObjectRegion) region);
        else if (region instanceof ISemanticRegion)
            result = toString((ISemanticRegion) region);
        else if (region instanceof IHiddenRegion)
            result = toString((IHiddenRegion) region);
        else if (region instanceof IWhitespace)
            result = toString((IWhitespace) region);
        else if (region instanceof IComment)
            result = toString((IComment) region);
        else if (region != null)
            result = region.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(region));
        else
            result = "null";
        if (hightlightOrigin && region == origin)
            return ">>>" + result + "<<<";
        return result;
    }

    protected String toString(IWhitespace whitespace) {
        String text = quote(whitespace.getText());
        String grammar = toString(whitespace.getGrammarElement());
        return String.format("%s Whitespace:%s", text, grammar);
    }

    protected List<ITextSegment> toTokenAndGapList() {
        final int range = this.hightlightOrigin ? 4 : (Integer.MAX_VALUE / 2);
        ITextSegment first = null;
        {
            ITextSegment current = origin;
            for (int i = 0; i < range && current != null; i++) {
                first = current;
                if (current instanceof ITextRegionAccess)
                    current = ((ITextRegionAccess) current).regionForRootEObject().getPreviousHiddenRegion();
                else if (current instanceof ISequentialRegion)
                    current = ((ISequentialRegion) current).getPreviousHiddenRegion();
                else if (current instanceof IHiddenRegionPart)
                    current = ((IHiddenRegionPart) current).getHiddenRegion();
                else
                    throw new IllegalStateException("Unexpected Type: " + current.getClass());
            }
        }
        if (first == null)
            return Collections.emptyList();
        List<ITextSegment> result = Lists.newArrayList();
        {
            ITextSegment current = first;
            for (int i = 0; i <= (range * 2) && current != null; i++) {
                result.add(current);
                if (current instanceof ISemanticRegion)
                    current = ((ISemanticRegion) current).getNextHiddenRegion();
                else if (current instanceof IHiddenRegion)
                    current = ((IHiddenRegion) current).getNextSemanticRegion();
                else
                    throw new IllegalStateException("Unexpected Type: " + current.getClass());
            }
        }
        return result;
    }

    public TextRegionAccessToString withOrigin(ITextSegment origin) {
        this.origin = origin;
        return this;
    }

    public TextRegionAccessToString withRegionAccess(ITextRegionAccess access) {
        this.origin = access.regionForRootEObject();
        this.hightlightOrigin = false;
        return this;
    }

    public TextRegionAccessToString withTextWidth(int width) {
        this.textWidth = width;
        return this;
    }

}