FqName class

for type safety, to easier distinguish between:

* short names
* qualified names
* jvm names (slash-separated)
* special names like <root>
* null values that mean "undefined" and "root ns" in different contexts
This commit is contained in:
Stepan Koltsov
2012-03-13 21:51:38 +04:00
parent 15078b1b70
commit 82d77560a2
41 changed files with 434 additions and 222 deletions
@@ -26,6 +26,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.psi.stubs.PsiJetFunctionStub;
import org.jetbrains.jet.lang.psi.stubs.elements.JetStubElementTypes;
import org.jetbrains.jet.lang.resolve.FqName;
import org.jetbrains.jet.lexer.JetTokens;
/**
@@ -96,7 +97,7 @@ public class JetNamedFunction extends JetFunction implements StubBasedPsiElement
* @return
*/
@Nullable
public String getQualifiedName() {
public FqName getQualifiedName() {
final PsiJetFunctionStub stub = getStub();
if (stub != null) {
// TODO (stubs): return stub.getQualifiedName();
@@ -105,12 +106,8 @@ public class JetNamedFunction extends JetFunction implements StubBasedPsiElement
PsiElement parent = getParent();
if (parent instanceof JetFile) {
JetFile jetFile = (JetFile) parent;
final String fileFQN = JetPsiUtil.getFQName(jetFile);
if (!fileFQN.isEmpty()) {
return fileFQN + "." + getName();
}
return getName();
final FqName fileFQN = JetPsiUtil.getFQName(jetFile);
return fileFQN.child(getName());
}
return null;
@@ -24,6 +24,7 @@ import com.intellij.psi.PsiFileFactory;
import com.intellij.util.LocalTimeCounter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.resolve.FqName;
import org.jetbrains.jet.lexer.JetKeywordToken;
import org.jetbrains.jet.lexer.JetTokens;
import org.jetbrains.jet.plugin.JetFileType;
@@ -144,11 +145,15 @@ public class JetPsiFactory {
// return file.getRootNamespace();
// }
public static JetImportDirective createImportDirective(Project project, String classPath) {
public static JetImportDirective createImportDirective(Project project, @NotNull String classPath) {
JetFile namespace = createFile(project, "import " + classPath);
return namespace.getImportDirectives().iterator().next();
}
public static JetImportDirective createImportDirective(Project project, @NotNull FqName fqName) {
return createImportDirective(project, fqName.getFqName());
}
public static PsiElement createPrimaryConstructor(Project project) {
JetClass aClass = createClass(project, "class A()");
return aClass.findElementAt(7).getParent();
@@ -22,6 +22,7 @@ import com.intellij.psi.impl.source.codeStyle.CodeEditUtil;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.resolve.FqName;
import org.jetbrains.jet.lexer.JetTokens;
import org.jetbrains.jet.util.QualifiedNamesUtil;
@@ -129,21 +130,26 @@ public class JetPsiUtil {
}
}
private static String getFQName(JetNamespaceHeader header) {
private static FqName getFQName(JetNamespaceHeader header) {
StringBuilder builder = new StringBuilder();
for (JetSimpleNameExpression nameExpression : header.getParentNamespaceNames()) {
builder.append(nameExpression.getReferencedName());
builder.append(".");
}
builder.append(header.getName());
return builder.toString();
return new FqName(builder.toString());
}
public static String getFQName(JetFile file) {
public static FqName getFQName(JetFile file) {
return getFQName(file.getNamespaceHeader());
}
public static String getFQName(JetClassOrObject jetClass) {
@Nullable
public static FqName getFQName(@NotNull JetClassOrObject jetClass) {
if (jetClass.getName() == null) {
return null;
}
PsiElement parent = jetClass.getParent();
if (parent instanceof JetFile) {
return makeFQName(getFQName((JetFile) parent), jetClass);
@@ -157,14 +163,15 @@ public class JetPsiUtil {
if (parent instanceof JetClassOrObject) {
return makeFQName(getFQName(((JetClassOrObject) parent)), jetClass);
}
return jetClass.getName();
return new FqName(jetClass.getName());
}
public static String getFQName(JetNamedFunction jetNamedFunction) {
@Nullable
public static FqName getFQName(@NotNull JetNamedFunction jetNamedFunction) {
String functionName = jetNamedFunction.getName();
if (functionName == null) {
return functionName;
return null;
}
@SuppressWarnings("unchecked")
@@ -172,7 +179,7 @@ public class JetPsiUtil {
jetNamedFunction,
JetFile.class, JetClassOrObject.class, JetNamedFunction.class);
String firstPart = "";
FqName firstPart = FqName.ROOT;
if (qualifiedElement instanceof JetFile) {
firstPart = getFQName((JetFile) qualifiedElement);
}
@@ -183,7 +190,7 @@ public class JetPsiUtil {
firstPart = getFQName((JetNamedFunction) qualifiedElement);
}
return QualifiedNamesUtil.combine(firstPart, functionName);
return firstPart.child(functionName);
}
@Nullable @JetElement.IfNotParsed
@@ -197,8 +204,9 @@ public class JetPsiUtil {
return text.replaceAll(" ", "") + (importDirective.isAllUnder() ? ".*" : "");
}
private static String makeFQName(String prefix, JetClassOrObject jetClass) {
return ((prefix == null || prefix.length() == 0) ? "" : prefix + ".") + jetClass.getName();
@NotNull
private static FqName makeFQName(@NotNull FqName prefix, @NotNull JetClassOrObject jetClass) {
return prefix.child(jetClass.getName());
}
public static boolean isIrrefutable(JetWhenEntry entry) {
@@ -30,6 +30,7 @@ import org.jetbrains.jet.lang.psi.JetClass;
import org.jetbrains.jet.lang.psi.JetPsiUtil;
import org.jetbrains.jet.lang.psi.stubs.PsiJetClassStub;
import org.jetbrains.jet.lang.psi.stubs.impl.PsiJetClassStubImpl;
import org.jetbrains.jet.lang.resolve.FqName;
import java.io.IOException;
@@ -60,7 +61,8 @@ public class JetClassElementType extends JetStubElementType<PsiJetClassStub, Jet
@Override
public PsiJetClassStub createStub(@NotNull JetClass psi, StubElement parentStub) {
return new PsiJetClassStubImpl(JetStubElementTypes.CLASS, parentStub, JetPsiUtil.getFQName(psi), psi.getName());
FqName fqName = JetPsiUtil.getFQName(psi);
return new PsiJetClassStubImpl(JetStubElementTypes.CLASS, parentStub, fqName != null ? fqName.getFqName() : null, psi.getName());
}
@Override
@@ -19,6 +19,7 @@ package org.jetbrains.jet.lang.psi.stubs.impl;
import com.intellij.psi.stubs.StubBase;
import com.intellij.psi.stubs.StubElement;
import com.intellij.util.io.StringRef;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.psi.JetClass;
import org.jetbrains.jet.lang.psi.stubs.PsiJetClassStub;
import org.jetbrains.jet.lang.psi.stubs.elements.JetClassElementType;
@@ -34,7 +35,7 @@ public class PsiJetClassStubImpl extends StubBase<JetClass> implements PsiJetCla
public PsiJetClassStubImpl(
JetClassElementType type,
final StubElement parent,
final String qualifiedName,
@Nullable final String qualifiedName,
final String name) {
this(type, parent, StringRef.fromString(qualifiedName), StringRef.fromString(name));
@@ -171,8 +171,8 @@ public interface BindingContext {
WritableSlice<JetReferenceExpression, PsiElement> LABEL_TARGET = Slices.<JetReferenceExpression, PsiElement>sliceBuilder().build();
WritableSlice<JetParameter, PropertyDescriptor> VALUE_PARAMETER_AS_PROPERTY = Slices.<JetParameter, PropertyDescriptor>sliceBuilder().build();
WritableSlice<String, ClassDescriptor> FQNAME_TO_CLASS_DESCRIPTOR = new BasicWritableSlice<String, ClassDescriptor>(DO_NOTHING, true);
WritableSlice<String, NamespaceDescriptor> FQNAME_TO_NAMESPACE_DESCRIPTOR = new BasicWritableSlice<String, NamespaceDescriptor>(DO_NOTHING);
WritableSlice<FqName, ClassDescriptor> FQNAME_TO_CLASS_DESCRIPTOR = new BasicWritableSlice<FqName, ClassDescriptor>(DO_NOTHING, true);
WritableSlice<FqName, NamespaceDescriptor> FQNAME_TO_NAMESPACE_DESCRIPTOR = new BasicWritableSlice<FqName, NamespaceDescriptor>(DO_NOTHING);
WritableSlice<ClassDescriptor, Boolean> INCOMPLETE_HIERARCHY = Slices.createCollectiveSetSlice();
@@ -178,14 +178,25 @@ public class DescriptorUtils {
return false;
}
public static String getFQName(DeclarationDescriptor descriptor) {
DeclarationDescriptor container = descriptor.getContainingDeclaration();
if (container != null && !(container instanceof ModuleDescriptor)) {
String baseName = getFQName(container);
if (!baseName.isEmpty()) return baseName + "." + descriptor.getName();
public static FqName getFQName(@NotNull DeclarationDescriptor descriptor) {
if (descriptor instanceof ModuleDescriptor) {
return FqName.ROOT;
}
return descriptor.getName();
if (descriptor.getContainingDeclaration() == null) {
if (descriptor instanceof NamespaceDescriptor) {
// TODO: namespace must always have parent
if (descriptor.getName().equals("jet")) {
return FqName.topLevel("jet");
}
if (descriptor.getName().equals("<java_root>")) {
return FqName.ROOT;
}
}
throw new IllegalStateException("descriptor is not module descriptor and has null containingDeclaration: " + descriptor.getContainingDeclaration());
}
return getFQName(descriptor.getContainingDeclaration()).child(descriptor.getName());
}
public static boolean isTopLevelFunction(@NotNull SimpleFunctionDescriptor functionDescriptor) {
@@ -0,0 +1,152 @@
/*
* Copyright 2000-2012 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.jet.lang.resolve;
import org.jetbrains.annotations.NotNull;
/**
* @author Stepan Koltsov
*/
public class FqName {
public static final FqName ROOT = new FqName("");
@NotNull
private final String fqName;
// cache
private transient FqName parent;
private transient String shortName;
public FqName(@NotNull String fqName) {
this.fqName = fqName;
validateFqName();
}
private FqName(@NotNull String fqName, FqName parent, String shortName) {
this.fqName = fqName;
this.parent = parent;
this.shortName = shortName;
validateFqName();
}
private void validateFqName() {
if (fqName.indexOf('/') >= 0 || fqName.indexOf('<') >= 0) {
throw new IllegalArgumentException("incorrect fq name: " + fqName);
}
}
private void compute() {
int lastDot = fqName.lastIndexOf('.');
if (lastDot >= 0) {
shortName = fqName.substring(lastDot + 1);
parent = new FqName(fqName.substring(0, lastDot));
} else {
shortName = fqName;
parent = ROOT;
}
}
@NotNull
public String getFqName() {
return fqName;
}
public boolean isRoot() {
return fqName.equals("");
}
@NotNull
public FqName parent() {
if (parent != null) {
return parent;
}
if (isRoot()) {
throw new IllegalStateException("root");
}
compute();
return parent;
}
@NotNull
public FqName child(@NotNull String name) {
String childFqName;
if (isRoot()) {
childFqName = name;
} else {
childFqName = fqName + "." + name;
}
return new FqName(childFqName, this, name);
}
@NotNull
public String shortName() {
if (shortName != null) {
return shortName;
}
if (isRoot()) {
throw new IllegalStateException("root");
}
compute();
return shortName;
}
@NotNull
public static FqName topLevel(@NotNull String shortName) {
if (shortName.indexOf('.') >= 0) {
throw new IllegalArgumentException();
}
return new FqName(shortName, ROOT, shortName);
}
@Override
public String toString() {
return isRoot() ? "<root>" : fqName;
}
@Override
public boolean equals(Object o) {
// generated by Idea
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FqName that = (FqName) o;
if (fqName != null ? !fqName.equals(that.fqName) : that.fqName != null) return false;
return true;
}
@Override
public int hashCode() {
// generated by Idea
return fqName != null ? fqName.hashCode() : 0;
}
}
@@ -69,7 +69,7 @@ public class OverloadResolver {
}
Key(NamespaceDescriptor namespaceDescriptor, String name) {
this(DescriptorUtils.getFQName(namespaceDescriptor), name);
this(DescriptorUtils.getFQName(namespaceDescriptor).getFqName(), name);
}
public String getNamespace() {
@@ -177,13 +177,13 @@ public class ExpressionTypingUtils {
* @return
*/
public static List<CallableDescriptor> canFindSuitableCall(
@NotNull String callableFQN,
@NotNull FqName callableFQN,
@NotNull Project project,
@NotNull JetExpression receiverExpression,
@NotNull JetType receiverType,
@NotNull JetScope scope) {
JetImportDirective importDirective = JetPsiFactory.createImportDirective(project, callableFQN);
JetImportDirective importDirective = JetPsiFactory.createImportDirective(project, callableFQN.getFqName());
Collection<? extends DeclarationDescriptor> declarationDescriptors = ImportsResolver.analyseImportReference(importDirective, scope, new BindingTraceContext());
@@ -16,9 +16,9 @@
package org.jetbrains.jet.util;
import com.intellij.openapi.util.text.StringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.resolve.FqName;
/**
* Common methods for working with qualified names strings.
@@ -27,14 +27,14 @@ import org.jetbrains.annotations.Nullable;
*/
public final class QualifiedNamesUtil {
public static boolean isSubpackageOf(@NotNull final String subpackageName, @NotNull String packageName) {
public static boolean isSubpackageOf(@NotNull final FqName subpackageName, @NotNull FqName packageName) {
return subpackageName.equals(packageName) ||
(subpackageName.startsWith(packageName) && subpackageName.charAt(packageName.length()) == '.');
(subpackageName.getFqName().startsWith(packageName.getFqName()) && subpackageName.getFqName().charAt(packageName.getFqName().length()) == '.');
}
public static boolean isShortNameForFQN(@NotNull final String name, @NotNull final String fqn) {
return fqn.equals(name) ||
(fqn.endsWith(name) && fqn.charAt(fqn.length() - name.length() - 1) == '.');
public static boolean isShortNameForFQN(@NotNull final String name, @NotNull final FqName fqn) {
return fqn.getFqName().equals(name) ||
(fqn.getFqName().endsWith(name) && fqn.getFqName().charAt(fqn.getFqName().length() - name.length() - 1) == '.');
}
public static boolean isOneSegmentFQN(@NotNull final String fqn) {
@@ -46,14 +46,13 @@ public final class QualifiedNamesUtil {
}
@NotNull
public static String fqnToShortName(@NotNull String fqn) {
public static String fqnToShortName(@NotNull FqName fqn) {
return getLastSegment(fqn);
}
@NotNull
public static String getLastSegment(@NotNull String fqn) {
int lastDotIndex = fqn.lastIndexOf('.');
return (lastDotIndex != -1) ? fqn.substring(lastDotIndex + 1, fqn.length()) : fqn;
public static String getLastSegment(@NotNull FqName fqn) {
return fqn.shortName();
}
@NotNull
@@ -72,9 +71,14 @@ public final class QualifiedNamesUtil {
return "";
}
public static String combine(@Nullable String first, @NotNull String second) {
if (StringUtil.isEmpty(first)) return second;
return first + "." + second;
@NotNull
public static FqName withoutLastSegment(@NotNull FqName fqName) {
return fqName.parent();
}
@NotNull
public static FqName combine(@NotNull FqName first, @NotNull String second) {
return first.child(second);
}
/**
@@ -85,14 +89,14 @@ public final class QualifiedNamesUtil {
* @return tail fqn. If first part is not a begging of the full fqn, fullFQN will be returned.
*/
@NotNull
public static String tail(@NotNull String headFQN, @NotNull String fullFQN) {
public static String tail(@NotNull FqName headFQN, @NotNull FqName fullFQN) {
if (!isSubpackageOf(fullFQN, headFQN)) {
return fullFQN;
return fullFQN.getFqName();
}
return fullFQN.equals(headFQN) ?
"" :
fullFQN.substring(headFQN.length() + 1); // (headFQN + '.').length
fullFQN.getFqName().substring(headFQN.getFqName().length() + 1); // (headFQN + '.').length
}
/**
@@ -103,7 +107,7 @@ public final class QualifiedNamesUtil {
* @return qualified name with one more segment or null if fqn is not head part of fullFQN or there's no additional segment.
*/
@Nullable
public static String plusOneSegment(String fqn, String fullFQN) {
public static FqName plusOneSegment(@NotNull FqName fqn, @NotNull FqName fullFQN) {
if (!isSubpackageOf(fullFQN, fqn)) {
return null;
}
@@ -121,11 +125,11 @@ public final class QualifiedNamesUtil {
* Check that given fqn could be imported with import.
*
* @param importPath path from the import. Could contain .* part
* @param fqn
* @return
* @param fqn or another import directive
*/
public static boolean isImported(@NotNull String importPath, @NotNull String fqn) {
if (importPath.endsWith("*")) {
// TODO: import path is not valid FQN
return withoutLastSegment(importPath).equals(withoutLastSegment(fqn));
}