001/*
002 * Copyright 2010-2013 JetBrains s.r.o.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.jetbrains.jet.util;
018
019import org.jetbrains.annotations.NotNull;
020import org.jetbrains.annotations.Nullable;
021import org.jetbrains.jet.lang.resolve.ImportPath;
022import org.jetbrains.jet.lang.resolve.name.FqName;
023import org.jetbrains.jet.lang.resolve.name.Name;
024
025/**
026 * Common methods for working with qualified names.
027 */
028public final class QualifiedNamesUtil {
029
030    private QualifiedNamesUtil() {
031    }
032
033    public static boolean isSubpackageOf(@NotNull FqName subpackageName, @NotNull FqName packageName) {
034        if (subpackageName.equals(packageName)) {
035            return true;
036        }
037
038        if (packageName.isRoot()) {
039            return true;
040        }
041
042        String subpackageNameStr = subpackageName.asString();
043        String packageNameStr = packageName.asString();
044
045        return (subpackageNameStr.startsWith(packageNameStr) && subpackageNameStr.charAt(packageNameStr.length()) == '.');
046    }
047
048    public static boolean isOneSegmentFQN(@NotNull String fqn) {
049        if (fqn.isEmpty()) {
050            return false;
051        }
052
053        return fqn.indexOf('.') < 0;
054    }
055
056    public static boolean isOneSegmentFQN(@NotNull FqName fqn) {
057        return isOneSegmentFQN(fqn.asString());
058    }
059
060    @NotNull
061    public static String getFirstSegment(@NotNull String fqn) {
062        int dotIndex = fqn.indexOf('.');
063        return (dotIndex != -1) ? fqn.substring(0, dotIndex) : fqn;
064    }
065
066    @NotNull
067    public static FqName withoutLastSegment(@NotNull FqName fqName) {
068        return fqName.parent();
069    }
070
071    @NotNull
072    public static FqName withoutFirstSegment(@NotNull FqName fqName) {
073        if (fqName.isRoot() || fqName.parent().isRoot()) {
074            return FqName.ROOT;
075        }
076
077        String fqNameStr = fqName.asString();
078        return new FqName(fqNameStr.substring(fqNameStr.indexOf('.'), fqNameStr.length()));
079    }
080
081    @NotNull
082    public static FqName combine(@NotNull FqName first, @NotNull Name second) {
083        return first.child(second);
084    }
085
086    /**
087     * Get tail part of the full fqn by subtracting head part.
088     *
089     * @param headFQN
090     * @param fullFQN
091     * @return tail fqn. If first part is not a begging of the full fqn, fullFQN will be returned.
092     */
093    @NotNull
094    public static String tail(@NotNull FqName headFQN, @NotNull FqName fullFQN) {
095        if (!isSubpackageOf(fullFQN, headFQN) || headFQN.isRoot()) {
096            return fullFQN.asString();
097        }
098
099        return fullFQN.equals(headFQN) ?
100               "" :
101               fullFQN.asString().substring(headFQN.asString().length() + 1); // (headFQN + '.').length
102    }
103
104    /**
105     * Add one segment of nesting to given qualified name according to the full qualified name.
106     *
107     * @param fqn
108     * @param fullFQN
109     * @return qualified name with one more segment or null if fqn is not head part of fullFQN or there's no additional segment.
110     */
111    @Nullable
112    public static FqName plusOneSegment(@NotNull FqName fqn, @NotNull FqName fullFQN) {
113        if (!isSubpackageOf(fullFQN, fqn)) {
114            return null;
115        }
116
117        String nextSegment = getFirstSegment(tail(fqn, fullFQN));
118
119        if (isOneSegmentFQN(nextSegment)) {
120            return combine(fqn, Name.guess(nextSegment));
121        }
122
123        return null;
124    }
125
126    public static boolean isImported(@NotNull ImportPath alreadyImported, @NotNull FqName fqName) {
127        if (alreadyImported.hasAlias()) {
128            return false;
129        }
130
131        if (alreadyImported.isAllUnder() && !fqName.isRoot()) {
132            return alreadyImported.fqnPart().equals(fqName.parent());
133        }
134
135        return alreadyImported.fqnPart().equals(fqName);
136    }
137
138    public static boolean isImported(@NotNull ImportPath alreadyImported, @NotNull ImportPath newImport) {
139        if (newImport.isAllUnder() || newImport.hasAlias()) {
140            return alreadyImported.equals(newImport);
141        }
142
143        return isImported(alreadyImported, newImport.fqnPart());
144    }
145
146    public static boolean isImported(@NotNull Iterable<ImportPath> imports, @NotNull ImportPath newImport) {
147        for (ImportPath alreadyImported : imports) {
148            if (isImported(alreadyImported, newImport)) {
149                return true;
150            }
151        }
152
153        return false;
154    }
155
156    public static boolean isValidJavaFqName(@Nullable String qualifiedName) {
157        if (qualifiedName == null) return false;
158
159        // Check that it is javaName(\.javaName)* or an empty string
160
161        class State {}
162        State BEGINNING = new State();
163        State MIDDLE = new State();
164        State AFTER_DOT = new State();
165
166        State state = BEGINNING;
167
168        int length = qualifiedName.length();
169        for (int i = 0; i < length; i++) {
170            char c = qualifiedName.charAt(i);
171            if (state == BEGINNING || state == AFTER_DOT) {
172                if (!Character.isJavaIdentifierPart(c)) return false;
173                state = MIDDLE;
174            }
175            else if (state == MIDDLE) {
176                if (c == '.') {
177                    state = AFTER_DOT;
178                }
179                else if (!Character.isJavaIdentifierPart(c)) {
180                    return false;
181                }
182            }
183        }
184
185        return state != AFTER_DOT;
186    }
187}