001/* 002 * Copyright 2010-2013 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 017package org.jetbrains.jet.lang.resolve.name; 018 019import com.google.common.collect.Lists; 020import org.jetbrains.annotations.NotNull; 021import org.jetbrains.annotations.Nullable; 022 023import java.util.List; 024 025/** 026 * Like {@link FqName} but allows '<' and '>' characters in name. 027 */ 028public class FqNameUnsafe extends FqNameBase { 029 030 public static final Name ROOT_NAME = Name.special("<root>"); 031 032 @NotNull 033 private final String fqName; 034 035 // cache 036 private transient FqName safe; 037 private transient FqNameUnsafe parent; 038 private transient Name shortName; 039 040 FqNameUnsafe(@NotNull String fqName, @NotNull FqName safe) { 041 this.fqName = fqName; 042 this.safe = safe; 043 044 validateFqName(); 045 } 046 047 public FqNameUnsafe(@NotNull String fqName) { 048 this.fqName = fqName; 049 050 validateFqName(); 051 } 052 053 private FqNameUnsafe(@NotNull String fqName, FqNameUnsafe parent, Name shortName) { 054 this.fqName = fqName; 055 this.parent = parent; 056 this.shortName = shortName; 057 058 validateFqName(); 059 } 060 061 062 private void validateFqName() { 063 if (!isValid(fqName)) { 064 throw new IllegalArgumentException("incorrect fq name: " + fqName); 065 } 066 } 067 068 public static boolean isValid(@Nullable String qualifiedName) { 069 // TODO: There's a valid name with escape char `` 070 return qualifiedName != null && qualifiedName.indexOf('/') < 0 && qualifiedName.indexOf('*') < 0; 071 } 072 073 private void compute() { 074 int lastDot = fqName.lastIndexOf('.'); 075 if (lastDot >= 0) { 076 shortName = Name.guess(fqName.substring(lastDot + 1)); 077 parent = new FqNameUnsafe(fqName.substring(0, lastDot)); 078 } 079 else { 080 shortName = Name.guess(fqName); 081 parent = FqName.ROOT.toUnsafe(); 082 } 083 } 084 085 086 087 @NotNull 088 public String asString() { 089 return fqName; 090 } 091 092 public boolean isSafe() { 093 if (safe != null) { 094 return true; 095 } 096 return FqName.isValidAfterUnsafeCheck(asString()); 097 } 098 099 @NotNull 100 public FqName toSafe() { 101 if (safe != null) { 102 return safe; 103 } 104 safe = new FqName(this); 105 return safe; 106 } 107 108 public boolean isRoot() { 109 return fqName.equals(""); 110 } 111 112 @NotNull 113 public FqNameUnsafe parent() { 114 if (parent != null) { 115 return parent; 116 } 117 118 if (isRoot()) { 119 throw new IllegalStateException("root"); 120 } 121 122 compute(); 123 124 return parent; 125 } 126 127 @NotNull 128 public FqNameUnsafe child(@NotNull Name name) { 129 String childFqName; 130 if (isRoot()) { 131 childFqName = name.asString(); 132 } 133 else { 134 childFqName = fqName + "." + name.asString(); 135 } 136 return new FqNameUnsafe(childFqName, this, name); 137 } 138 139 @NotNull 140 public Name shortName() { 141 if (shortName != null) { 142 return shortName; 143 } 144 145 if (isRoot()) { 146 throw new IllegalStateException("root"); 147 } 148 149 compute(); 150 151 return shortName; 152 } 153 154 @NotNull 155 public Name shortNameOrSpecial() { 156 if (isRoot()) { 157 return ROOT_NAME; 158 } 159 else { 160 return shortName(); 161 } 162 } 163 164 interface WalkCallback { 165 void segment(@NotNull Name shortName, @NotNull FqNameUnsafe fqName); 166 } 167 168 @NotNull 169 public List<FqNameUnsafe> path() { 170 final List<FqNameUnsafe> path = Lists.newArrayList(); 171 path.add(FqName.ROOT.toUnsafe()); 172 walk(new WalkCallback() { 173 @Override 174 public void segment(@NotNull Name shortName, @NotNull FqNameUnsafe fqName) { 175 path.add(fqName); 176 } 177 }); 178 return path; 179 } 180 181 @NotNull 182 public List<Name> pathSegments() { 183 final List<Name> path = Lists.newArrayList(); 184 walk(new WalkCallback() { 185 @Override 186 public void segment(@NotNull Name shortName, @NotNull FqNameUnsafe fqName) { 187 path.add(shortName); 188 } 189 }); 190 return path; 191 } 192 193 194 void walk(@NotNull WalkCallback callback) { 195 if (isRoot()) { 196 return; 197 } 198 199 int pos = fqName.indexOf('.'); 200 201 if (pos < 0) { 202 if (this.parent == null) { 203 this.parent = FqName.ROOT.toUnsafe(); 204 } 205 if (this.shortName == null) { 206 this.shortName = Name.guess(fqName); 207 } 208 callback.segment(shortName, this); 209 return; 210 } 211 212 Name firstSegment = Name.guess(fqName.substring(0, pos)); 213 FqNameUnsafe last = new FqNameUnsafe(firstSegment.asString(), FqName.ROOT.toUnsafe(), firstSegment); 214 callback.segment(firstSegment, last); 215 216 while (true) { 217 int next = fqName.indexOf('.', pos + 1); 218 if (next < 0) { 219 if (this.parent == null) { 220 this.parent = last; 221 } 222 Name shortName = Name.guess(fqName.substring(pos + 1)); 223 if (this.shortName == null) { 224 this.shortName = shortName; 225 } 226 callback.segment(shortName, this); 227 return; 228 } 229 230 Name shortName = Name.guess(fqName.substring(pos + 1, next)); 231 last = new FqNameUnsafe(fqName.substring(0, next), last, shortName); 232 callback.segment(shortName, last); 233 234 pos = next; 235 } 236 } 237 238 public boolean firstSegmentIs(@NotNull Name segment) { 239 if (isRoot()) { 240 return false; 241 } 242 List<Name> pathSegments = pathSegments(); 243 return pathSegments.get(0).equals(segment); 244 } 245 246 public boolean lastSegmentIs(@NotNull Name segment) { 247 if (isRoot()) { 248 return false; 249 } 250 return shortName().equals(segment); 251 } 252 253 254 255 @NotNull 256 public static FqNameUnsafe topLevel(@NotNull Name shortName) { 257 return new FqNameUnsafe(shortName.asString(), FqName.ROOT.toUnsafe(), shortName); 258 } 259 260 261 @Override 262 public String toString() { 263 return isRoot() ? ROOT_NAME.asString() : fqName; 264 } 265 266 @Override 267 public boolean equals(Object o) { 268 // generated by Idea 269 if (this == o) return true; 270 if (o == null || getClass() != o.getClass()) return false; 271 272 FqNameUnsafe that = (FqNameUnsafe) o; 273 274 if (fqName != null ? !fqName.equals(that.fqName) : that.fqName != null) return false; 275 276 return true; 277 } 278 279 @Override 280 public int hashCode() { 281 // generated by Idea 282 return fqName != null ? fqName.hashCode() : 0; 283 } 284}