org.eclipse.xtext.generator.trace.AbstractTraceRegion.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.xtext.generator.trace.AbstractTraceRegion.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.generator.trace;

import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.eclipse.emf.common.util.AbstractTreeIterator;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.xtext.util.ITextRegionWithLineInformation;
import org.eclipse.xtext.util.Pair;
import org.eclipse.xtext.util.TextRegionWithLineInformation;
import org.eclipse.xtext.util.Tuples;

import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.primitives.Ints;

/**
 * Abstract base class for trace regions. Implements the {@link #equals(Object)} and {@link #hashCode()} contract and
 * delegates some functionality to the parent.
 * 
 * @author Sebastian Zarnekow - Initial contribution and API
 */
public abstract class AbstractTraceRegion {

    private AbstractTraceRegion parent;
    private List<AbstractTraceRegion> nestedRegions;

    /**
     * Creates a new trace region and adds it to the parent if a parent was given.
     */
    protected AbstractTraceRegion(AbstractTraceRegion parent) {
        setParent(parent);
    }

    protected boolean isConsistentWithParent() {
        AbstractTraceRegion parent = getParent();
        if (parent == null)
            return true;
        if (parent.getMyOffset() > getMyOffset())
            return false;
        if (parent.getMyOffset() + parent.getMyLength() < getMyOffset() + getMyLength())
            return false;
        if (parent.getMyLineNumber() > getMyLineNumber())
            return false;
        if (parent.getMyEndLineNumber() < getMyEndLineNumber())
            return false;
        List<AbstractTraceRegion> siblings = parent.getNestedRegions();
        if (siblings.size() >= 2 && siblings.get(siblings.size() - 1) == this) {
            AbstractTraceRegion prev = siblings.get(siblings.size() - 2);
            if (prev.getMyEndLineNumber() > getMyLineNumber()) {
                return false;
            }
        }
        return true;
    }

    /**
     * Sets the parent (if any) and maintains the {@link #getWritableNestedRegions() nested regions} of the parent.
     * 
     * @see #setAsChildIn(AbstractTraceRegion)
     * @param parent
     *            the parent or <code>null</code> if none.
     */
    public void setParent(AbstractTraceRegion parent) {
        this.parent = parent;
        if (parent != null)
            setAsChildIn(parent);
    }

    protected void setAsChildIn(AbstractTraceRegion parent) {
        parent.getWritableNestedRegions().add(this);
    }

    /**
     * Returns the nested trace regions. The list does not necessarily contain all the regions that will be returned by
     * the {@link #leafIterator()}.
     * 
     * @return the list of directly nested regions.
     */
    public final List<AbstractTraceRegion> getNestedRegions() {
        if (nestedRegions == null)
            return Collections.emptyList();
        return Collections.unmodifiableList(nestedRegions);
    }

    protected final List<AbstractTraceRegion> getWritableNestedRegions() {
        if (nestedRegions == null)
            nestedRegions = Lists.newArrayListWithCapacity(4);
        return nestedRegions;
    }

    public List<AbstractTraceRegion> invertFor(SourceRelativeURI expectedAssociatedPath, SourceRelativeURI myPath) {
        Map<SourceRelativeURI, List<Pair<ILocationData, AbstractTraceRegion>>> matchingLocations = collectMatchingLocations(
                expectedAssociatedPath);
        List<Pair<ILocationData, AbstractTraceRegion>> expectedMatchingLocations = matchingLocations
                .get(expectedAssociatedPath);
        if (expectedMatchingLocations == null)
            return Collections.emptyList();
        inplaceSortByOffset(expectedMatchingLocations);
        List<AbstractTraceRegion> result = toInvertedTraceRegions(expectedMatchingLocations, myPath);
        return result;
    }

    public Map<SourceRelativeURI, List<AbstractTraceRegion>> invertAll(SourceRelativeURI myPath) {
        Map<SourceRelativeURI, List<Pair<ILocationData, AbstractTraceRegion>>> matchingLocations = collectMatchingLocations(
                null);
        Map<SourceRelativeURI, List<AbstractTraceRegion>> result = Maps
                .newHashMapWithExpectedSize(matchingLocations.size());
        for (SourceRelativeURI uri : matchingLocations.keySet()) {
            List<Pair<ILocationData, AbstractTraceRegion>> expectedMatchingLocations = matchingLocations.get(uri);
            if (expectedMatchingLocations != null) {
                inplaceSortByOffset(expectedMatchingLocations);
                List<AbstractTraceRegion> resultPerURI = toInvertedTraceRegions(expectedMatchingLocations, myPath);
                result.put(uri, resultPerURI);
            }
        }
        return result;
    }

    protected Map<SourceRelativeURI, List<Pair<ILocationData, AbstractTraceRegion>>> collectMatchingLocations(
            SourceRelativeURI expectedAssociatedPath) {
        Map<SourceRelativeURI, List<Pair<ILocationData, AbstractTraceRegion>>> result = Maps
                .newHashMapWithExpectedSize(2);
        Iterator<AbstractTraceRegion> treeIterator = treeIterator();
        while (treeIterator.hasNext()) {
            AbstractTraceRegion next = treeIterator.next();
            SourceRelativeURI associatedPath = next.getAssociatedSrcRelativePath();
            List<Pair<ILocationData, AbstractTraceRegion>> matchingLocations = getCollectingList(associatedPath,
                    expectedAssociatedPath, result);
            for (ILocationData locationData : next.getAssociatedLocations()) {
                if (associatedPath == null) {
                    matchingLocations = getCollectingList(locationData.getSrcRelativePath(), expectedAssociatedPath,
                            result);
                }
                if (matchingLocations != null) {
                    matchingLocations.add(Tuples.create(locationData, next));
                }
            }
        }
        return result;
    }

    protected List<Pair<ILocationData, AbstractTraceRegion>> getCollectingList(SourceRelativeURI associatedPath,
            SourceRelativeURI expectedAssociatedPath,
            Map<SourceRelativeURI, List<Pair<ILocationData, AbstractTraceRegion>>> listsPerURI) {
        List<Pair<ILocationData, AbstractTraceRegion>> result = null;
        if (associatedPath != null
                && (expectedAssociatedPath == null || associatedPath.equals(expectedAssociatedPath))) {
            result = listsPerURI.get(associatedPath);
            if (result == null) {
                result = Lists.newArrayList();
                listsPerURI.put(associatedPath, result);
            }
        }
        return result;
    }

    public TreeIterator<AbstractTraceRegion> treeIterator() {
        TreeIterator<AbstractTraceRegion> treeIterator = new AbstractTreeIterator<AbstractTraceRegion>(this) {
            private static final long serialVersionUID = 1L;

            @Override
            protected Iterator<? extends AbstractTraceRegion> getChildren(Object object) {
                if (object == null)
                    return ImmutableSet.<AbstractTraceRegion>of().iterator();
                AbstractTraceRegion casted = (AbstractTraceRegion) object;
                return casted.getNestedRegions().iterator();
            }

            /**
             * @throws UnsupportedOperationException
             *             always
             */
            @Override
            public void remove() {
                throw new UnsupportedOperationException("remove is not supported");
            }
        };
        return treeIterator;
    }

    protected void inplaceSortByOffset(List<Pair<ILocationData, AbstractTraceRegion>> locations) {
        Collections.sort(locations, new Comparator<Pair<ILocationData, AbstractTraceRegion>>() {
            @Override
            public int compare(Pair<ILocationData, AbstractTraceRegion> o1,
                    Pair<ILocationData, AbstractTraceRegion> o2) {
                if (o1 == null || o2 == null)
                    throw new IllegalArgumentException("o1 == null || o2 == null");
                ILocationData loc1 = o1.getFirst();
                ILocationData loc2 = o2.getFirst();
                int result = Ints.compare(loc1.getOffset(), loc2.getOffset());
                if (result == 0) {
                    result = Ints.compare(loc2.getLength(), loc1.getLength());
                }
                return result;
            }
        });
    }

    /**
     * Produces trees from a sorted list of locations. If the locations overlap, they'll be splitted automatically to
     * fulfill the contract of invariant of trace regions.
     */
    protected List<AbstractTraceRegion> toInvertedTraceRegions(
            List<Pair<ILocationData, AbstractTraceRegion>> locations, SourceRelativeURI myPath) {
        List<AbstractTraceRegion> result = Lists.newArrayListWithCapacity(2);
        TraceRegion current = null;
        int currentEndOffset = 0;
        outer: for (int i = 0; i < locations.size(); i++) { // avoid concurrent modification exceptions
            Pair<ILocationData, AbstractTraceRegion> nextPair = locations.get(i);
            ILocationData nextLocation = nextPair.getFirst();
            if (nextLocation.getOffset() == nextLocation.getLength() && nextLocation.getOffset() == 0) {
                continue;
            }
            AbstractTraceRegion nextRegion = nextPair.getSecond();
            if (current != null) {
                // equal region - add mapped location
                if (current.getMyOffset() == nextLocation.getOffset()
                        && current.getMyLength() == nextLocation.getLength()) {
                    List<ILocationData> writableLocations = current.getWritableAssociatedLocations();
                    ILocationData newData = createLocationData(nextRegion, myPath);
                    if (!writableLocations.contains(newData))
                        writableLocations.add(newData);
                    continue outer;
                } else {
                    // walk upwards if necessary
                    while (current != null && currentEndOffset <= nextLocation.getOffset()) {
                        current = (TraceRegion) current.getParent();
                        if (current != null)
                            currentEndOffset = current.getMyOffset() + current.getMyLength();
                        else
                            currentEndOffset = 0;
                    }
                }
            }
            if (current != null) {
                int nextOffset = nextLocation.getOffset();
                if (nextOffset + nextLocation.getLength() <= currentEndOffset) {
                    current = new TraceRegion(nextOffset, nextLocation.getLength(), nextLocation.getLineNumber(),
                            nextLocation.getEndLineNumber(), true, createLocationData(nextRegion, myPath), current);
                    currentEndOffset = nextLocation.getOffset() + nextLocation.getLength();
                } else {
                    int nextLength = currentEndOffset - nextOffset;
                    int nextEndLine = current.getMyEndLineNumber();
                    int splittedLength = nextLocation.getLength() - nextLength;
                    int splittedBeginLine = nextEndLine;
                    ILocationData splitted = new LocationData(currentEndOffset, splittedLength, splittedBeginLine,
                            nextLocation.getEndLineNumber(), nextLocation.getSrcRelativePath());
                    for (int j = i + 1; j < locations.size() && splitted != null; j++) {
                        ILocationData shiftMe = locations.get(j).getFirst();
                        if (splitted.getOffset() == shiftMe.getOffset()) {
                            if (splitted.getLength() > shiftMe.getLength()) {
                                locations.add(j, Tuples.create(splitted, nextRegion));
                                splitted = null;
                            }
                        } else if (splitted.getOffset() < shiftMe.getOffset()) {
                            locations.add(j, Tuples.create(splitted, nextRegion));
                            splitted = null;
                        }
                    }
                    if (splitted != null) {
                        locations.add(Tuples.create(splitted, nextRegion));
                    }
                    current = new TraceRegion(nextOffset, nextLength, nextLocation.getLineNumber(),
                            splittedBeginLine, true, createLocationData(nextRegion, myPath), current);
                    currentEndOffset = nextOffset + nextLength;
                }
            } else {
                current = new TraceRegion(nextLocation.getOffset(), nextLocation.getLength(),
                        nextLocation.getLineNumber(), nextLocation.getEndLineNumber(), true,
                        createLocationData(nextRegion, myPath), null);
                currentEndOffset = nextLocation.getOffset() + nextLocation.getLength();
                result.add(current);
            }
        }
        return result;
    }

    public static AbstractTraceRegion mergedFrom(List<AbstractTraceRegion> regions) {
        return new TraceRegionMerger().mergeTraceRegions(regions);
    }

    public LocationData createLocationData(AbstractTraceRegion region, SourceRelativeURI myPath) {
        return new LocationData(region.getMyOffset(), region.getMyLength(), region.getMyLineNumber(),
                region.getMyEndLineNumber(), myPath);
    }

    /**
     * Returns an iterator that will only offer leaf trace regions. If the nested regions have gaps, these will be
     * filled with parent data. If this region is a leaf, a singleton iterator will be returned.
     * 
     * @return an unmodifiable iterator for all leafs. Never <code>null</code>.
     */
    public final Iterator<AbstractTraceRegion> leafIterator() {
        if (nestedRegions == null)
            return Collections.<AbstractTraceRegion>singleton(this).iterator();
        return new LeafIterator(this);
    }

    public abstract int getMyLength();

    public abstract int getMyOffset();

    public abstract int getMyLineNumber();

    public abstract int getMyEndLineNumber();

    public ITextRegionWithLineInformation getMyRegion() {
        return new TextRegionWithLineInformation(getMyOffset(), getMyLength(), getMyLineNumber(),
                getMyEndLineNumber());
    }

    public abstract List<ILocationData> getAssociatedLocations();

    /**
     * Returns the merged location of all associated locations if they belong to the same resource. Otherwise
     * <code>null</code> is returned.
     */
    public ILocationData getMergedAssociatedLocation() {
        List<ILocationData> allData = getAssociatedLocations();
        if (allData.isEmpty()) {
            return null;
        }
        if (allData.size() == 1) {
            return allData.get(0);
        }
        boolean allNull = true;
        SourceRelativeURI path = null;
        ITextRegionWithLineInformation region = ITextRegionWithLineInformation.EMPTY_REGION;
        for (ILocationData data : allData) {
            if (path != null) {
                if (!path.equals(data.getSrcRelativePath())) {
                    return null;
                }
            } else {
                if (data.getSrcRelativePath() == null) {
                    if (!allNull)
                        throw new IllegalStateException(
                                "Iff multiple associated locations are present, the path has to be set");
                } else {
                    allNull = false;
                    path = data.getSrcRelativePath();
                }
            }
            region = region.merge(new TextRegionWithLineInformation(data.getOffset(), data.getLength(),
                    data.getLineNumber(), data.getEndLineNumber()));
        }
        return new LocationData(region.getOffset(), region.getLength(), region.getLineNumber(),
                region.getEndLineNumber(), path);
    }

    public SourceRelativeURI getAssociatedSrcRelativePath() {
        ILocationData data = getMergedAssociatedLocation();
        if (data != null) {
            SourceRelativeURI result = data.getSrcRelativePath();
            if (result != null) {
                return result;
            }
        }
        if (parent == null)
            return null;
        boolean uriSeen = false;
        for (ILocationData associated : getAssociatedLocations())
            if (associated.getSrcRelativePath() != null) {
                if (!uriSeen)
                    uriSeen = true;
                else
                    return null;
            }
        return parent.getAssociatedSrcRelativePath();
    }

    public AbstractTraceRegion getParent() {
        return parent;
    }

    public AbstractTraceRegion getRoot() {
        if (parent != null)
            return parent.getRoot();
        return this;
    }

    public String getAnnotatedString(String input) {
        StringBuilder result = new StringBuilder(input.length() * 3);
        int nextOffset = doAnnotateTrace(input, result, 0);
        if (nextOffset < input.length()) {
            result.append(input.substring(nextOffset));
        }
        return result.toString();
    }

    private int doAnnotateTrace(String input, StringBuilder result, int nextOffset) {
        if (nextOffset < getMyOffset()) {
            result.append(input.substring(nextOffset, getMyOffset()));
            nextOffset = getMyOffset();
        }
        result.append('<');
        List<ILocationData> associatedLocations = getAssociatedLocations();
        for (int i = 0; i < associatedLocations.size(); i++) {
            if (i != 0)
                result.append("/");
            ILocationData associatedLocation = associatedLocations.get(i);
            result.append(associatedLocation.getOffset()).append(':').append(associatedLocation.getLength());
        }
        result.append("[");
        for (AbstractTraceRegion nested : getNestedRegions()) {
            nextOffset = nested.doAnnotateTrace(input, result, nextOffset);
        }
        if (nextOffset < getMyOffset() + getMyLength()) {
            result.append(input.substring(nextOffset, getMyOffset() + getMyLength()));
            nextOffset = getMyOffset() + getMyLength();
        }
        result.append(']');
        return nextOffset;
    }

    public abstract boolean isUseForDebugging();

    /**
     * Returns the hash code value for this region. The hash code of a trace region <code>r</code> is defined to be:
     * 
     * <pre>
     * r.getMyOffset() ^ r.getMyLength() ^ r.getAssociatedOffset() ^ r.getAssociatedLength()
     *       ^ (r.getParent() == null ? 0 : r.getParent().hashCode())
     * </pre>
     * 
     * This ensures that <tt>r1.equals(r2)</tt> implies that <tt>r1.hashCode()==r2.hashCode()</tt> for any two
     * {@link AbstractTraceRegion} <tt>r1</tt> and <tt>r2</tt>, as required by the general contract of
     * <tt>Object.hashCode</tt>.
     *
     * @return the hash code value for this trace region
     * @see Object#hashCode()
     * @see Object#equals(Object)
     * @see #equals(Object)
     */
    @Override
    public int hashCode() {
        AbstractTraceRegion parent = getParent();
        return getMyOffset() ^ getMyLength() ^ getAssociatedLocations().hashCode()
                ^ (parent == null ? 0 : parent.hashCode());
    }

    /**
     * Compares the specified object with this region for equality. Returns <code>true</code> if the given object is
     * also an {@link AbstractTraceRegion} and the two regions represent the same data. More formally, two regions
     * <code>r1</code> and <code>r2</code> are considered to be equal if
     * 
     * <pre>
     * (r1.getMyOffset() == r2.getMyOffset()) && (r1.getMyLength() == r2.getMyLength())
     *       && (r1.getAssociatedOffset() == r2.getAssociatedOffset())
     *       && (r1.getAssociatedLength() == r2.getAssociatedLength())
     *       && (r1.getParent() == null ? r2.getParent() == null : r1.getParent().equals(r2.getParent()))
     * </pre>
     * 
     * This ensures that the {@link #equals(Object)} method works properly across different implementations of the
     * {@link AbstractTraceRegion} interface.
     * 
     * @param obj
     *            object to be compared for equality with this trace region
     * @return <tt>true</tt> if the specified object is equal to this trace region
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (!(obj instanceof AbstractTraceRegion))
            return false;
        AbstractTraceRegion other = (AbstractTraceRegion) obj;
        if (getMyLength() != other.getMyLength())
            return false;
        if (getMyOffset() != other.getMyOffset())
            return false;
        if (!getAssociatedLocations().equals(other.getAssociatedLocations()))
            return false;
        AbstractTraceRegion otherParent = other.getParent();
        AbstractTraceRegion parent = getParent();
        if (otherParent == null) {
            if (parent != null)
                return false;
        } else {
            if (parent == null)
                return false;
            if (!parent.equals(otherParent))
                return false;
        }
        return true;
    }

    @Override
    public String toString() {
        List<AbstractTraceRegion> nested = getNestedRegions();
        String nestedText;
        if (nested.isEmpty())
            nestedText = "";
        else
            nestedText = " nestedRegions={\n  " + Joiner.on("\n").join(nested).replace("\n", "\n  ") + "\n}";
        List<ILocationData> associated = getAssociatedLocations();
        String associatedText;
        if (associated.isEmpty())
            associatedText = "";
        else
            associatedText = " associations={\n  " + Joiner.on("\n").join(associated).replace("\n", "\n  ") + "\n}";
        return getClass().getSimpleName() + " [myOffset=" + getMyOffset() + ", myLength=" + getMyLength() + "]"
                + associatedText + nestedText;
    }

}