Move fqname related stuff into core:util.runtime module

This commit is contained in:
Victor Petukhov
2021-06-01 12:05:06 +03:00
parent c8af1b735f
commit 205087cae3
8 changed files with 0 additions and 0 deletions
@@ -0,0 +1,74 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.name
// NB: with className == null we are at top level
data class CallableId(
val packageName: FqName,
val className: FqName?,
val callableName: Name,
// Currently, it's only used for debug info
private val pathToLocal: FqName? = null
) {
private companion object {
val LOCAL_NAME = Name.special("<local>")
val PACKAGE_FQ_NAME_FOR_LOCAL = FqName.topLevel(LOCAL_NAME)
}
/**
* Return `true` if corresponding declaration is itself local or it is a member of local class
* Otherwise, returns `false`
*/
val isLocal: Boolean
get() = packageName == PACKAGE_FQ_NAME_FOR_LOCAL
|| classId?.isLocal == true
var classId: ClassId? = null
get() {
if (field == null && className != null) {
field = ClassId(packageName, className, false)
}
return field
}
constructor(classId: ClassId, callableName: Name) : this(classId.packageFqName, classId.relativeClassName, callableName) {
this.classId = classId
}
constructor(packageName: FqName, callableName: Name) : this(packageName, null, callableName)
constructor(
callableName: Name,
// Currently, it's only used for debug info
pathToLocal: FqName? = null
) : this(
PACKAGE_FQ_NAME_FOR_LOCAL,
className = null,
callableName,
pathToLocal,
)
fun asFqNameForDebugInfo(): FqName {
if (pathToLocal != null) return pathToLocal.child(callableName)
return asSingleFqName()
}
fun asSingleFqName(): FqName {
return classId?.asSingleFqName()?.child(callableName) ?: packageName.child(callableName)
}
override fun toString(): String {
return buildString {
append(packageName.asString().replace('.', '/'))
append("/")
if (className != null) {
append(className)
append(".")
}
append(callableName)
}
}
}
@@ -0,0 +1,154 @@
/*
* Copyright 2010-2015 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.kotlin.name;
import kotlin.text.StringsKt;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* A class name which is used to uniquely identify a Kotlin class.
*
* If local = true, the class represented by this id is either itself local or is an inner class of some local class. This also means that
* the first non-class container of the class is not a package.
* In the case of a local class, relativeClassName consists of a single name including all callables' and class' names all the way up to
* the package, separated by dollar signs. If a class is an inner of local, relativeClassName would consist of two names,
* the second one being the class' short name.
*/
public final class ClassId {
@NotNull
public static ClassId topLevel(@NotNull FqName topLevelFqName) {
return new ClassId(topLevelFqName.parent(), topLevelFqName.shortName());
}
private final FqName packageFqName;
private final FqName relativeClassName;
private final boolean local;
public ClassId(@NotNull FqName packageFqName, @NotNull FqName relativeClassName, boolean local) {
this.packageFqName = packageFqName;
assert !relativeClassName.isRoot() :
"Class name must not be root: " + packageFqName + (local ? " (local)" : "");
this.relativeClassName = relativeClassName;
this.local = local;
}
public ClassId(@NotNull FqName packageFqName, @NotNull Name topLevelName) {
this(packageFqName, FqName.topLevel(topLevelName), false);
}
@NotNull
public FqName getPackageFqName() {
return packageFqName;
}
@NotNull
public FqName getRelativeClassName() {
return relativeClassName;
}
@NotNull
public Name getShortClassName() {
return relativeClassName.shortName();
}
public boolean isLocal() {
return local;
}
@NotNull
public ClassId createNestedClassId(@NotNull Name name) {
return new ClassId(getPackageFqName(), relativeClassName.child(name), local);
}
@Nullable
public ClassId getOuterClassId() {
FqName parent = relativeClassName.parent();
return parent.isRoot() ? null : new ClassId(getPackageFqName(), parent, local);
}
public boolean isNestedClass() {
return !relativeClassName.parent().isRoot();
}
@NotNull
public FqName asSingleFqName() {
if (packageFqName.isRoot()) return relativeClassName;
return new FqName(packageFqName.asString() + "." + relativeClassName.asString());
}
public boolean startsWith(@NotNull Name segment) {
return packageFqName.startsWith(segment);
}
/**
* @param string a string where packages are delimited by '/' and classes by '.', e.g. "kotlin/Map.Entry"
*/
@NotNull
public static ClassId fromString(@NotNull String string) {
return fromString(string, false);
}
@NotNull
public static ClassId fromString(@NotNull String string, boolean isLocal) {
int lastSlashIndex = string.lastIndexOf("/");
String packageName;
String className;
if (lastSlashIndex == -1) {
packageName = "";
className = string;
} else {
packageName = string.substring(0, lastSlashIndex).replace('/', '.');
className = string.substring(lastSlashIndex + 1);
}
return new ClassId(new FqName(packageName), new FqName(className), isLocal);
}
/**
* @return a string where packages are delimited by '/' and classes by '.', e.g. "kotlin/Map.Entry"
*/
@NotNull
public String asString() {
if (packageFqName.isRoot()) return relativeClassName.asString();
return packageFqName.asString().replace('.', '/') + "/" + relativeClassName.asString();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ClassId id = (ClassId) o;
return packageFqName.equals(id.packageFqName) &&
relativeClassName.equals(id.relativeClassName) &&
local == id.local;
}
@Override
public int hashCode() {
int result = packageFqName.hashCode();
result = 31 * result + relativeClassName.hashCode();
result = 31 * result + Boolean.valueOf(local).hashCode();
return result;
}
@Override
public String toString() {
return packageFqName.isRoot() ? "/" + asString() : asString();
}
}
@@ -0,0 +1,131 @@
/*
* Copyright 2010-2015 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.kotlin.name;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.utils.StringsKt;
import java.util.List;
public final class FqName {
@NotNull
public static FqName fromSegments(@NotNull List<String> names) {
return new FqName(StringsKt.join(names, "."));
}
public static final FqName ROOT = new FqName("");
@NotNull
private final FqNameUnsafe fqName;
// cache
private transient FqName parent;
public FqName(@NotNull String fqName) {
this.fqName = new FqNameUnsafe(fqName, this);
}
public FqName(@NotNull FqNameUnsafe fqName) {
this.fqName = fqName;
}
private FqName(@NotNull FqNameUnsafe fqName, FqName parent) {
this.fqName = fqName;
this.parent = parent;
}
@NotNull
public String asString() {
return fqName.asString();
}
@NotNull
public FqNameUnsafe toUnsafe() {
return fqName;
}
public boolean isRoot() {
return fqName.isRoot();
}
@NotNull
public FqName parent() {
if (parent != null) {
return parent;
}
if (isRoot()) {
throw new IllegalStateException("root");
}
parent = new FqName(fqName.parent());
return parent;
}
@NotNull
public FqName child(@NotNull Name name) {
return new FqName(fqName.child(name), this);
}
@NotNull
public Name shortName() {
return fqName.shortName();
}
@NotNull
public Name shortNameOrSpecial() {
return fqName.shortNameOrSpecial();
}
@NotNull
public List<Name> pathSegments() {
return fqName.pathSegments();
}
public boolean startsWith(@NotNull Name segment) {
return fqName.startsWith(segment);
}
@NotNull
public static FqName topLevel(@NotNull Name shortName) {
return new FqName(FqNameUnsafe.topLevel(shortName));
}
@Override
public String toString() {
return fqName.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof FqName)) return false;
FqName otherFqName = (FqName) o;
if (!fqName.equals(otherFqName.fqName)) return false;
return true;
}
@Override
public int hashCode() {
return fqName.hashCode();
}
}
@@ -0,0 +1,196 @@
/*
* Copyright 2010-2015 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.kotlin.name;
import kotlin.collections.ArraysKt;
import kotlin.jvm.functions.Function1;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
/**
* Like {@link FqName} but allows '<' and '>' characters in name.
*/
public final class FqNameUnsafe {
private static final Name ROOT_NAME = Name.special("<root>");
private static final Pattern SPLIT_BY_DOTS = Pattern.compile("\\.");
private static final Function1<String, Name> STRING_TO_NAME = new Function1<String, Name>() {
@Override
public Name invoke(String name) {
return Name.guessByFirstCharacter(name);
}
};
@NotNull
private final String fqName;
// cache
private transient FqName safe;
private transient FqNameUnsafe parent;
private transient Name shortName;
FqNameUnsafe(@NotNull String fqName, @NotNull FqName safe) {
this.fqName = fqName;
this.safe = safe;
}
public FqNameUnsafe(@NotNull String fqName) {
this.fqName = fqName;
}
private FqNameUnsafe(@NotNull String fqName, FqNameUnsafe parent, Name shortName) {
this.fqName = fqName;
this.parent = parent;
this.shortName = shortName;
}
public static boolean isValid(@Nullable String qualifiedName) {
// TODO: There's a valid name with escape char ``
return qualifiedName != null && qualifiedName.indexOf('/') < 0 && qualifiedName.indexOf('*') < 0;
}
private void compute() {
int lastDot = fqName.lastIndexOf('.');
if (lastDot >= 0) {
shortName = Name.guessByFirstCharacter(fqName.substring(lastDot + 1));
parent = new FqNameUnsafe(fqName.substring(0, lastDot));
}
else {
shortName = Name.guessByFirstCharacter(fqName);
parent = FqName.ROOT.toUnsafe();
}
}
@NotNull
public String asString() {
return fqName;
}
public boolean isSafe() {
return safe != null || asString().indexOf('<') < 0;
}
@NotNull
public FqName toSafe() {
if (safe != null) {
return safe;
}
safe = new FqName(this);
return safe;
}
public boolean isRoot() {
return fqName.isEmpty();
}
@NotNull
public FqNameUnsafe parent() {
if (parent != null) {
return parent;
}
if (isRoot()) {
throw new IllegalStateException("root");
}
compute();
return parent;
}
@NotNull
public FqNameUnsafe child(@NotNull Name name) {
String childFqName;
if (isRoot()) {
childFqName = name.asString();
}
else {
childFqName = fqName + "." + name.asString();
}
return new FqNameUnsafe(childFqName, this, name);
}
@NotNull
public Name shortName() {
if (shortName != null) {
return shortName;
}
if (isRoot()) {
throw new IllegalStateException("root");
}
compute();
return shortName;
}
@NotNull
public Name shortNameOrSpecial() {
if (isRoot()) {
return ROOT_NAME;
}
else {
return shortName();
}
}
@NotNull
public List<Name> pathSegments() {
return isRoot() ? Collections.<Name>emptyList() : ArraysKt.map(SPLIT_BY_DOTS.split(fqName), STRING_TO_NAME);
}
public boolean startsWith(@NotNull Name segment) {
if (isRoot())
return false;
int firstDot = fqName.indexOf('.');
return fqName.regionMatches(0, segment.asString(), 0, firstDot == -1 ? fqName.length() : firstDot);
}
@NotNull
public static FqNameUnsafe topLevel(@NotNull Name shortName) {
return new FqNameUnsafe(shortName.asString(), FqName.ROOT.toUnsafe(), shortName);
}
@Override
@NotNull
public String toString() {
return isRoot() ? ROOT_NAME.asString() : fqName;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof FqNameUnsafe)) return false;
FqNameUnsafe that = (FqNameUnsafe) o;
if (!fqName.equals(that.fqName)) return false;
return true;
}
@Override
public int hashCode() {
return fqName.hashCode();
}
}
@@ -0,0 +1,82 @@
/*
* Copyright 2010-2015 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.kotlin.name
fun FqName.isSubpackageOf(packageName: FqName): Boolean {
return when {
this == packageName -> true
packageName.isRoot -> true
else -> isSubpackageOf(this.asString(), packageName.asString())
}
}
fun FqName.isChildOf(packageName: FqName): Boolean = parentOrNull() == packageName
private fun isSubpackageOf(subpackageNameStr: String, packageNameStr: String): Boolean {
return subpackageNameStr.startsWith(packageNameStr) && subpackageNameStr[packageNameStr.length] == '.'
}
fun FqName.isOneSegmentFQN(): Boolean = !isRoot && parent().isRoot
/**
* Get the tail part of the FQ name by stripping a prefix. If FQ name does not begin with the given prefix, it will be returned as is.
*
* Examples:
* "org.jetbrains.kotlin".tail("org") = "jetbrains.kotlin"
* "org.jetbrains.kotlin".tail("") = "org.jetbrains.kotlin"
* "org.jetbrains.kotlin".tail("org.jetbrains.kotlin") = ""
* "org.jetbrains.kotlin".tail("org.jetbrains.gogland") = "org.jetbrains.kotlin"
*/
fun FqName.tail(prefix: FqName): FqName {
return when {
!isSubpackageOf(prefix) || prefix.isRoot -> this
this == prefix -> FqName.ROOT
else -> FqName(asString().substring(prefix.asString().length + 1))
}
}
fun FqName.parentOrNull(): FqName? = if (this.isRoot) null else parent()
private enum class State {
BEGINNING,
MIDDLE,
AFTER_DOT
}
// Check that it is javaName(\.javaName)* or an empty string
fun isValidJavaFqName(qualifiedName: String?): Boolean {
if (qualifiedName == null) return false
var state = State.BEGINNING
for (c in qualifiedName) {
when (state) {
State.BEGINNING, State.AFTER_DOT -> {
if (!Character.isJavaIdentifierPart(c)) return false
state = State.MIDDLE
}
State.MIDDLE -> {
if (c == '.') {
state = State.AFTER_DOT
}
else if (!Character.isJavaIdentifierPart(c)) return false
}
}
}
return state != State.AFTER_DOT
}
@@ -0,0 +1,125 @@
/*
* Copyright 2010-2015 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.kotlin.name;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public final class Name implements Comparable<Name> {
@NotNull
private final String name;
private final boolean special;
private Name(@NotNull String name, boolean special) {
this.name = name;
this.special = special;
}
@NotNull
public String asString() {
return name;
}
@NotNull
public String getIdentifier() {
if (special) {
throw new IllegalStateException("not identifier: " + this);
}
return asString();
}
public boolean isSpecial() {
return special;
}
@NotNull
public String asStringStripSpecialMarkers() {
if (isSpecial()) return asString().substring(1, asString().length() - 1);
return asString();
}
@Override
public int compareTo(Name that) {
return this.name.compareTo(that.name);
}
@NotNull
public static Name identifier(@NotNull String name) {
return new Name(name, false);
}
public static boolean isValidIdentifier(@NotNull String name) {
if (name.isEmpty() || name.startsWith("<")) return false;
for (int i = 0; i < name.length(); i++) {
char ch = name.charAt(i);
if (ch == '.' || ch == '/' || ch == '\\') {
return false;
}
}
return true;
}
@NotNull
public static Name special(@NotNull String name) {
if (!name.startsWith("<")) {
throw new IllegalArgumentException("special name must start with '<': " + name);
}
return new Name(name, true);
}
@NotNull
public static Name guessByFirstCharacter(@NotNull String name) {
if (name.startsWith("<")) {
return special(name);
}
else {
return identifier(name);
}
}
@Nullable
public String getIdentifierOrNullIfSpecial() {
if (special) return null;
return asString();
}
@Override
public String toString() {
return name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Name)) return false;
Name name1 = (Name) o;
if (special != name1.special) return false;
if (!name.equals(name1.name)) return false;
return true;
}
@Override
public int hashCode() {
int result = name.hashCode();
result = 31 * result + (special ? 1 : 0);
return result;
}
}
@@ -0,0 +1,56 @@
/*
* Copyright 2010-2017 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.kotlin.name
import java.util.*
object NameUtils {
private val SANITIZE_AS_JAVA_INVALID_CHARACTERS = "[^\\p{L}\\p{Digit}]".toRegex()
@JvmStatic
fun sanitizeAsJavaIdentifier(name: String): String {
return SANITIZE_AS_JAVA_INVALID_CHARACTERS.replace(name, "_")
}
/**
* Capitalizes the short name of the file (without extension) and sanitizes it so that it's a valid Java identifier.
* E.g. "fileName" -> "FileName", "1" -> "_1", "" -> "_"
*/
@JvmStatic
fun getPackagePartClassNamePrefix(shortFileName: String): String =
if (shortFileName.isEmpty())
"_"
else
capitalizeAsJavaClassName(sanitizeAsJavaIdentifier(shortFileName))
@JvmStatic
private fun capitalizeAsJavaClassName(str: String): String =
// NB `uppercase` uses Locale.ROOT and is locale-independent.
// See Javadoc on java.lang.String.toUpperCase() for more details.
if (Character.isJavaIdentifierStart(str[0]))
str[0].uppercase() + str.substring(1)
else
"_$str"
// "pkg/someScript.kts" -> "SomeScript"
@JvmStatic
fun getScriptNameForFile(filePath: String): Name =
Name.identifier(NameUtils.getPackagePartClassNamePrefix(filePath.substringAfterLast('/').substringBeforeLast('.')))
@JvmStatic
fun hasName(name: Name) = name != SpecialNames.NO_NAME_PROVIDED && name != SpecialNames.ANONYMOUS_FUNCTION
}
@@ -0,0 +1,52 @@
/*
* Copyright 2010-2015 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.kotlin.name;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class SpecialNames {
public static final Name NO_NAME_PROVIDED = Name.special("<no name provided>");
public static final Name ROOT_PACKAGE = Name.special("<root package>");
public static final Name DEFAULT_NAME_FOR_COMPANION_OBJECT = Name.identifier("Companion");
// This name is used as a key for the case when something has no name _due to a syntactic error_
// Example: fun (x: Int) = 5
// There's no name for this function in the PSI
// The name contains a GUID to avoid clashes, if a clash happens, it's not a big deal: the code does not compile anyway
public static final Name SAFE_IDENTIFIER_FOR_NO_NAME = Name.identifier("no_name_in_PSI_3d19d79d_1ba9_4cd0_b7f5_b46aa3cd5d40");
public static final String ANONYMOUS = "<anonymous>";
public static final Name ANONYMOUS_FUNCTION = Name.special(ANONYMOUS);
@NotNull
public static Name safeIdentifier(@Nullable Name name) {
return name != null && !name.isSpecial() ? name : SAFE_IDENTIFIER_FOR_NO_NAME;
}
@NotNull
public static Name safeIdentifier(@Nullable String name) {
return safeIdentifier(name == null ? null : Name.identifier(name));
}
public static boolean isSafeIdentifier(@NotNull Name name) {
return !name.asString().isEmpty() && !name.isSpecial();
}
private SpecialNames() {}
}