blob: 23811567380cb458be3b8ddb6f1a48efb5a8dfc6 [file] [log] [blame]
/*
* Copyright 2003-2012 Bas Leijdekkers
*
* 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.siyeh.ig.fixes;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiFormatUtil;
import com.intellij.psi.util.PsiUtil;
import com.siyeh.ig.psiutils.ClassUtils;
import com.siyeh.ig.psiutils.TypeUtils;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
public class SerialVersionUIDBuilder extends JavaRecursiveElementVisitor {
@NonNls private static final String ACCESS_METHOD_NAME_PREFIX = "access$";
private final PsiClass clazz;
private int index = -1;
private final Set<MemberSignature> nonPrivateConstructors;
private final Set<MemberSignature> nonPrivateMethods;
private final Set<MemberSignature> nonPrivateFields;
private final List<MemberSignature> staticInitializers;
private boolean assertStatement = false;
private boolean classObjectAccessExpression = false;
private final Map<PsiElement, String> memberMap =
new HashMap<PsiElement, String>();
private static final Comparator<PsiClass> INTERFACE_COMPARATOR =
new Comparator<PsiClass>() {
@Override
public int compare(PsiClass object1, PsiClass object2) {
if (object1 == null && object2 == null) {
return 0;
}
if (object1 == null) {
return 1;
}
if (object2 == null) {
return -1;
}
final String name1 = object1.getQualifiedName();
final String name2 = object2.getQualifiedName();
if (name1 == null && name2 == null) {
return 0;
}
if (name1 == null) {
return 1;
}
if (name2 == null) {
return -1;
}
return name1.compareTo(name2);
}
};
@NonNls private static final String CLASS_ACCESS_METHOD_PREFIX = "class$";
private SerialVersionUIDBuilder(PsiClass clazz) {
super();
this.clazz = clazz;
nonPrivateMethods = new HashSet<MemberSignature>();
final PsiMethod[] methods = clazz.getMethods();
for (final PsiMethod method : methods) {
if (!method.isConstructor() &&
!method.hasModifierProperty(PsiModifier.PRIVATE)) {
final MemberSignature methodSignature =
new MemberSignature(method);
nonPrivateMethods.add(methodSignature);
}
}
nonPrivateFields = new HashSet<MemberSignature>();
final PsiField[] fields = clazz.getFields();
for (final PsiField field : fields) {
if (!field.hasModifierProperty(PsiModifier.PRIVATE) ||
!(field.hasModifierProperty(PsiModifier.STATIC) ||
field.hasModifierProperty(PsiModifier.TRANSIENT))) {
final MemberSignature fieldSignature =
new MemberSignature(field);
nonPrivateFields.add(fieldSignature);
}
}
staticInitializers = new ArrayList<MemberSignature>();
final PsiClassInitializer[] initializers = clazz.getInitializers();
if (initializers.length > 0) {
for (final PsiClassInitializer initializer : initializers) {
final PsiModifierList modifierList =
initializer.getModifierList();
if (modifierList != null &&
modifierList.hasModifierProperty(PsiModifier.STATIC)) {
final MemberSignature initializerSignature =
MemberSignature.getStaticInitializerMemberSignature();
staticInitializers.add(initializerSignature);
break;
}
}
}
if (staticInitializers.isEmpty()) {
final PsiField[] psiFields = clazz.getFields();
for (final PsiField field : psiFields) {
if (hasStaticInitializer(field)) {
final MemberSignature initializerSignature =
MemberSignature.getStaticInitializerMemberSignature();
staticInitializers.add(initializerSignature);
break;
}
}
}
nonPrivateConstructors = new HashSet<MemberSignature>();
final PsiMethod[] constructors = clazz.getConstructors();
if (constructors.length == 0 && !clazz.isInterface()) {
// generated empty constructor if no constructor is defined in the source
final MemberSignature constructorSignature;
if (clazz.hasModifierProperty(PsiModifier.PUBLIC)) {
constructorSignature = MemberSignature.getPublicConstructor();
}
else {
constructorSignature = MemberSignature.getPackagePrivateConstructor();
}
nonPrivateConstructors.add(constructorSignature);
}
for (final PsiMethod constructor : constructors) {
if (!constructor.hasModifierProperty(PsiModifier.PRIVATE)) {
final MemberSignature constructorSignature =
new MemberSignature(constructor);
nonPrivateConstructors.add(constructorSignature);
}
}
}
public static long computeDefaultSUID(PsiClass psiClass) {
final Project project = psiClass.getProject();
final GlobalSearchScope scope = GlobalSearchScope.allScope(project);
final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project);
final PsiClass serializable =
psiFacade.findClass(CommonClassNames.JAVA_IO_SERIALIZABLE, scope);
if (serializable == null) {
// no jdk defined for project.
return -1L;
}
final boolean isSerializable = psiClass.isInheritor(serializable, true);
if (!isSerializable) {
return 0L;
}
final SerialVersionUIDBuilder serialVersionUIDBuilder = new SerialVersionUIDBuilder(psiClass);
try {
final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
final DataOutputStream dataOutputStream = new DataOutputStream(byteArrayOutputStream);
final String className = PsiFormatUtil.getExternalName(psiClass);
dataOutputStream.writeUTF(className);
final PsiModifierList classModifierList = psiClass.getModifierList();
int classModifiers = classModifierList != null ? MemberSignature.calculateModifierBitmap(classModifierList) : 0;
final MemberSignature[] methodSignatures = serialVersionUIDBuilder.getNonPrivateMethodSignatures();
if (psiClass.isInterface()) {
classModifiers |= Modifier.INTERFACE;
if (methodSignatures.length == 0) {
// interfaces were not marked abstract when they did't have methods in java 1.0
// For serialization compatibility the abstract modifier is ignored.
classModifiers &= ~Modifier.ABSTRACT;
}
}
dataOutputStream.writeInt(classModifiers);
final PsiClass[] interfaces = psiClass.getInterfaces();
Arrays.sort(interfaces, INTERFACE_COMPARATOR);
for (PsiClass aInterfaces : interfaces) {
final String name = aInterfaces.getQualifiedName();
dataOutputStream.writeUTF(name);
}
final MemberSignature[] fields = serialVersionUIDBuilder.getNonPrivateFields();
Arrays.sort(fields);
for (final MemberSignature field : fields) {
dataOutputStream.writeUTF(field.getName());
dataOutputStream.writeInt(field.getModifiers());
dataOutputStream.writeUTF(field.getSignature());
}
final MemberSignature[] staticInitializers = serialVersionUIDBuilder.getStaticInitializers();
for (final MemberSignature staticInitializer : staticInitializers) {
dataOutputStream.writeUTF(staticInitializer.getName());
dataOutputStream.writeInt(staticInitializer.getModifiers());
dataOutputStream.writeUTF(staticInitializer.getSignature());
}
final MemberSignature[] constructors = serialVersionUIDBuilder.getNonPrivateConstructors();
Arrays.sort(constructors);
for (final MemberSignature constructor : constructors) {
dataOutputStream.writeUTF(constructor.getName());
dataOutputStream.writeInt(constructor.getModifiers());
dataOutputStream.writeUTF(constructor.getSignature());
}
Arrays.sort(methodSignatures);
for (final MemberSignature methodSignature : methodSignatures) {
dataOutputStream.writeUTF(methodSignature.getName());
dataOutputStream.writeInt(methodSignature.getModifiers());
dataOutputStream.writeUTF(methodSignature.getSignature());
}
dataOutputStream.flush();
@NonNls final String algorithm = "SHA";
final MessageDigest digest = MessageDigest.getInstance(algorithm);
final byte[] digestBytes = digest.digest(byteArrayOutputStream.toByteArray());
long serialVersionUID = 0L;
for (int i = Math.min(digestBytes.length, 8) - 1; i >= 0; i--) {
serialVersionUID = serialVersionUID << 8 | digestBytes[i] & 0xFF;
}
return serialVersionUID;
}
catch (IOException exception) {
final InternalError internalError = new InternalError(exception.getMessage());
internalError.initCause(exception);
throw internalError;
}
catch (NoSuchAlgorithmException exception) {
final SecurityException securityException = new SecurityException(exception.getMessage());
securityException.initCause(exception);
throw securityException;
}
}
private void createClassObjectAccessSynthetics(PsiType type) {
if (!classObjectAccessExpression) {
final MemberSignature syntheticMethod =
MemberSignature.getClassAccessMethodMemberSignature();
nonPrivateMethods.add(syntheticMethod);
}
PsiType unwrappedType = type;
@NonNls final StringBuffer fieldNameBuffer;
if (type instanceof PsiArrayType) {
fieldNameBuffer = new StringBuffer();
fieldNameBuffer.append("array");
while (unwrappedType instanceof PsiArrayType) {
final PsiArrayType arrayType = (PsiArrayType)unwrappedType;
unwrappedType = arrayType.getComponentType();
fieldNameBuffer.append('$');
}
}
else {
fieldNameBuffer = new StringBuffer(CLASS_ACCESS_METHOD_PREFIX);
}
if (unwrappedType instanceof PsiPrimitiveType) {
final PsiPrimitiveType primitiveType = (PsiPrimitiveType)unwrappedType;
fieldNameBuffer.append(MemberSignature.createPrimitiveType(primitiveType));
}
else {
final String text = unwrappedType.getCanonicalText().replace('.',
'$');
fieldNameBuffer.append(text);
}
final String fieldName = fieldNameBuffer.toString();
final MemberSignature memberSignature =
new MemberSignature(fieldName, Modifier.STATIC,
"Ljava/lang/Class;");
if (!nonPrivateFields.contains(memberSignature)) {
nonPrivateFields.add(memberSignature);
}
classObjectAccessExpression = true;
}
private String getAccessMethodIndex(PsiElement element) {
String cache = memberMap.get(element);
if (cache == null) {
cache = String.valueOf(index);
index++;
memberMap.put(element, cache);
}
return cache;
}
public MemberSignature[] getNonPrivateConstructors() {
init();
return nonPrivateConstructors.toArray(new MemberSignature[nonPrivateConstructors.size()]);
}
public MemberSignature[] getNonPrivateFields() {
init();
return nonPrivateFields.toArray(new MemberSignature[nonPrivateFields.size()]);
}
public MemberSignature[] getNonPrivateMethodSignatures() {
init();
return nonPrivateMethods.toArray(new MemberSignature[nonPrivateMethods.size()]);
}
public MemberSignature[] getStaticInitializers() {
init();
return staticInitializers.toArray(new MemberSignature[staticInitializers.size()]);
}
private static boolean hasStaticInitializer(PsiField field) {
if (field.hasModifierProperty(PsiModifier.STATIC)) {
final PsiExpression initializer = field.getInitializer();
if (initializer == null) {
return false;
}
final PsiType fieldType = field.getType();
final PsiType stringType = TypeUtils.getStringType(field);
if (field.hasModifierProperty(PsiModifier.FINAL) && (fieldType instanceof PsiPrimitiveType || fieldType.equals(stringType))) {
return !PsiUtil.isConstantExpression(initializer);
}
else {
return true;
}
}
return false;
}
private void init() {
if (index < 0) {
index = 0;
clazz.acceptChildren(this);
}
}
@Override
public void visitAssertStatement(PsiAssertStatement statement) {
super.visitAssertStatement(statement);
if (assertStatement) {
return;
}
final MemberSignature memberSignature =
MemberSignature.getAssertionsDisabledFieldMemberSignature();
nonPrivateFields.add(memberSignature);
final PsiManager manager = clazz.getManager();
final PsiElementFactory factory = JavaPsiFacade.getInstance(manager.getProject()).getElementFactory();
final PsiClassType classType = factory.createType(clazz);
createClassObjectAccessSynthetics(classType);
if (staticInitializers.isEmpty()) {
final MemberSignature initializerSignature =
MemberSignature.getStaticInitializerMemberSignature();
staticInitializers.add(initializerSignature);
}
assertStatement = true;
}
@Override
public void visitClassObjectAccessExpression(
PsiClassObjectAccessExpression expression) {
final PsiTypeElement operand = expression.getOperand();
final PsiType type = operand.getType();
if (!(type instanceof PsiPrimitiveType)) {
createClassObjectAccessSynthetics(type);
}
super.visitClassObjectAccessExpression(expression);
}
@Override
public void visitMethodCallExpression(
@NotNull PsiMethodCallExpression methodCallExpression) {
// for navigating the psi tree in the order javac navigates its AST
final PsiExpressionList argumentList =
methodCallExpression.getArgumentList();
final PsiExpression[] expressions = argumentList.getExpressions();
for (final PsiExpression expression : expressions) {
expression.accept(this);
}
final PsiReferenceExpression methodExpression =
methodCallExpression.getMethodExpression();
methodExpression.accept(this);
}
@Override
public void visitReferenceElement(PsiJavaCodeReferenceElement reference) {
super.visitReferenceElement(reference);
final PsiElement parentClass = ClassUtils.getContainingClass(reference);
if (reference.getParent() instanceof PsiTypeElement) {
return;
}
final PsiElement element = reference.resolve();
if (!(element instanceof PsiClass)) {
return;
}
final PsiClass elementParentClass =
ClassUtils.getContainingClass(element);
if (elementParentClass == null ||
!elementParentClass.equals(clazz) ||
element.equals(parentClass)) {
return;
}
final PsiClass innerClass = (PsiClass)element;
if (!innerClass.hasModifierProperty(PsiModifier.PRIVATE)) {
return;
}
final PsiMethod[] constructors = innerClass.getConstructors();
if (constructors.length == 0) {
getAccessMethodIndex(innerClass);
}
}
@Override
public void visitReferenceExpression(
@NotNull PsiReferenceExpression expression) {
super.visitReferenceExpression(expression);
final PsiElement element = expression.resolve();
final PsiElement elementParentClass =
ClassUtils.getContainingClass(element);
final PsiElement expressionParentClass =
ClassUtils.getContainingClass(expression);
if (expressionParentClass == null || expressionParentClass
.equals(elementParentClass)) {
return;
}
PsiElement parentOfParentClass =
ClassUtils.getContainingClass(expressionParentClass);
while (parentOfParentClass != null &&
!parentOfParentClass.equals(clazz)) {
if (!(expressionParentClass instanceof PsiAnonymousClass)) {
getAccessMethodIndex(expressionParentClass);
}
getAccessMethodIndex(parentOfParentClass);
parentOfParentClass = ClassUtils.getContainingClass(parentOfParentClass);
}
if (element instanceof PsiField) {
final PsiField field = (PsiField)element;
if (field.hasModifierProperty(PsiModifier.PRIVATE)) {
boolean isStatic = false;
final PsiType type = field.getType();
if (field.hasModifierProperty(PsiModifier.STATIC)) {
if (field.hasModifierProperty(PsiModifier.FINAL) &&
type instanceof PsiPrimitiveType) {
final PsiExpression initializer = field.getInitializer();
if (PsiUtil.isConstantExpression(initializer)) {
return;
}
}
isStatic = true;
}
final String returnTypeSignature =
MemberSignature.createTypeSignature(type).replace('/',
'.');
final String className = clazz.getQualifiedName();
@NonNls final StringBuilder signatureBuffer =
new StringBuilder("(");
if (!isStatic) {
signatureBuffer.append('L').append(className).append(';');
}
final String accessMethodIndex = getAccessMethodIndex(field);
if (!field.getContainingClass().equals(clazz)) {
return;
}
@NonNls String name = null;
final PsiElement parent = expression.getParent();
if (parent instanceof PsiAssignmentExpression) {
final PsiAssignmentExpression assignment = (PsiAssignmentExpression)parent;
if (assignment.getLExpression().equals(expression)) {
name = ACCESS_METHOD_NAME_PREFIX + accessMethodIndex +
"02";
signatureBuffer.append(returnTypeSignature);
}
}
else if (parent instanceof PsiPostfixExpression) {
final PsiPostfixExpression postfixExpression =
(PsiPostfixExpression)parent;
final IElementType tokenType = postfixExpression.getOperationTokenType();
if (tokenType.equals(JavaTokenType.PLUSPLUS)) {
name = ACCESS_METHOD_NAME_PREFIX + accessMethodIndex +
"08";
}
else if (tokenType.equals(JavaTokenType.MINUSMINUS)) {
name = ACCESS_METHOD_NAME_PREFIX + accessMethodIndex +
"10";
}
}
else if (parent instanceof PsiPrefixExpression) {
final PsiPrefixExpression prefixExpression = (PsiPrefixExpression)parent;
final IElementType tokenType = prefixExpression.getOperationTokenType();
if (tokenType.equals(JavaTokenType.PLUSPLUS)) {
name = ACCESS_METHOD_NAME_PREFIX + accessMethodIndex +
"04";
}
else if (tokenType.equals(JavaTokenType.MINUSMINUS)) {
name = ACCESS_METHOD_NAME_PREFIX + accessMethodIndex +
"06";
}
}
if (name == null) {
name = ACCESS_METHOD_NAME_PREFIX + accessMethodIndex + "00";
}
signatureBuffer.append(')').append(returnTypeSignature);
final String signature = signatureBuffer.toString();
final MemberSignature methodSignature =
new MemberSignature(name, Modifier.STATIC, signature);
nonPrivateMethods.add(methodSignature);
}
}
else if (element instanceof PsiMethod) {
final PsiMethod method = (PsiMethod)element;
if (method.hasModifierProperty(PsiModifier.PRIVATE) &&
method.getContainingClass().equals(clazz)) {
final String signature;
if (method.hasModifierProperty(PsiModifier.STATIC)) {
signature =
MemberSignature.createMethodSignature(method)
.replace('/', '.');
}
else {
final String returnTypeSignature =
MemberSignature.createTypeSignature(method.getReturnType())
.replace('/', '.');
@NonNls final StringBuilder signatureBuffer =
new StringBuilder();
signatureBuffer.append("(L");
signatureBuffer.append(clazz.getQualifiedName())
.append(';');
final PsiParameter[] parameters = method.getParameterList()
.getParameters();
for (final PsiParameter parameter : parameters) {
final PsiType type = parameter.getType();
final String typeSignature = MemberSignature.createTypeSignature(type)
.replace('/', '.');
signatureBuffer.append(typeSignature);
}
signatureBuffer.append(')');
signatureBuffer.append(returnTypeSignature);
signature = signatureBuffer.toString();
}
final String accessMethodIndex = getAccessMethodIndex(method);
final MemberSignature methodSignature =
new MemberSignature(ACCESS_METHOD_NAME_PREFIX +
accessMethodIndex + "00",
Modifier.STATIC, signature);
nonPrivateMethods.add(methodSignature);
}
}
}
}