/* * Copyright 2000-2013 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 com.intellij.psi; import com.intellij.pom.java.LanguageLevel; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.ArrayUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.regex.Pattern; import static com.intellij.util.ObjectUtils.notNull; /** * Service for validating and parsing Java identifiers. * * @see com.intellij.psi.JavaPsiFacade#getNameHelper() */ public abstract class PsiNameHelper { /** * Checks if the specified text is a Java identifier, using the language level of the project * with which the name helper is associated to filter out keywords. * * @param text the text to check. * @return true if the text is an identifier, false otherwise */ public abstract boolean isIdentifier(@Nullable String text); /** * Checks if the specified text is a Java identifier, using the specified language level * with which the name helper is associated to filter out keywords. * * @param text the text to check. * @param languageLevel to check text against. For instance 'assert' or 'enum' might or might not be identifiers depending on language level * @return true if the text is an identifier, false otherwise */ public abstract boolean isIdentifier(@Nullable String text, LanguageLevel languageLevel); /** * Checks if the specified text is a Java keyword, using the language level of the project * with which the name helper is associated. * * @param text the text to check. * @return true if the text is a keyword, false otherwise */ public abstract boolean isKeyword(@Nullable String text); /** * Checks if the specified string is a qualified name (sequence of identifiers separated by * periods). * * @param text the text to check. * @return true if the text is a qualified name, false otherwise. */ public abstract boolean isQualifiedName(@Nullable String text); @NotNull public static String getShortClassName(@NotNull String referenceText) { int lessPos = referenceText.length(), bracesBalance = 0, i; loop: for (i = referenceText.length() - 1; i >= 0; i--) { char ch = referenceText.charAt(i); switch (ch) { case ')': case '>': bracesBalance++; break; case '(': case '<': bracesBalance--; lessPos = i; break; case '@': case '.': if (bracesBalance <= 0) break loop; break; default: if (Character.isWhitespace(ch) && bracesBalance <= 0) { for (int j = i + 1; j < lessPos; j++) { if (!Character.isWhitespace(referenceText.charAt(j))) break loop; } lessPos = i; } } } String sub = referenceText.substring(i + 1, lessPos).trim(); return sub.length() == referenceText.length() ? sub : new String(sub); } @NotNull public static String getPresentableText(@NotNull PsiJavaCodeReferenceElement ref) { String name = ref.getReferenceName(); PsiAnnotation[] annotations = PsiTreeUtil.getChildrenOfType(ref, PsiAnnotation.class); return getPresentableText(name, notNull(annotations, PsiAnnotation.EMPTY_ARRAY), ref.getTypeParameters()); } @NotNull public static String getPresentableText(@Nullable String refName, @NotNull PsiAnnotation[] annotations, @NotNull PsiType[] typeParameters) { if (typeParameters.length == 0 && annotations.length == 0) { return refName != null ? refName : ""; } StringBuilder buffer = new StringBuilder(); if (annotations.length > 0) { for (PsiAnnotation annotation : annotations) { buffer.append(annotation.getText()).append(' '); } } buffer.append(refName); if (typeParameters.length > 0) { buffer.append("<"); for (int i = 0; i < typeParameters.length; i++) { buffer.append(typeParameters[i].getPresentableText()); if (i < typeParameters.length - 1) buffer.append(", "); } buffer.append(">"); } return buffer.toString(); } /** deprecated use {@link #getPresentableText(String, PsiAnnotation[], PsiType[])} (to remove in IDEA 13) */ public static String getPresentableText(@Nullable String referenceName, @NotNull PsiType[] typeParameters) { return getPresentableText(referenceName, PsiAnnotation.EMPTY_ARRAY, typeParameters); } @NotNull public static String getQualifiedClassName(String referenceText, boolean removeWhitespace) { if (removeWhitespace) { referenceText = removeWhitespace(referenceText); } if (referenceText.indexOf('<') < 0) return referenceText; final StringBuilder buffer = new StringBuilder(referenceText.length()); final char[] chars = referenceText.toCharArray(); int gtPos = 0; int count = 0; for (int i = 0; i < chars.length; i++) { final char aChar = chars[i]; switch (aChar) { case '<': count++; if (count == 1) buffer.append(new String(chars, gtPos, i - gtPos)); break; case '>': count--; gtPos = i + 1; break; } } if (count == 0) { buffer.append(new String(chars, gtPos, chars.length - gtPos)); } return buffer.toString(); } private static final Pattern WHITESPACE_PATTERN = Pattern.compile("(?:\\s)|(?:/\\*.*\\*/)|(?://[^\\n]*)"); private static String removeWhitespace(String referenceText) { return WHITESPACE_PATTERN.matcher(referenceText).replaceAll(""); } /** * Obtains text of all type parameter values in a reference. * They go in left-to-right order: <code>A<List<String>>.B<Integer></code> yields * <code>["List<String>","Integer"]</code> * * @param referenceText the text of the reference to calculate type parameters for. * @return the calculated array of type parameters. */ public static String[] getClassParametersText(String referenceText) { if (referenceText.indexOf('<') < 0) return ArrayUtil.EMPTY_STRING_ARRAY; referenceText = removeWhitespace(referenceText); final char[] chars = referenceText.toCharArray(); int afterLastDotIndex = 0; int level = 0; for (int i = 0; i < chars.length; i++) { char aChar = chars[i]; switch (aChar) { case '<': level++; break; case '.': if (level == 0) afterLastDotIndex = i + 1; break; case '>': level--; break; } } if (level != 0) return ArrayUtil.EMPTY_STRING_ARRAY; int dim = 0; for (int i = afterLastDotIndex; i < chars.length; i++) { char aChar = chars[i]; switch (aChar) { case '<': level++; if (level == 1) dim++; break; case ',': if (level == 1) dim++; break; case '>': level--; break; } } if (level != 0 || dim == 0) return ArrayUtil.EMPTY_STRING_ARRAY; final String[] result = new String[dim]; dim = 0; int ltPos = 0; for (int i = afterLastDotIndex; i < chars.length; i++) { final char aChar = chars[i]; switch (aChar) { case '<': level++; if (level == 1) ltPos = i; break; case ',': if (level == 1) { result[dim++] = new String(chars, ltPos + 1, i - ltPos - 1); ltPos = i; } break; case '>': level--; if (level == 0) result[dim++] = new String(chars, ltPos + 1, i - ltPos - 1); break; } } return result; } public static boolean isSubpackageOf(@NotNull String subpackageName, @NotNull String packageName) { return subpackageName.equals(packageName) || subpackageName.startsWith(packageName) && subpackageName.charAt(packageName.length()) == '.'; } }