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 }