blob: a886f29c84f1cf4f37bc41682ead274b3be33a4d [file] [log] [blame]
/*
* Copyright 2001-2013 the original author or authors.
*
* 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.generate.tostring.psi;
import com.intellij.openapi.project.Project;
import com.intellij.pom.java.LanguageLevel;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.javadoc.PsiDocComment;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PropertyUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.generate.tostring.util.StringUtil;
import static com.intellij.psi.CommonClassNames.*;
/**
* Basic PSI Adapter with common function that works in all supported versions of IDEA.
*/
public class PsiAdapter {
private PsiAdapter() {}
/**
* Returns true if a field is constant.
* <p/>
* This is identified as the name of the field is only in uppercase and it has
* a <code>static</code> modifier.
*
* @param field field to check if it's a constant
* @return true if constant.
*/
public static boolean isConstantField(PsiField field) {
PsiModifierList list = field.getModifierList();
if (list == null) {
return false;
}
// modifier must be static
if (!list.hasModifierProperty(PsiModifier.STATIC)) {
return false;
}
// name must NOT have any lowercase character
return !StringUtil.hasLowerCaseChar(field.getName());
}
/**
* Finds an existing method with the given name.
* If there isn't a method with the name, null is returned.
*
* @param clazz the class
* @param name name of method to find
* @return the found method, null if none exist
*/
@Nullable
public static PsiMethod findMethodByName(PsiClass clazz, String name) {
PsiMethod[] methods = clazz.getMethods();
// use reverse to find from bottom as the duplicate conflict resolution policy requires this
for (int i = methods.length - 1; i >= 0; i--) {
PsiMethod method = methods[i];
if (name.equals(method.getName())) {
return method;
}
}
return null;
}
/**
* Returns true if the given field a primitive array type (e.g., int[], long[], float[]).
*
* @param type type.
* @return true if field is a primitive array type.
*/
public static boolean isPrimitiveArrayType(PsiType type) {
return type instanceof PsiArrayType && isPrimitiveType(((PsiArrayType) type).getComponentType());
}
/**
* Is the type an Object array type (etc. String[], Object[])?
*
* @param type type.
* @return true if it's an Object array type.
*/
public static boolean isObjectArrayType(PsiType type) {
return type instanceof PsiArrayType && !isPrimitiveType(((PsiArrayType) type).getComponentType());
}
/**
* Is the type a String array type (etc. String[])?
*
* @param type type.
* @return true if it's a String array type.
*/
public static boolean isStringArrayType(PsiType type) {
if (isPrimitiveType(type))
return false;
return type.getCanonicalText().indexOf("String[]") > 0;
}
/**
* Is the given field a {@link java.util.Collection} type?
*
* @param factory element factory.
* @param type type.
* @return true if it's a Collection type.
*/
public static boolean isCollectionType(PsiElementFactory factory, PsiType type) {
return isTypeOf(factory, type, "java.util.Collection");
}
/**
* Is the given field a {@link java.util.Map} type?
*
* @param factory element factory.
* @param type type.
* @return true if it's a Map type.
*/
public static boolean isMapType(PsiElementFactory factory, PsiType type) {
return isTypeOf(factory, type, CommonClassNames.JAVA_UTIL_MAP);
}
/**
* Is the given field a {@link java.util.Set} type?
*
* @param factory element factory.
* @param type type.
* @return true if it's a Map type.
*/
public static boolean isSetType(PsiElementFactory factory, PsiType type) {
return isTypeOf(factory, type, CommonClassNames.JAVA_UTIL_SET);
}
/**
* Is the given field a {@link java.util.List} type?
*
* @param factory element factory.
* @param type type.
* @return true if it's a Map type.
*/
public static boolean isListType(PsiElementFactory factory, PsiType type) {
return isTypeOf(factory, type, CommonClassNames.JAVA_UTIL_LIST);
}
/**
* Is the given field a {@link java.lang.String} type?
*
* @param factory element factory.
* @param type type.
* @return true if it's a String type.
*/
public static boolean isStringType(PsiElementFactory factory, PsiType type) {
return isTypeOf(factory, type, JAVA_LANG_STRING);
}
/**
* Is the given field assignable from {@link java.lang.Object}?
*
* @param factory element factory.
* @param type type.
* @return true if it's an Object type.
*/
public static boolean isObjectType(PsiElementFactory factory, PsiType type) {
return isTypeOf(factory, type, JAVA_LANG_OBJECT);
}
/**
* Is the given field a {@link java.util.Date} type?
*
* @param factory element factory.
* @param type type.
* @return true if it's a Date type.
*/
public static boolean isDateType(PsiElementFactory factory, PsiType type) {
return isTypeOf(factory, type, "java.util.Date");
}
/**
* Is the given field a {@link java.util.Calendar} type?
*
* @param factory element factory.
* @param type type.
* @return true if it's a Calendar type.
*/
public static boolean isCalendarType(PsiElementFactory factory, PsiType type) {
return isTypeOf(factory, type, "java.util.Calendar");
}
/**
* Is the given field a {@link java.lang.Boolean} type or a primitive boolean type?
*
* @param factory element factory.
* @param type type.
* @return true if it's a Boolean or boolean type.
*/
public static boolean isBooleanType(PsiElementFactory factory, PsiType type) {
if (isPrimitiveType(type)) {
// test for simple type of boolean
String s = type.getCanonicalText();
return "boolean".equals(s);
} else {
// test for Object type of Boolean
return isTypeOf(factory, type, JAVA_LANG_BOOLEAN);
}
}
/**
* Is the given field a numeric type (assignable from java.lang.Numeric or a primitive type of byte, short, int, long, float, double type)?
*
* @param factory element factory.
* @param type type.
* @return true if it's a numeric type.
*/
public static boolean isNumericType(PsiElementFactory factory, PsiType type) {
if (isPrimitiveType(type)) {
// test for simple type of numeric
String s = type.getCanonicalText();
return "byte".equals(s) || "double".equals(s) || "float".equals(s) || "int".equals(s) || "long".equals(s) || "short".equals(s);
} else {
// test for Object type of numeric
return isTypeOf(factory, type, "java.lang.Number");
}
}
/**
* Does the javafile have the import statement?
*
* @param javaFile javafile.
* @param importStatement import statement to test existing for.
* @return true if the javafile has the import statement.
*/
public static boolean hasImportStatement(PsiJavaFile javaFile, String importStatement) {
PsiImportList importList = javaFile.getImportList();
if (importList == null) {
return false;
}
if (importStatement.endsWith(".*")) {
return importList.findOnDemandImportStatement(fixImportStatement(importStatement)) != null;
} else {
return importList.findSingleClassImportStatement(importStatement) != null;
}
}
/**
* Adds an import statement to the javafile and optimizes the imports afterwards.
*
*
* @param javaFile javafile.
* @param importStatementOnDemand name of import statement, must be with a wildcard (etc. java.util.*).
* @throws com.intellij.util.IncorrectOperationException
* is thrown if there is an error creating the import statement.
*/
public static void addImportStatement(PsiJavaFile javaFile, String importStatementOnDemand) {
PsiElementFactory factory = JavaPsiFacade.getInstance(javaFile.getProject()).getElementFactory();
PsiImportStatement is = factory.createImportStatementOnDemand(fixImportStatement(importStatementOnDemand));
// add the import to the file, and optimize the imports
PsiImportList importList = javaFile.getImportList();
if (importList != null) {
importList.add(is);
}
JavaCodeStyleManager.getInstance(javaFile.getProject()).optimizeImports(javaFile);
}
/**
* Fixes the import statement to be returned as packagename only (without .* or any Classname).
* <p/>
* <br/>Example: java.util will be returned as java.util
* <br/>Example: java.util.* will be returned as java.util
* <br/>Example: java.text.SimpleDateFormat will be returned as java.text
*
* @param importStatementOnDemand import statement
* @return import statement only with packagename
*/
private static String fixImportStatement(String importStatementOnDemand) {
if (importStatementOnDemand.endsWith(".*")) {
return importStatementOnDemand.substring(0, importStatementOnDemand.length() - 2);
} else {
boolean hasClassname = StringUtil.hasUpperCaseChar(importStatementOnDemand);
if (hasClassname) {
// extract packagename part
int pos = importStatementOnDemand.lastIndexOf(".");
return importStatementOnDemand.substring(0, pos);
} else {
// it is a pure packagename
return importStatementOnDemand;
}
}
}
/**
* Gets the fields fully qualified classname (etc java.lang.String, java.util.ArrayList)
*
* @param type the type.
* @return the fully qualified classname, null if the field is a primitive.
* @see #getTypeClassName(com.intellij.psi.PsiType) for the non qualified version.
*/
@Nullable
public static String getTypeQualifiedClassName(PsiType type) {
if (isPrimitiveType(type)) {
return null;
}
// avoid [] if the type is an array
String name = type.getCanonicalText();
if (name.endsWith("[]")) {
return name.substring(0, name.length() - 2);
}
return name;
}
/**
* Gets the fields classname (etc. String, ArrayList)
*
* @param type the type.
* @return the classname, null if the field is a primitive.
* @see #getTypeQualifiedClassName(com.intellij.psi.PsiType) for the qualified version.
*/
@Nullable
public static String getTypeClassName(PsiType type) {
String name = getTypeQualifiedClassName(type);
// return null if it was a primitive type
if (name == null) {
return null;
}
int i = name.lastIndexOf('.');
return name.substring(i + 1, name.length());
}
/**
* Finds the public static void main(String[] args) method.
*
* @param clazz the class.
* @return the method if it exists, null if not.
*/
@Nullable
public static PsiMethod findPublicStaticVoidMainMethod(PsiClass clazz) {
PsiMethod[] methods = clazz.findMethodsByName("main", false);
// is it public static void main(String[] args)
for (PsiMethod method : methods) {
// must be public
if (!method.hasModifierProperty(PsiModifier.PUBLIC)) {
continue;
}
// must be static
if (!method.hasModifierProperty(PsiModifier.STATIC)) {
continue;
}
// must have void as return type
PsiType returnType = method.getReturnType();
if (!PsiType.VOID.equals(returnType)) {
continue;
}
// must have one parameter
PsiParameter[] parameters = method.getParameterList().getParameters();
if (parameters.length != 1) {
continue;
}
// parameter must be string array
if (!isStringArrayType(parameters[0].getType())) {
continue;
}
// public static void main(String[] args) method found
return method;
}
// main not found
return null;
}
/**
* Add or replaces the javadoc comment to the given method.
*
* @param method the method the javadoc should be added/set to.
* @param javadoc the javadoc comment.
* @param replace true if any existing javadoc should be replaced. false will not replace any existing javadoc and thus leave the javadoc untouched.
* @return the added/replace javadoc comment, null if the was an existing javadoc and it should <b>not</b> be replaced.
* @throws IncorrectOperationException is thrown if error adding/replacing the javadoc comment.
*/
@Nullable
public static PsiComment addOrReplaceJavadoc(PsiMethod method, String javadoc, boolean replace) {
final Project project = method.getProject();
PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory();
PsiComment comment = factory.createCommentFromText(javadoc, null);
// does a method already exists?
PsiDocComment doc = method.getDocComment();
if (doc != null) {
if (replace) {
// javadoc already exists, so replace
doc.replace(comment);
final CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(project);
codeStyleManager.reformat(method); // to reformat javadoc
return comment;
} else {
// do not replace existing javadoc
return null;
}
} else {
// add new javadoc
method.addBefore(comment, method.getFirstChild());
final CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(project);
codeStyleManager.reformat(method); // to reformat javadoc
return comment;
}
}
/**
* Is the given type a "void" type.
*
* @param type the type.
* @return true if a void type, false if not.
*/
public static boolean isTypeOfVoid(PsiType type) {
return type != null && type.equalsToText("void");
}
/**
* Is the method a getter method?
* <p/>
* The name of the method must start with <code>get</code> or <code>is</code>.
* And if the method is a <code>isXXX</code> then the method must return a java.lang.Boolean or boolean.
*
*
* @param method the method
* @return true if a getter method, false if not.
*/
public static boolean isGetterMethod(PsiMethod method) {
// must not be a void method
if (isTypeOfVoid(method.getReturnType())) {
return false;
}
final PsiParameterList parameterList = method.getParameterList();
if (parameterList.getParametersCount() != 0) {
return false;
}
return true;
}
/**
* Gets the field name of the getter method.
* <p/>
* The method must be a getter method for a field.
* Returns null if this method is not a getter.
* <p/>
* The fieldname is the part of the name that is after the <code>get</code> or <code>is</code> part
* of the name.
* <p/>
* Example: methodName=getName will return fieldname=name
*
*
* @param method the method
* @return the fieldname if this is a getter method.
* @see #isGetterMethod(com.intellij.psi.PsiMethod) for the getter check
*/
@Nullable
public static String getGetterFieldName(PsiMethod method) {
// must be a getter
if (!isGetterMethod(method)) {
return null;
}
return PropertyUtil.getPropertyNameByGetter(method);
}
/**
* Returns true if the field is enum (JDK1.5).
*
* @param field field to check if it's a enum
* @return true if enum.
*/
public static boolean isEnumField(PsiField field) {
PsiType type = field.getType();
if (!(type instanceof PsiClassType)) {
return false;
}
final PsiClassType classType = (PsiClassType)type;
final PsiClass aClass = classType.resolve();
return (aClass != null) && aClass.isEnum();
}
/**
* Is the class an exception - extends Throwable (will check super).
*
* @param clazz class to check.
* @return true if class is an exception.
*/
public static boolean isExceptionClass(PsiClass clazz) {
return InheritanceUtil.isInheritor(clazz, CommonClassNames.JAVA_LANG_THROWABLE);
}
/**
* Finds the public boolean equals(Object o) method.
*
* @param clazz the class.
* @return the method if it exists, null if not.
*/
@Nullable
public static PsiMethod findEqualsMethod(PsiClass clazz) {
PsiMethod[] methods = clazz.findMethodsByName("equals", false);
// is it public boolean equals(Object o)
for (PsiMethod method : methods) {
// must be public
if (!method.hasModifierProperty(PsiModifier.PUBLIC)) {
continue;
}
// must not be static
if (method.hasModifierProperty(PsiModifier.STATIC)) {
continue;
}
// must have boolean as return type
PsiType returnType = method.getReturnType();
if (!PsiType.BOOLEAN.equals(returnType)) {
continue;
}
// must have one parameter
PsiParameter[] parameters = method.getParameterList().getParameters();
if (parameters.length != 1) {
continue;
}
// parameter must be Object
if (!(parameters[0].getType().getCanonicalText().equals(JAVA_LANG_OBJECT))) {
continue;
}
// equals method found
return method;
}
// equals not found
return null;
}
/**
* Finds the public int hashCode() method.
*
* @param clazz the class.
* @return the method if it exists, null if not.
*/
@Nullable
public static PsiMethod findHashCodeMethod(PsiClass clazz) {
PsiMethod[] methods = clazz.findMethodsByName("hashCode", false);
// is it public int hashCode()
for (PsiMethod method : methods) {
// must be public
if (!method.hasModifierProperty(PsiModifier.PUBLIC)) {
continue;
}
// must not be static
if (method.hasModifierProperty(PsiModifier.STATIC)) {
continue;
}
// must have int as return type
PsiType returnType = method.getReturnType();
if (!PsiType.INT.equals(returnType)) {
continue;
}
// must not have a parameter
PsiParameterList parameters = method.getParameterList();
if (parameters.getParametersCount() != 0) {
continue;
}
// hashCode method found
return method;
}
// hashCode not found
return null;
}
/**
* Check if the given type against a FQ classname (assignable).
*
* @param factory IDEA factory
* @param type the type
* @param typeFQClassName the FQ classname to test against.
* @return true if the given type is assignable of FQ classname.
*/
protected static boolean isTypeOf(PsiElementFactory factory, PsiType type, String typeFQClassName) {
// fix for IDEA where fields can have 'void' type and generate NPE.
if (isTypeOfVoid(type)) {
return false;
}
if (isPrimitiveType(type)) {
return false;
}
GlobalSearchScope scope = type.getResolveScope();
if (scope == null) {
return false;
}
PsiType typeTarget = factory.createTypeByFQClassName(typeFQClassName, scope);
return typeTarget.isAssignableFrom(type);
}
/**
* Gets the names the given class implements (not FQ names).
*
* @param clazz the class
* @return the names.
*/
public static String[] getImplementsClassnames(PsiClass clazz) {
PsiClass[] interfaces = clazz.getInterfaces();
if (interfaces == null || interfaces.length == 0) {
return ArrayUtil.EMPTY_STRING_ARRAY;
}
String[] names = new String[interfaces.length];
for (int i = 0; i < interfaces.length; i++) {
PsiClass anInterface = interfaces[i];
names[i] = anInterface.getName();
}
return names;
}
/**
* Is the given type a primitive?
*
* @param type the type.
* @return true if primitive, false if not.
*/
public static boolean isPrimitiveType(PsiType type) {
return type instanceof PsiPrimitiveType;
}
public static int getJavaVersion(PsiElement element) {
final LanguageLevel languageLevel = PsiUtil.getLanguageLevel(element);
int version = 0;
switch (languageLevel) {
case JDK_1_3:
version = 3;
break;
case JDK_1_4:
version = 4;
break;
case JDK_1_5:
version = 5;
break;
case JDK_1_6:
version = 6;
break;
case JDK_1_7:
version = 7;
break;
case JDK_1_8:
version = 8;
break;
}
return version;
}
}