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 }