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    }