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