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.lang.resolve.name;
018
019 import com.google.common.collect.Lists;
020 import com.intellij.openapi.util.text.StringUtil;
021 import org.jetbrains.annotations.NotNull;
022 import org.jetbrains.annotations.Nullable;
023
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 @NotNull
089 public String asString() {
090 return fqName;
091 }
092
093 public boolean isSafe() {
094 if (safe != null) {
095 return true;
096 }
097 return FqName.isValidAfterUnsafeCheck(asString());
098 }
099
100 @NotNull
101 public FqName toSafe() {
102 if (safe != null) {
103 return safe;
104 }
105 safe = new FqName(this);
106 return safe;
107 }
108
109 public boolean isRoot() {
110 return fqName.equals("");
111 }
112
113 @NotNull
114 public FqNameUnsafe parent() {
115 if (parent != null) {
116 return parent;
117 }
118
119 if (isRoot()) {
120 throw new IllegalStateException("root");
121 }
122
123 compute();
124
125 return parent;
126 }
127
128 @NotNull
129 public FqNameUnsafe child(@NotNull Name name) {
130 String childFqName;
131 if (isRoot()) {
132 childFqName = name.asString();
133 }
134 else {
135 childFqName = fqName + "." + name.asString();
136 }
137 return new FqNameUnsafe(childFqName, this, name);
138 }
139
140 @NotNull
141 public Name shortName() {
142 if (shortName != null) {
143 return shortName;
144 }
145
146 if (isRoot()) {
147 throw new IllegalStateException("root");
148 }
149
150 compute();
151
152 return shortName;
153 }
154
155 @NotNull
156 public Name shortNameOrSpecial() {
157 if (isRoot()) {
158 return ROOT_NAME;
159 }
160 else {
161 return shortName();
162 }
163 }
164
165 interface WalkCallback {
166 void segment(@NotNull Name shortName, @NotNull FqNameUnsafe fqName);
167 }
168
169 @NotNull
170 public List<FqNameUnsafe> path() {
171 final List<FqNameUnsafe> path = Lists.newArrayList();
172 path.add(FqName.ROOT.toUnsafe());
173 walk(new WalkCallback() {
174 @Override
175 public void segment(@NotNull Name shortName, @NotNull FqNameUnsafe fqName) {
176 path.add(fqName);
177 }
178 });
179 return path;
180 }
181
182 @NotNull
183 public List<Name> pathSegments() {
184 final List<Name> path = Lists.newArrayList();
185 walk(new WalkCallback() {
186 @Override
187 public void segment(@NotNull Name shortName, @NotNull FqNameUnsafe fqName) {
188 path.add(shortName);
189 }
190 });
191 return path;
192 }
193
194
195 void walk(@NotNull WalkCallback callback) {
196 if (isRoot()) {
197 return;
198 }
199
200 int pos = fqName.indexOf('.');
201
202 if (pos < 0) {
203 if (this.parent == null) {
204 this.parent = FqName.ROOT.toUnsafe();
205 }
206 if (this.shortName == null) {
207 this.shortName = Name.guess(fqName);
208 }
209 callback.segment(shortName, this);
210 return;
211 }
212
213 Name firstSegment = Name.guess(fqName.substring(0, pos));
214 FqNameUnsafe last = new FqNameUnsafe(firstSegment.asString(), FqName.ROOT.toUnsafe(), firstSegment);
215 callback.segment(firstSegment, last);
216
217 while (true) {
218 int next = fqName.indexOf('.', pos + 1);
219 if (next < 0) {
220 if (this.parent == null) {
221 this.parent = last;
222 }
223 Name shortName = Name.guess(fqName.substring(pos + 1));
224 if (this.shortName == null) {
225 this.shortName = shortName;
226 }
227 callback.segment(shortName, this);
228 return;
229 }
230
231 Name shortName = Name.guess(fqName.substring(pos + 1, next));
232 last = new FqNameUnsafe(fqName.substring(0, next), last, shortName);
233 callback.segment(shortName, last);
234
235 pos = next;
236 }
237 }
238
239 public boolean firstSegmentIs(@NotNull Name segment) {
240 if (isRoot()) {
241 return false;
242 }
243 List<Name> pathSegments = pathSegments();
244 return pathSegments.get(0).equals(segment);
245 }
246
247 public boolean lastSegmentIs(@NotNull Name segment) {
248 if (isRoot()) {
249 return false;
250 }
251 return shortName().equals(segment);
252 }
253
254 @NotNull
255 public static FqNameUnsafe fromSegments(@NotNull List<Name> names) {
256 String fqName = StringUtil.join(names, ".");
257 return new FqNameUnsafe(fqName);
258 }
259
260
261
262 @NotNull
263 public static FqNameUnsafe topLevel(@NotNull Name shortName) {
264 return new FqNameUnsafe(shortName.asString(), FqName.ROOT.toUnsafe(), shortName);
265 }
266
267
268 @Override
269 public String toString() {
270 return isRoot() ? ROOT_NAME.asString() : fqName;
271 }
272
273 @Override
274 public boolean equals(Object o) {
275 if (this == o) return true;
276 if (!(o instanceof FqNameUnsafe)) return false;
277
278 FqNameUnsafe that = (FqNameUnsafe) o;
279
280 if (!fqName.equals(that.fqName)) return false;
281
282 return true;
283 }
284
285 @Override
286 public int hashCode() {
287 return fqName.hashCode();
288 }
289 }