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