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 }