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 isSubpackageOf(subpackageNameStr, packageNameStr);
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        public static int numberOfSegments(@NotNull FqName fqName) {
082            if (fqName.isRoot()) {
083                return 0;
084            }
085    
086            return 1 + numberOfSegments(fqName.parent());
087        }
088    
089        @NotNull
090        public static FqName combine(@NotNull FqName first, @NotNull Name second) {
091            return first.child(second);
092        }
093    
094        /**
095         * Get tail part of the full fqn by subtracting head part.
096         *
097         * @param headFQN
098         * @param fullFQN
099         * @return tail fqn. If first part is not a begging of the full fqn, fullFQN will be returned.
100         */
101        @NotNull
102        public static String tail(@NotNull FqName headFQN, @NotNull FqName fullFQN) {
103            if (!isSubpackageOf(fullFQN, headFQN) || headFQN.isRoot()) {
104                return fullFQN.asString();
105            }
106    
107            return fullFQN.equals(headFQN) ?
108                   "" :
109                   fullFQN.asString().substring(headFQN.asString().length() + 1); // (headFQN + '.').length
110        }
111    
112        /**
113         * Add one segment of nesting to given qualified name according to the full qualified name.
114         *
115         * @param fqn
116         * @param fullFQN
117         * @return qualified name with one more segment or null if fqn is not head part of fullFQN or there's no additional segment.
118         */
119        @Nullable
120        public static FqName plusOneSegment(@NotNull FqName fqn, @NotNull FqName fullFQN) {
121            if (!isSubpackageOf(fullFQN, fqn)) {
122                return null;
123            }
124    
125            String nextSegment = getFirstSegment(tail(fqn, fullFQN));
126    
127            if (isOneSegmentFQN(nextSegment)) {
128                return combine(fqn, Name.guess(nextSegment));
129            }
130    
131            return null;
132        }
133    
134        public static boolean isImported(@NotNull ImportPath alreadyImported, @NotNull FqName fqName) {
135            if (alreadyImported.hasAlias()) {
136                return false;
137            }
138    
139            if (alreadyImported.isAllUnder() && !fqName.isRoot()) {
140                return alreadyImported.fqnPart().equals(fqName.parent());
141            }
142    
143            return alreadyImported.fqnPart().equals(fqName);
144        }
145    
146        public static boolean isImported(@NotNull ImportPath alreadyImported, @NotNull ImportPath newImport) {
147            if (newImport.isAllUnder() || newImport.hasAlias()) {
148                return alreadyImported.equals(newImport);
149            }
150    
151            return isImported(alreadyImported, newImport.fqnPart());
152        }
153    
154        public static boolean isImported(@NotNull Iterable<ImportPath> imports, @NotNull ImportPath newImport) {
155            for (ImportPath alreadyImported : imports) {
156                if (isImported(alreadyImported, newImport)) {
157                    return true;
158                }
159            }
160    
161            return false;
162        }
163    
164        public static boolean isValidJavaFqName(@Nullable String qualifiedName) {
165            if (qualifiedName == null) return false;
166    
167            // Check that it is javaName(\.javaName)* or an empty string
168    
169            class State {}
170            State BEGINNING = new State();
171            State MIDDLE = new State();
172            State AFTER_DOT = new State();
173    
174            State state = BEGINNING;
175    
176            int length = qualifiedName.length();
177            for (int i = 0; i < length; i++) {
178                char c = qualifiedName.charAt(i);
179                if (state == BEGINNING || state == AFTER_DOT) {
180                    if (!Character.isJavaIdentifierPart(c)) return false;
181                    state = MIDDLE;
182                }
183    
184                //noinspection ConstantConditions
185                assert state == MIDDLE;
186    
187                if (c == '.') {
188                    state = AFTER_DOT;
189                }
190                else if (!Character.isJavaIdentifierPart(c)) {
191                    return false;
192                }
193            }
194    
195            return state != AFTER_DOT;
196        }
197    
198        public static boolean isSubpackageOf(String subpackageNameStr, String packageNameStr) {
199            return subpackageNameStr.equals(packageNameStr) ||
200                    (subpackageNameStr.startsWith(packageNameStr) && subpackageNameStr.charAt(packageNameStr.length()) == '.');
201        }
202    }