001 /*
002 * Copyright 2010-2015 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.kotlin.name;
018
019 import org.jetbrains.annotations.NotNull;
020 import org.jetbrains.annotations.Nullable;
021 import org.jetbrains.kotlin.utils.UtilsPackage;
022
023 import java.util.ArrayList;
024 import java.util.List;
025
026 /**
027 * Like {@link FqName} but allows '<' and '>' characters in name.
028 */
029 public final class FqNameUnsafe extends FqNameBase {
030
031 public static final Name ROOT_NAME = Name.special("<root>");
032
033 @NotNull
034 private final String fqName;
035
036 // cache
037 private transient FqName safe;
038 private transient FqNameUnsafe parent;
039 private transient Name shortName;
040
041 FqNameUnsafe(@NotNull String fqName, @NotNull FqName safe) {
042 this.fqName = fqName;
043 this.safe = safe;
044
045 validateFqName();
046 }
047
048 public FqNameUnsafe(@NotNull String fqName) {
049 this.fqName = fqName;
050
051 validateFqName();
052 }
053
054 private FqNameUnsafe(@NotNull String fqName, FqNameUnsafe parent, Name shortName) {
055 this.fqName = fqName;
056 this.parent = parent;
057 this.shortName = shortName;
058
059 validateFqName();
060 }
061
062
063 private void validateFqName() {
064 if (!isValid(fqName)) {
065 throw new IllegalArgumentException("incorrect fq name: " + fqName);
066 }
067 }
068
069 public static boolean isValid(@Nullable String qualifiedName) {
070 // TODO: There's a valid name with escape char ``
071 return qualifiedName != null && qualifiedName.indexOf('/') < 0 && qualifiedName.indexOf('*') < 0;
072 }
073
074 private void compute() {
075 int lastDot = fqName.lastIndexOf('.');
076 if (lastDot >= 0) {
077 shortName = Name.guess(fqName.substring(lastDot + 1));
078 parent = new FqNameUnsafe(fqName.substring(0, lastDot));
079 }
080 else {
081 shortName = Name.guess(fqName);
082 parent = FqName.ROOT.toUnsafe();
083 }
084 }
085
086
087
088 @Override
089 @NotNull
090 public String asString() {
091 return fqName;
092 }
093
094 public boolean isSafe() {
095 if (safe != null) {
096 return true;
097 }
098 return FqName.isValidAfterUnsafeCheck(asString());
099 }
100
101 @NotNull
102 public FqName toSafe() {
103 if (safe != null) {
104 return safe;
105 }
106 safe = new FqName(this);
107 return safe;
108 }
109
110 public boolean isRoot() {
111 return fqName.isEmpty();
112 }
113
114 @NotNull
115 public FqNameUnsafe parent() {
116 if (parent != null) {
117 return parent;
118 }
119
120 if (isRoot()) {
121 throw new IllegalStateException("root");
122 }
123
124 compute();
125
126 return parent;
127 }
128
129 @NotNull
130 public FqNameUnsafe child(@NotNull Name name) {
131 String childFqName;
132 if (isRoot()) {
133 childFqName = name.asString();
134 }
135 else {
136 childFqName = fqName + "." + name.asString();
137 }
138 return new FqNameUnsafe(childFqName, this, name);
139 }
140
141 @NotNull
142 public Name shortName() {
143 if (shortName != null) {
144 return shortName;
145 }
146
147 if (isRoot()) {
148 throw new IllegalStateException("root");
149 }
150
151 compute();
152
153 return shortName;
154 }
155
156 @Override
157 @NotNull
158 public Name shortNameOrSpecial() {
159 if (isRoot()) {
160 return ROOT_NAME;
161 }
162 else {
163 return shortName();
164 }
165 }
166
167 interface WalkCallback {
168 void segment(@NotNull Name shortName, @NotNull FqNameUnsafe fqName);
169 }
170
171 @NotNull
172 public List<FqNameUnsafe> path() {
173 final List<FqNameUnsafe> path = new ArrayList<FqNameUnsafe>();
174 path.add(FqName.ROOT.toUnsafe());
175 walk(new WalkCallback() {
176 @Override
177 public void segment(@NotNull Name shortName, @NotNull FqNameUnsafe fqName) {
178 path.add(fqName);
179 }
180 });
181 return path;
182 }
183
184 @Override
185 @NotNull
186 public List<Name> pathSegments() {
187 final List<Name> path = new ArrayList<Name>();
188 walk(new WalkCallback() {
189 @Override
190 public void segment(@NotNull Name shortName, @NotNull FqNameUnsafe fqName) {
191 path.add(shortName);
192 }
193 });
194 return path;
195 }
196
197
198 void walk(@NotNull WalkCallback callback) {
199 if (isRoot()) {
200 return;
201 }
202
203 int pos = fqName.indexOf('.');
204
205 if (pos < 0) {
206 if (this.parent == null) {
207 this.parent = FqName.ROOT.toUnsafe();
208 }
209 if (this.shortName == null) {
210 this.shortName = Name.guess(fqName);
211 }
212 callback.segment(shortName, this);
213 return;
214 }
215
216 Name firstSegment = Name.guess(fqName.substring(0, pos));
217 FqNameUnsafe last = new FqNameUnsafe(firstSegment.asString(), FqName.ROOT.toUnsafe(), firstSegment);
218 callback.segment(firstSegment, last);
219
220 while (true) {
221 int next = fqName.indexOf('.', pos + 1);
222 if (next < 0) {
223 if (this.parent == null) {
224 this.parent = last;
225 }
226 Name shortName = Name.guess(fqName.substring(pos + 1));
227 if (this.shortName == null) {
228 this.shortName = shortName;
229 }
230 callback.segment(shortName, this);
231 return;
232 }
233
234 Name shortName = Name.guess(fqName.substring(pos + 1, next));
235 last = new FqNameUnsafe(fqName.substring(0, next), last, shortName);
236 callback.segment(shortName, last);
237
238 pos = next;
239 }
240 }
241
242 public boolean firstSegmentIs(@NotNull Name segment) {
243 if (isRoot()) {
244 return false;
245 }
246 List<Name> pathSegments = pathSegments();
247 return pathSegments.get(0).equals(segment);
248 }
249
250 public boolean lastSegmentIs(@NotNull Name segment) {
251 if (isRoot()) {
252 return false;
253 }
254 return shortName().equals(segment);
255 }
256
257 @NotNull
258 public static FqNameUnsafe fromSegments(@NotNull List<?> names) {
259 return new FqNameUnsafe(UtilsPackage.join(names, "."));
260 }
261
262
263 @NotNull
264 public static FqNameUnsafe topLevel(@NotNull Name shortName) {
265 return new FqNameUnsafe(shortName.asString(), FqName.ROOT.toUnsafe(), shortName);
266 }
267
268
269 @Override
270 @NotNull
271 public String toString() {
272 return isRoot() ? ROOT_NAME.asString() : fqName;
273 }
274
275 @Override
276 public boolean equals(Object o) {
277 if (this == o) return true;
278 if (!(o instanceof FqNameUnsafe)) return false;
279
280 FqNameUnsafe that = (FqNameUnsafe) o;
281
282 if (!fqName.equals(that.fqName)) return false;
283
284 return true;
285 }
286
287 @Override
288 public int hashCode() {
289 return fqName.hashCode();
290 }
291 }