org.eclipse.xtext.naming.QualifiedName.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.xtext.naming.QualifiedName.java

Source

/*******************************************************************************
 * Copyright (c) 2010 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.naming;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import org.eclipse.emf.common.util.CommonUtil;
import org.eclipse.emf.ecore.resource.impl.BinaryResourceImpl.EObjectInputStream;
import org.eclipse.emf.ecore.resource.impl.BinaryResourceImpl.EObjectOutputStream;
import org.eclipse.xtext.util.Strings;

import com.google.common.base.Function;

/**
 * A datatype for dealing with qualified names.
 * Instances are usually provided by a {@link IQualifiedNameProvider}.
 *
 * @author Jan Koehnlein - Initial contribution and API
 * @author Sebastian Zarnekow
 */
public class QualifiedName implements Comparable<QualifiedName> {

    private final int hash;

    private final String[] segments;

    private QualifiedName lowerCase;

    private static final boolean USE_INTERNING = Boolean.getBoolean("xtext.qn.interning");

    public static final QualifiedName EMPTY = new QualifiedName() {
        @Override
        public QualifiedName append(QualifiedName relativeQualifiedName) {
            return relativeQualifiedName;
        }

        @Override
        public QualifiedName append(String segment) {
            return QualifiedName.create(segment);
        }

        @Override
        boolean hasLowerCase() {
            return true;
        }

        @Override
        public QualifiedName toLowerCase() {
            return this;
        }

        @Override
        public QualifiedName toUpperCase() {
            return this;
        }

        @Override
        public String toString(String delimiter) {
            return "";
        }
    };

    /**
     * Low-level factory method. Consider using a {@link IQualifiedNameConverter} instead.
     *
     * @param segments the segments of the to-be-created qualified name.
     *    May be <code>null</code>, but may not contain <code>null</code> entries.
     * @return a {@link QualifiedName}. Never <code>null</code>.
     * @exception IllegalArgumentException
     *                if any of the segments is null
     */
    public static QualifiedName create(String... segments) {
        if (segments == null || segments.length == 0) {
            return EMPTY;
        }
        if (segments.length == 1) {
            return create(segments[0]);
        }
        String[] newArray = new String[segments.length];
        for (int i = 0; i < segments.length; i++) {
            String string = segments[i];
            if (string == null) {
                throw new IllegalArgumentException("Segment cannot be null");
            }
            newArray[i] = intern(string);
        }
        return new QualifiedName(newArray);
    }

    /**
     * Returns string internal instance from string pool, if a system property {@code xtext.qn.interning} is set to {@code true}, or the
     * same object otherwise.
     * <p>
     * Implementation notes:
     * <ol>
     * <li>Interning {@link String} objects may affect performance, see bug 484215.
     * <li>Interning {@link String} objects is not recommended for older JVM's, because of possible perm gen memory explosion, see
     * http://java-performance.info/string-intern-in-java-6-7-8/.
     * </ol>
     */
    private static String intern(String string) {
        return USE_INTERNING ? CommonUtil.intern(string) : string;
    }

    /**
     * Internal low level factory method.
     * @noreference This method is not intended to be referenced by clients.
     * @since 2.4
     */
    public static QualifiedName createFromStream(EObjectInputStream eObjectInputStream) throws IOException {
        int segmentCount = eObjectInputStream.readCompressedInt();
        if (segmentCount == 0) {
            return QualifiedName.EMPTY;
        }
        // lowercase QN serialize a 'null' value at index 0 and
        String firstSegment = eObjectInputStream.readSegmentedString();
        boolean lowerCase = false;
        if (firstSegment == null) {
            lowerCase = true;
            // first was null, read another string which is the actual first segment
            firstSegment = eObjectInputStream.readSegmentedString();
            if (firstSegment == null) {
                throw new IllegalStateException("Read unexpected first segment from object stream");
            }
        }

        String[] segments = readSegmentArray(eObjectInputStream, segmentCount, firstSegment);
        if (lowerCase) {
            return new QualifiedNameLowerCase(segments);
        } else {
            return new QualifiedName(segments);
        }
    }

    private static String[] readSegmentArray(EObjectInputStream from, int count, String first) throws IOException {
        String[] segments = new String[count];
        segments[0] = intern(first);
        for (int i = 1; i < count; i++) {
            String segment = from.readSegmentedString();
            if (segment == null) {
                throw new IllegalStateException("Read unexpected segment (#" + i + ") from object stream");
            }
            segments[i] = intern(segment);
        }
        return segments;
    }

    /**
     * Internal low level serialization of QualifiedNames.
     * @since 2.4
     */
    public void writeToStream(EObjectOutputStream eObjectOutputStream) throws IOException {
        int segmentCount = getSegmentCount();
        eObjectOutputStream.writeCompressedInt(segmentCount);
        for (int i = 0; i < segmentCount; ++i) {
            eObjectOutputStream.writeSegmentedString(getSegment(i));
        }
    }

    /**
     * Low-level factory method. Consider using a {@link IQualifiedNameConverter} instead.
     *
     * @param segments
     *            the segments of the to-be-created qualified name. May be <code>null</code>, but may not contain
     *            <code>null</code> entries.
     * @return a {@link QualifiedName}. Never <code>null</code>.
     * @exception IllegalArgumentException
     *                if any of the segments is null
     * @since 2.3
     */
    public static QualifiedName create(List<String> segments) {
        if (segments == null || segments.isEmpty())
            return QualifiedName.EMPTY;
        if (segments.size() == 1) {
            String singleSegment = segments.get(0);
            return QualifiedName.create(singleSegment);
        }
        String[] segmentArray = new String[segments.size()];
        for (int i = 0; i < segments.size(); i++) {
            String string = segments.get(i);
            if (string == null) {
                throw new IllegalArgumentException("Segment cannot be null");
            }
            segmentArray[i] = intern(string);
        }
        return new QualifiedName(segmentArray);
    }

    /**
     * Low-level factory method. Consider using a {@link IQualifiedNameConverter} instead.
     *
     * @param singleSegment
     *            the single segment of the newly created qualified name
     * @exception IllegalArgumentException
     *                if the singleSegment is null
     * @since 2.3
     */
    public static QualifiedName create(String singleSegment) {
        if (singleSegment == null) {
            throw new IllegalArgumentException("Segment cannot be null");
        }
        return new QualifiedName(intern(singleSegment));
    }

    /**
     * Wraps a name function to return a qualified name. Returns null if the name function returns null.
     */
    public static <F> Function<F, QualifiedName> wrapper(final Function<F, String> nameFunction) {
        return new Function<F, QualifiedName>() {
            @Override
            public QualifiedName apply(F from) {
                String name = nameFunction.apply(from);
                if (name == null)
                    return null;
                return QualifiedName.create(name);
            }
        };
    }

    protected QualifiedName(String... segments) {
        if (segments == null || segments.length == 0)
            this.segments = Strings.EMPTY_ARRAY;
        else
            this.segments = segments;
        hash = Arrays.hashCode(this.segments);
    }

    public boolean isEmpty() {
        return segments.length == 0;
    }

    public List<String> getSegments() {
        return Collections.unmodifiableList(Arrays.asList(segments));
    }

    public int getSegmentCount() {
        return segments.length;
    }

    public String getSegment(int index) {
        return segments[index];
    }

    public String getLastSegment() {
        return segments[segments.length - 1];
    }

    public String getFirstSegment() {
        return segments[0];
    }

    public QualifiedName append(String segment) {
        if (segment == null) {
            throw new IllegalArgumentException("Segment cannot be null");
        }
        String[] newSegments = new String[getSegmentCount() + 1];
        System.arraycopy(segments, 0, newSegments, 0, segments.length);
        newSegments[segments.length] = intern(segment);
        return new QualifiedName(newSegments);
    }

    public QualifiedName append(QualifiedName relativeQualifiedName) {
        String[] newSegments = new String[getSegmentCount() + relativeQualifiedName.getSegmentCount()];
        for (int i = 0; i < getSegmentCount(); ++i)
            newSegments[i] = getSegment(i);
        for (int i = 0; i < relativeQualifiedName.getSegmentCount(); ++i)
            newSegments[i + getSegmentCount()] = relativeQualifiedName.getSegment(i);
        return new QualifiedName(newSegments);
    }

    public QualifiedName skipFirst(int skipCount) {
        if (skipCount == getSegmentCount()) {
            return EMPTY;
        }
        if (skipCount == 0) {
            return this;
        }
        if (skipCount > getSegmentCount() || skipCount < 0) {
            throw new IllegalArgumentException("Cannot skip " + skipCount + " fragments from QualifiedName with "
                    + getSegmentCount() + " segments");
        }
        String[] newSegments = new String[segments.length - skipCount];
        System.arraycopy(segments, skipCount, newSegments, 0, newSegments.length);
        return new QualifiedName(newSegments);
    }

    public QualifiedName skipLast(int skipCount) {
        if (skipCount == getSegmentCount()) {
            return EMPTY;
        }
        if (skipCount == 0) {
            return this;
        }
        if (skipCount > getSegmentCount() || skipCount < 0) {
            throw new IllegalArgumentException("Cannot skip " + skipCount + " fragments from QualifiedName with "
                    + getSegmentCount() + " segments");
        }
        String[] newSegments = new String[segments.length - skipCount];
        System.arraycopy(segments, 0, newSegments, 0, newSegments.length);
        return new QualifiedName(newSegments);
    }

    public QualifiedName toLowerCase() {
        if (lowerCase != null)
            return lowerCase;
        String[] newSegments = new String[segments.length];
        boolean isLowerCase = true;
        for (int i = 0; i < getSegmentCount(); ++i) {
            String segment = segments[i];
            String lowerCaseSegment = segment.toLowerCase();
            isLowerCase = isLowerCase && segment == lowerCaseSegment;
            newSegments[i] = intern(lowerCaseSegment);
        }
        if (isLowerCase) {
            lowerCase = this;
        } else {
            lowerCase = new QualifiedNameLowerCase(newSegments);
        }
        return lowerCase;
    }

    private static class QualifiedNameLowerCase extends QualifiedName {
        public QualifiedNameLowerCase(String[] segments) {
            super(segments);
        }

        @Override
        public QualifiedName toLowerCase() {
            return this;
        }

        @Override
        boolean hasLowerCase() {
            return true;
        }

        /**
         * We serialize a segmentCount + 1 and a dummy null value as the first entry.
         * This is used to retrieve the information about lowercase QN in {@link QualifiedName#createFromStream(EObjectInputStream)}
         */
        @Override
        public void writeToStream(EObjectOutputStream eObjectOutputStream) throws IOException {
            int segmentCount = getSegmentCount();
            eObjectOutputStream.writeCompressedInt(segmentCount);
            // indicator for lowercase instance
            eObjectOutputStream.writeSegmentedString(null);
            for (int i = 0; i < segmentCount; ++i) {
                eObjectOutputStream.writeSegmentedString(getSegment(i));
            }
        }
    }

    public QualifiedName toUpperCase() {
        String[] newSegments = new String[getSegmentCount()];
        for (int i = 0; i < getSegmentCount(); ++i)
            newSegments[i] = intern(segments[i].toUpperCase());
        QualifiedName result = new QualifiedName(newSegments);
        result.lowerCase = this.lowerCase;
        return result;
    }

    @Override
    public int hashCode() {
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == this)
            return true;
        if (obj instanceof QualifiedName) {
            QualifiedName other = (QualifiedName) obj;
            if (hash != other.hash)
                return false;
            return Arrays.equals(segments, other.segments);
        }
        return false;
    }

    /**
     * Returns <code>true</code> if this instance can provide a ready to use
     * lowercase representation.
     *
     * @noreference This method is not intended to be referenced by clients.
     */
    boolean hasLowerCase() {
        return lowerCase != null;
    }

    public boolean equalsIgnoreCase(Object obj) {
        if (obj == this)
            return true;
        if (obj instanceof QualifiedName) {
            // if both instances can provide lowerCase representations
            // use their equals method
            QualifiedName other = (QualifiedName) obj;
            if (hasLowerCase() && other.hasLowerCase()) {
                return toLowerCase().equals(other.toLowerCase());
            }
            // check the length
            int segmentCount = getSegmentCount();
            if (segmentCount != other.getSegmentCount()) {
                return false;
            }
            // compare by segment
            for (int i = 0; i < segmentCount; i++) {
                if (!getSegment(i).equalsIgnoreCase(other.getSegment(i))) {
                    return false;
                }
            }
            return true;
        }
        return false;
    }

    @Override
    public int compareTo(QualifiedName qualifiedName) {
        return compareTo(qualifiedName, false);
    }

    public int compareToIgnoreCase(QualifiedName qualifiedName) {
        return compareTo(qualifiedName, true);
    }

    protected int compareTo(QualifiedName qualifiedName, boolean ignoreCase) {
        if (ignoreCase) {
            for (int i = 0, upTo = Math.min(getSegmentCount(), qualifiedName.getSegmentCount()); i < upTo; ++i) {
                int result = getSegment(i).compareToIgnoreCase(qualifiedName.getSegment(i));
                if (result != 0)
                    return result;
            }
        } else {
            for (int i = 0, upTo = Math.min(getSegmentCount(), qualifiedName.getSegmentCount()); i < upTo; ++i) {
                int result = getSegment(i).compareTo(qualifiedName.getSegment(i));
                if (result != 0)
                    return result;
            }
        }
        // with Java7 this should probably read
        // return Integer.compare(getSegmentCount(), qualifiedName.getSegmentCount())
        return getSegmentCount() - qualifiedName.getSegmentCount();
    }

    public boolean startsWith(QualifiedName prefix) {
        return startsWith(prefix, false);
    }

    public boolean startsWithIgnoreCase(QualifiedName prefix) {
        return startsWith(prefix, true);
    }

    protected boolean startsWith(QualifiedName prefix, boolean ignoreCase) {
        if (prefix.getSegmentCount() > getSegmentCount())
            return false;
        if (ignoreCase) {
            for (int i = 0; i < prefix.getSegmentCount(); ++i) {
                if (!this.getSegment(i).equalsIgnoreCase(prefix.getSegment(i)))
                    return false;
            }
            return true;
        } else {
            for (int i = 0; i < prefix.getSegmentCount(); ++i) {
                if (!getSegment(i).equals(prefix.getSegment(i)))
                    return false;
            }
            return true;
        }
    }

    /**
     * Returns a canonical String representation of this using '.' as namespace delimiter. For language specific
     * conversion taking the concrete syntax into account see {@link IQualifiedNameConverter#toString(QualifiedName)}.
     */
    @Override
    public String toString() {
        return toString(".");
    }

    /**
     * Returns a String representation of this using {@code delimiter} as namespace delimiter.
     * @param delimiter the delimiter to use. <code>null</code> will be represented as the String "<code>null</code>".
     * @return the concatenated segments joined with the given {@code delimiter}
     * @since 2.3
     */
    public String toString(String delimiter) {
        int segmentCount = getSegmentCount();
        switch (segmentCount) {
        case 0:
            return "";
        case 1:
            return getFirstSegment();
        default:
            StringBuilder builder = new StringBuilder();
            builder.append(getFirstSegment());
            for (int i = 1; i < segmentCount; i++) {
                builder.append(delimiter);
                builder.append(segments[i]);
            }
            return builder.toString();
        }
    }
}