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    }