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