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.util; 018 019import org.jetbrains.annotations.NotNull; 020import org.jetbrains.annotations.Nullable; 021import org.jetbrains.jet.lang.resolve.ImportPath; 022import org.jetbrains.jet.lang.resolve.name.FqName; 023import org.jetbrains.jet.lang.resolve.name.Name; 024 025/** 026 * Common methods for working with qualified names. 027 */ 028public 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 (subpackageNameStr.startsWith(packageNameStr) && subpackageNameStr.charAt(packageNameStr.length()) == '.'); 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 @NotNull 082 public static FqName combine(@NotNull FqName first, @NotNull Name second) { 083 return first.child(second); 084 } 085 086 /** 087 * Get tail part of the full fqn by subtracting head part. 088 * 089 * @param headFQN 090 * @param fullFQN 091 * @return tail fqn. If first part is not a begging of the full fqn, fullFQN will be returned. 092 */ 093 @NotNull 094 public static String tail(@NotNull FqName headFQN, @NotNull FqName fullFQN) { 095 if (!isSubpackageOf(fullFQN, headFQN) || headFQN.isRoot()) { 096 return fullFQN.asString(); 097 } 098 099 return fullFQN.equals(headFQN) ? 100 "" : 101 fullFQN.asString().substring(headFQN.asString().length() + 1); // (headFQN + '.').length 102 } 103 104 /** 105 * Add one segment of nesting to given qualified name according to the full qualified name. 106 * 107 * @param fqn 108 * @param fullFQN 109 * @return qualified name with one more segment or null if fqn is not head part of fullFQN or there's no additional segment. 110 */ 111 @Nullable 112 public static FqName plusOneSegment(@NotNull FqName fqn, @NotNull FqName fullFQN) { 113 if (!isSubpackageOf(fullFQN, fqn)) { 114 return null; 115 } 116 117 String nextSegment = getFirstSegment(tail(fqn, fullFQN)); 118 119 if (isOneSegmentFQN(nextSegment)) { 120 return combine(fqn, Name.guess(nextSegment)); 121 } 122 123 return null; 124 } 125 126 public static boolean isImported(@NotNull ImportPath alreadyImported, @NotNull FqName fqName) { 127 if (alreadyImported.hasAlias()) { 128 return false; 129 } 130 131 if (alreadyImported.isAllUnder() && !fqName.isRoot()) { 132 return alreadyImported.fqnPart().equals(fqName.parent()); 133 } 134 135 return alreadyImported.fqnPart().equals(fqName); 136 } 137 138 public static boolean isImported(@NotNull ImportPath alreadyImported, @NotNull ImportPath newImport) { 139 if (newImport.isAllUnder() || newImport.hasAlias()) { 140 return alreadyImported.equals(newImport); 141 } 142 143 return isImported(alreadyImported, newImport.fqnPart()); 144 } 145 146 public static boolean isImported(@NotNull Iterable<ImportPath> imports, @NotNull ImportPath newImport) { 147 for (ImportPath alreadyImported : imports) { 148 if (isImported(alreadyImported, newImport)) { 149 return true; 150 } 151 } 152 153 return false; 154 } 155 156 public static boolean isValidJavaFqName(@Nullable String qualifiedName) { 157 if (qualifiedName == null) return false; 158 159 // Check that it is javaName(\.javaName)* or an empty string 160 161 class State {} 162 State BEGINNING = new State(); 163 State MIDDLE = new State(); 164 State AFTER_DOT = new State(); 165 166 State state = BEGINNING; 167 168 int length = qualifiedName.length(); 169 for (int i = 0; i < length; i++) { 170 char c = qualifiedName.charAt(i); 171 if (state == BEGINNING || state == AFTER_DOT) { 172 if (!Character.isJavaIdentifierPart(c)) return false; 173 state = MIDDLE; 174 } 175 else if (state == MIDDLE) { 176 if (c == '.') { 177 state = AFTER_DOT; 178 } 179 else if (!Character.isJavaIdentifierPart(c)) { 180 return false; 181 } 182 } 183 } 184 185 return state != AFTER_DOT; 186 } 187}