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    }