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 }