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    }