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 017 package org.jetbrains.jet.util; 018 019 import org.jetbrains.annotations.NotNull; 020 import org.jetbrains.annotations.Nullable; 021 import org.jetbrains.jet.lang.resolve.ImportPath; 022 import org.jetbrains.jet.lang.resolve.name.FqName; 023 import org.jetbrains.jet.lang.resolve.name.Name; 024 025 /** 026 * Common methods for working with qualified names. 027 */ 028 public final class QualifiedNamesUtil { 029 030 private QualifiedNamesUtil() { 031 } 032 033 public static boolean isSubpackageOf(@NotNull FqName subpackageName, @NotNull FqName packageName) { 034 if (subpackageName.equals(packageName)) { 035 return true; 036 } 037 038 if (packageName.isRoot()) { 039 return true; 040 } 041 042 String subpackageNameStr = subpackageName.asString(); 043 String packageNameStr = packageName.asString(); 044 045 return isSubpackageOf(subpackageNameStr, packageNameStr); 046 } 047 048 public static boolean isOneSegmentFQN(@NotNull String fqn) { 049 if (fqn.isEmpty()) { 050 return false; 051 } 052 053 return fqn.indexOf('.') < 0; 054 } 055 056 public static boolean isOneSegmentFQN(@NotNull FqName fqn) { 057 return isOneSegmentFQN(fqn.asString()); 058 } 059 060 @NotNull 061 public static String getFirstSegment(@NotNull String fqn) { 062 int dotIndex = fqn.indexOf('.'); 063 return (dotIndex != -1) ? fqn.substring(0, dotIndex) : fqn; 064 } 065 066 @NotNull 067 public static FqName withoutLastSegment(@NotNull FqName fqName) { 068 return fqName.parent(); 069 } 070 071 @NotNull 072 public static FqName withoutFirstSegment(@NotNull FqName fqName) { 073 if (fqName.isRoot() || fqName.parent().isRoot()) { 074 return FqName.ROOT; 075 } 076 077 String fqNameStr = fqName.asString(); 078 return new FqName(fqNameStr.substring(fqNameStr.indexOf('.'), fqNameStr.length())); 079 } 080 081 public static int numberOfSegments(@NotNull FqName fqName) { 082 if (fqName.isRoot()) { 083 return 0; 084 } 085 086 return 1 + numberOfSegments(fqName.parent()); 087 } 088 089 @NotNull 090 public static FqName combine(@NotNull FqName first, @NotNull Name second) { 091 return first.child(second); 092 } 093 094 /** 095 * Get tail part of the full fqn by subtracting head part. 096 * 097 * @param headFQN 098 * @param fullFQN 099 * @return tail fqn. If first part is not a begging of the full fqn, fullFQN will be returned. 100 */ 101 @NotNull 102 public static String tail(@NotNull FqName headFQN, @NotNull FqName fullFQN) { 103 if (!isSubpackageOf(fullFQN, headFQN) || headFQN.isRoot()) { 104 return fullFQN.asString(); 105 } 106 107 return fullFQN.equals(headFQN) ? 108 "" : 109 fullFQN.asString().substring(headFQN.asString().length() + 1); // (headFQN + '.').length 110 } 111 112 /** 113 * Add one segment of nesting to given qualified name according to the full qualified name. 114 * 115 * @param fqn 116 * @param fullFQN 117 * @return qualified name with one more segment or null if fqn is not head part of fullFQN or there's no additional segment. 118 */ 119 @Nullable 120 public static FqName plusOneSegment(@NotNull FqName fqn, @NotNull FqName fullFQN) { 121 if (!isSubpackageOf(fullFQN, fqn)) { 122 return null; 123 } 124 125 String nextSegment = getFirstSegment(tail(fqn, fullFQN)); 126 127 if (isOneSegmentFQN(nextSegment)) { 128 return combine(fqn, Name.guess(nextSegment)); 129 } 130 131 return null; 132 } 133 134 public static boolean isImported(@NotNull ImportPath alreadyImported, @NotNull FqName fqName) { 135 if (alreadyImported.hasAlias()) { 136 return false; 137 } 138 139 if (alreadyImported.isAllUnder() && !fqName.isRoot()) { 140 return alreadyImported.fqnPart().equals(fqName.parent()); 141 } 142 143 return alreadyImported.fqnPart().equals(fqName); 144 } 145 146 public static boolean isImported(@NotNull ImportPath alreadyImported, @NotNull ImportPath newImport) { 147 if (newImport.isAllUnder() || newImport.hasAlias()) { 148 return alreadyImported.equals(newImport); 149 } 150 151 return isImported(alreadyImported, newImport.fqnPart()); 152 } 153 154 public static boolean isImported(@NotNull Iterable<ImportPath> imports, @NotNull ImportPath newImport) { 155 for (ImportPath alreadyImported : imports) { 156 if (isImported(alreadyImported, newImport)) { 157 return true; 158 } 159 } 160 161 return false; 162 } 163 164 public static boolean isValidJavaFqName(@Nullable String qualifiedName) { 165 if (qualifiedName == null) return false; 166 167 // Check that it is javaName(\.javaName)* or an empty string 168 169 class State {} 170 State BEGINNING = new State(); 171 State MIDDLE = new State(); 172 State AFTER_DOT = new State(); 173 174 State state = BEGINNING; 175 176 int length = qualifiedName.length(); 177 for (int i = 0; i < length; i++) { 178 char c = qualifiedName.charAt(i); 179 if (state == BEGINNING || state == AFTER_DOT) { 180 if (!Character.isJavaIdentifierPart(c)) return false; 181 state = MIDDLE; 182 } 183 184 //noinspection ConstantConditions 185 assert state == MIDDLE; 186 187 if (c == '.') { 188 state = AFTER_DOT; 189 } 190 else if (!Character.isJavaIdentifierPart(c)) { 191 return false; 192 } 193 } 194 195 return state != AFTER_DOT; 196 } 197 198 public static boolean isSubpackageOf(String subpackageNameStr, String packageNameStr) { 199 return subpackageNameStr.equals(packageNameStr) || 200 (subpackageNameStr.startsWith(packageNameStr) && subpackageNameStr.charAt(packageNameStr.length()) == '.'); 201 } 202 }