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 kotlin.collections.ArraysKt;
020    import kotlin.jvm.functions.Function1;
021    import org.jetbrains.annotations.NotNull;
022    import org.jetbrains.annotations.Nullable;
023    
024    import java.util.Collections;
025    import java.util.List;
026    
027    /**
028     * Like {@link FqName} but allows '<' and '>' characters in name.
029     */
030    public final class FqNameUnsafe {
031    
032        public static final Name ROOT_NAME = Name.special("<root>");
033    
034        @NotNull
035        private final String fqName;
036    
037        // cache
038        private transient FqName safe;
039        private transient FqNameUnsafe parent;
040        private transient Name shortName;
041    
042        FqNameUnsafe(@NotNull String fqName, @NotNull FqName safe) {
043            this.fqName = fqName;
044            this.safe = safe;
045        }
046    
047        public FqNameUnsafe(@NotNull String fqName) {
048            this.fqName = fqName;
049        }
050    
051        private FqNameUnsafe(@NotNull String fqName, FqNameUnsafe parent, Name shortName) {
052            this.fqName = fqName;
053            this.parent = parent;
054            this.shortName = shortName;
055        }
056    
057        public static boolean isValid(@Nullable String qualifiedName) {
058            // TODO: There's a valid name with escape char ``
059            return qualifiedName != null && qualifiedName.indexOf('/') < 0 && qualifiedName.indexOf('*') < 0;
060        }
061    
062        private void compute() {
063            int lastDot = fqName.lastIndexOf('.');
064            if (lastDot >= 0) {
065                shortName = Name.guessByFirstCharacter(fqName.substring(lastDot + 1));
066                parent = new FqNameUnsafe(fqName.substring(0, lastDot));
067            }
068            else {
069                shortName = Name.guessByFirstCharacter(fqName);
070                parent = FqName.ROOT.toUnsafe();
071            }
072        }
073    
074        @NotNull
075        public String asString() {
076            return fqName;
077        }
078    
079        public boolean isSafe() {
080            return safe != null || asString().indexOf('<') < 0;
081        }
082    
083        @NotNull
084        public FqName toSafe() {
085            if (safe != null) {
086                return safe;
087            }
088            safe = new FqName(this);
089            return safe;
090        }
091    
092        public boolean isRoot() {
093            return fqName.isEmpty();
094        }
095    
096        @NotNull
097        public FqNameUnsafe parent() {
098            if (parent != null) {
099                return parent;
100            }
101    
102            if (isRoot()) {
103                throw new IllegalStateException("root");
104            }
105    
106            compute();
107    
108            return parent;
109        }
110    
111        @NotNull
112        public FqNameUnsafe child(@NotNull Name name) {
113            String childFqName;
114            if (isRoot()) {
115                childFqName = name.asString();
116            }
117            else {
118                childFqName = fqName + "." + name.asString();
119            }
120            return new FqNameUnsafe(childFqName, this, name);
121        }
122    
123        @NotNull
124        public Name shortName() {
125            if (shortName != null) {
126                return shortName;
127            }
128    
129            if (isRoot()) {
130                throw new IllegalStateException("root");
131            }
132    
133            compute();
134    
135            return shortName;
136        }
137    
138        @NotNull
139        public Name shortNameOrSpecial() {
140            if (isRoot()) {
141                return ROOT_NAME;
142            }
143            else {
144                return shortName();
145            }
146        }
147    
148        @NotNull
149        public List<Name> pathSegments() {
150            return isRoot() ? Collections.<Name>emptyList() :
151                   ArraysKt.map(fqName.split("\\."), new Function1<String, Name>() {
152                       @Override
153                       public Name invoke(String name) {
154                           return Name.guessByFirstCharacter(name);
155                       }
156                   });
157        }
158    
159        public boolean startsWith(@NotNull Name segment) {
160            return !isRoot() && pathSegments().get(0).equals(segment);
161        }
162    
163        @NotNull
164        public static FqNameUnsafe topLevel(@NotNull Name shortName) {
165            return new FqNameUnsafe(shortName.asString(), FqName.ROOT.toUnsafe(), shortName);
166        }
167    
168        @Override
169        @NotNull
170        public String toString() {
171            return isRoot() ? ROOT_NAME.asString() : fqName;
172        }
173    
174        @Override
175        public boolean equals(Object o) {
176            if (this == o) return true;
177            if (!(o instanceof FqNameUnsafe)) return false;
178    
179            FqNameUnsafe that = (FqNameUnsafe) o;
180    
181            if (!fqName.equals(that.fqName)) return false;
182    
183            return true;
184        }
185    
186        @Override
187        public int hashCode() {
188            return fqName.hashCode();
189        }
190    }