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    
017    package org.jetbrains.jet.util;
018    
019    import org.jetbrains.annotations.NotNull;
020    import org.jetbrains.annotations.Nullable;
021    import org.jetbrains.jet.lang.resolve.ImportPath;
022    import org.jetbrains.jet.lang.resolve.name.FqName;
023    import org.jetbrains.jet.lang.resolve.name.Name;
024    
025    /**
026     * Common methods for working with qualified names.
027     */
028    public 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    }