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}