blob: 9a9fce76f6daf1d85a74f023647dc0f0f9e3fa09 [file] [log] [blame]
/*
* Copyright 2000-2014 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 org.jetbrains.plugins.groovy.refactoring.convertToJava;
import com.intellij.codeInsight.generation.OverrideImplementExploreUtil;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.impl.light.LightElement;
import com.intellij.psi.impl.light.LightMethodBuilder;
import com.intellij.psi.util.MethodSignature;
import com.intellij.psi.util.MethodSignatureBackedByPsiMethod;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.ContainerUtil;
import gnu.trove.THashSet;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.GrModifierList;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrConstructorInvocation;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariableDeclaration;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrEnumConstantInitializer;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrEnumConstant;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrReflectedMethod;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeElement;
import org.jetbrains.plugins.groovy.lang.psi.impl.PsiImplUtil;
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil;
import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GroovyScriptClass;
import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames;
import org.jetbrains.plugins.groovy.lang.psi.util.GroovyConstantExpressionEvaluator;
import org.jetbrains.plugins.groovy.lang.resolve.ast.DelegatedMethod;
import java.util.*;
/**
* @author Maxim.Medvedev
*/
public class StubGenerator implements ClassItemGenerator {
public static final String[] STUB_MODIFIERS = {
PsiModifier.PUBLIC,
PsiModifier.PROTECTED,
PsiModifier.PRIVATE,
PsiModifier.PACKAGE_LOCAL,
PsiModifier.STATIC,
PsiModifier.ABSTRACT,
PsiModifier.FINAL,
PsiModifier.NATIVE,
};
private static final String[] STUB_FIELD_MODIFIERS = {
PsiModifier.PUBLIC,
PsiModifier.PROTECTED,
PsiModifier.PRIVATE,
PsiModifier.PACKAGE_LOCAL,
PsiModifier.STATIC,
PsiModifier.FINAL,
};
private final ClassNameProvider classNameProvider;
private static final Logger LOG = Logger.getInstance("#org.jetbrains.plugins.groovy.refactoring.convertToJava.StubGenerator");
public StubGenerator(ClassNameProvider classNameProvider) {
this.classNameProvider = classNameProvider;
}
@Override
public void writeEnumConstant(StringBuilder text, GrEnumConstant enumConstant) {
text.append(enumConstant.getName());
PsiMethod constructor = enumConstant.resolveMethod();
if (constructor != null) {
text.append('(');
writeStubConstructorInvocation(text, constructor, PsiSubstitutor.EMPTY, enumConstant);
text.append(')');
}
GrEnumConstantInitializer initializer = enumConstant.getInitializingClass();
if (initializer != null) {
text.append("{\n");
for (PsiMethod method : initializer.getMethods()) {
writeMethod(text, method);
}
text.append('}');
}
}
private void writeStubConstructorInvocation(StringBuilder text,
PsiMethod constructor,
PsiSubstitutor substitutor,
PsiElement invocation) {
final PsiParameter[] superParams = constructor.getParameterList().getParameters();
for (int j = 0; j < superParams.length; j++) {
if (j > 0) text.append(", ");
text.append('(');
final PsiType type = superParams[j].getType();
TypeWriter.writeType(text, substitutor.substitute(type), invocation, classNameProvider);
text.append(')').append(GroovyToJavaGenerator.getDefaultValueText(type.getCanonicalText()));
}
}
@Override
public void writeConstructor(final StringBuilder text, PsiMethod constructor, boolean isEnum) {
LOG.assertTrue(constructor.isConstructor());
if (!isEnum) {
text.append("public ");
//writeModifiers(text, constructor.getModifierList(), JAVA_MODIFIERS);
}
/************* name **********/
//append constructor name
text.append(constructor.getName());
/************* parameters **********/
GenerationUtil.writeParameterList(text, constructor.getParameterList().getParameters(), classNameProvider, null);
final Set<String> throwsTypes = collectThrowsTypes(constructor, new THashSet<PsiMethod>());
if (!throwsTypes.isEmpty()) {
text.append("throws ").append(StringUtil.join(throwsTypes, ", ")).append(' ');
}
/************* body **********/
text.append("{\n");
if (constructor instanceof GrReflectedMethod) {
constructor = ((GrReflectedMethod)constructor).getBaseMethod();
}
if (constructor instanceof GrMethod) {
final GrConstructorInvocation invocation = PsiImplUtil.getChainingConstructorInvocation((GrMethod)constructor);
if (invocation != null) {
final GroovyResolveResult resolveResult = resolveChainingConstructor((GrMethod)constructor);
if (resolveResult != null) {
text.append(invocation.isSuperCall() ? "super(" : "this(");
writeStubConstructorInvocation(text, (PsiMethod)resolveResult.getElement(), resolveResult.getSubstitutor(), invocation);
text.append(");");
}
}
else if (constructor instanceof LightElement) {
writeStubConstructorInvocation(constructor, text);
}
}
text.append("\n}\n");
}
private void writeStubConstructorInvocation(PsiMethod constructor, StringBuilder text) {
final PsiClass containingClass = constructor.getContainingClass();
if (containingClass == null) return;
final PsiClass superClass = containingClass.getSuperClass();
if (superClass == null) return;
final PsiMethod[] constructors = superClass.getConstructors();
if (constructors.length == 0) return;
for (PsiMethod method : constructors) {
if (method.getParameterList().getParameters().length == 0 && PsiUtil.isAccessible(method, containingClass, containingClass)) {
return; //default constructor exists
}
}
for (PsiMethod method : constructors) {
if (PsiUtil.isAccessible(method, containingClass, containingClass)) {
text.append("super(");
writeStubConstructorInvocation(text, method, TypeConversionUtil.getSuperClassSubstitutor(superClass, containingClass, PsiSubstitutor.EMPTY), constructor);
text.append(");");
return;
}
}
}
private Set<String> collectThrowsTypes(PsiMethod constructor, Set<PsiMethod> visited) {
LOG.assertTrue(constructor.isConstructor());
final GroovyResolveResult resolveResult = constructor instanceof GrMethod ? resolveChainingConstructor((GrMethod)constructor) : null;
if (resolveResult == null) {
return Collections.emptySet();
}
final PsiSubstitutor substitutor = resolveResult.getSubstitutor();
final PsiMethod chainedConstructor = (PsiMethod)resolveResult.getElement();
assert chainedConstructor != null;
if (!visited.add(chainedConstructor)) {
return Collections.emptySet();
}
final Set<String> result = ContainerUtil.newTroveSet(ArrayUtil.EMPTY_STRING_ARRAY);
for (PsiClassType type : chainedConstructor.getThrowsList().getReferencedTypes()) {
StringBuilder builder = new StringBuilder();
TypeWriter.writeType(builder, substitutor.substitute(type), constructor, classNameProvider);
result.add(builder.toString());
}
if (chainedConstructor instanceof GrMethod) {
LOG.assertTrue(chainedConstructor.isConstructor());
result.addAll(collectThrowsTypes(chainedConstructor, visited));
}
return result;
}
@Override
public void writeMethod(StringBuilder text, PsiMethod method) {
if (method == null) return;
String name = method.getName();
if (!PsiNameHelper.getInstance(method.getProject()).isIdentifier(name)) {
return; //does not have a java image
}
boolean isAbstract = method.hasModifierProperty(PsiModifier.ABSTRACT);
PsiModifierList modifierList = method.getModifierList();
ModifierListGenerator.writeModifiers(text, modifierList, STUB_MODIFIERS, false);
if (method.hasTypeParameters()) {
GenerationUtil.writeTypeParameters(text, method, classNameProvider);
text.append(' ');
}
//append return type
PsiType retType = method.getReturnType();
if (retType == null) {
retType = TypesUtil.getJavaLangObject(method);
}
if (!method.hasModifierProperty(PsiModifier.STATIC)) {
final List<MethodSignatureBackedByPsiMethod> superSignatures = method.findSuperMethodSignaturesIncludingStatic(true);
for (MethodSignatureBackedByPsiMethod superSignature : superSignatures) {
final PsiType superType = superSignature.getSubstitutor().substitute(superSignature.getMethod().getReturnType());
if (superType != null &&
!superType.isAssignableFrom(retType) &&
!(PsiUtil.resolveClassInType(superType) instanceof PsiTypeParameter)) {
retType = superType;
}
}
}
TypeWriter.writeType(text, retType, method, classNameProvider);
text.append(' ');
text.append(name);
GenerationUtil.writeParameterList(text, method.getParameterList().getParameters(), classNameProvider, null);
writeThrowsList(text, method);
if (!isAbstract && !method.hasModifierProperty(PsiModifier.NATIVE)) {
/************* body **********/
text.append("{\nreturn ");
text.append(GroovyToJavaGenerator.getDefaultValueText(retType.getCanonicalText()));
text.append(";\n}");
}
else {
text.append(';');
}
text.append('\n');
}
private void writeThrowsList(StringBuilder text, PsiMethod method) {
final PsiReferenceList throwsList = method.getThrowsList();
final PsiClassType[] exceptions = throwsList.getReferencedTypes();
GenerationUtil.writeThrowsList(text, throwsList, exceptions, classNameProvider);
}
@Nullable
private static GroovyResolveResult resolveChainingConstructor(GrMethod constructor) {
LOG.assertTrue(constructor.isConstructor());
final GrConstructorInvocation constructorInvocation = PsiImplUtil.getChainingConstructorInvocation(constructor);
if (constructorInvocation == null) {
return null;
}
GroovyResolveResult resolveResult = constructorInvocation.advancedResolve();
if (resolveResult.getElement() != null) {
return resolveResult;
}
final GroovyResolveResult[] results = constructorInvocation.multiResolve(false);
if (results.length > 0) {
int i = 0;
while (results.length > i + 1) {
final PsiMethod candidate = (PsiMethod)results[i].getElement();
final PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(constructor.getProject()).getResolveHelper();
if (candidate != null && candidate != constructor && resolveHelper.isAccessible(candidate, constructorInvocation, null)) {
break;
}
i++;
}
return results[i];
}
return null;
}
@Override
public Collection<PsiMethod> collectMethods(PsiClass typeDefinition) {
List<PsiMethod> methods = new ArrayList<PsiMethod>();
for (PsiMethod method : typeDefinition.getMethods()) {
if (method instanceof DelegatedMethod) {
PsiMethod prototype = ((DelegatedMethod)method).getPrototype();
PsiClass aClass = prototype.getContainingClass();
if (prototype.hasModifierProperty(PsiModifier.FINAL) && aClass != null && typeDefinition.isInheritor(aClass, true)) {
continue; //skip final super methods
}
}
methods.add(method);
}
boolean isClass = !typeDefinition.isInterface() &&
!typeDefinition.isAnnotationType() &&
!typeDefinition.isEnum() &&
!(typeDefinition instanceof GroovyScriptClass);
if (isClass) {
final Collection<MethodSignature> toOverride = OverrideImplementExploreUtil.getMethodSignaturesToOverride(typeDefinition);
for (MethodSignature signature : toOverride) {
if (!(signature instanceof MethodSignatureBackedByPsiMethod)) continue;
final PsiMethod method = ((MethodSignatureBackedByPsiMethod)signature).getMethod();
final PsiClass baseClass = method.getContainingClass();
if (baseClass == null) continue;
final String qname = baseClass.getQualifiedName();
if (GroovyCommonClassNames.DEFAULT_BASE_CLASS_NAME.equals(qname) || GroovyCommonClassNames.GROOVY_OBJECT_SUPPORT.equals(qname) ||
method.hasModifierProperty(PsiModifier.ABSTRACT) && typeDefinition.isInheritor(baseClass, true)) {
if (method.isConstructor()) continue;
methods.add(mirrorMethod(typeDefinition, method, baseClass, signature.getSubstitutor()));
}
}
final Collection<MethodSignature> toImplement = OverrideImplementExploreUtil.getMethodSignaturesToImplement(typeDefinition);
for (MethodSignature signature : toImplement) {
if (!(signature instanceof MethodSignatureBackedByPsiMethod)) continue;
final PsiMethod resolved = ((MethodSignatureBackedByPsiMethod)signature).getMethod();
final PsiClass baseClass = resolved.getContainingClass();
if (baseClass == null) continue;
if (!GroovyCommonClassNames.DEFAULT_BASE_CLASS_NAME.equals(baseClass.getQualifiedName())) continue;
methods.add(mirrorMethod(typeDefinition, resolved, baseClass, signature.getSubstitutor()));
}
}
return methods;
}
@Override
public boolean generateAnnotations() {
return false;
}
@Override
public void writePostponed(StringBuilder text, PsiClass psiClass) {
}
private static LightMethodBuilder mirrorMethod(PsiClass typeDefinition,
PsiMethod method,
PsiClass baseClass,
PsiSubstitutor substitutor) {
final LightMethodBuilder builder = new LightMethodBuilder(method.getManager(), method.getName());
substitutor = substitutor.putAll(TypeConversionUtil.getSuperClassSubstitutor(baseClass, typeDefinition, PsiSubstitutor.EMPTY));
for (PsiParameter parameter : method.getParameterList().getParameters()) {
builder.addParameter(StringUtil.notNullize(parameter.getName()), substitutor.substitute(parameter.getType()));
}
builder.setMethodReturnType(substitutor.substitute(method.getReturnType()));
for (String modifier : STUB_MODIFIERS) {
if (method.hasModifierProperty(modifier)) {
builder.addModifier(modifier);
}
}
return builder;
}
@Override
public void writeVariableDeclarations(StringBuilder text, GrVariableDeclaration variableDeclaration) {
GrTypeElement typeElement = variableDeclaration.getTypeElementGroovy();
final GrModifierList modifierList = variableDeclaration.getModifierList();
final PsiNameHelper nameHelper = PsiNameHelper.getInstance(variableDeclaration.getProject());
for (final GrVariable variable : variableDeclaration.getVariables()) {
String name = variable.getName();
if (!nameHelper.isIdentifier(name)) {
continue; //does not have a java image
}
ModifierListGenerator.writeModifiers(text, modifierList, STUB_FIELD_MODIFIERS, false);
//type
PsiType declaredType =
typeElement == null ? PsiType.getJavaLangObject(variable.getManager(), variable.getResolveScope()) : typeElement.getType();
TypeWriter.writeType(text, declaredType, variableDeclaration, classNameProvider);
text.append(' ').append(name).append(" = ").append(getVariableInitializer(variable, declaredType));
text.append(";\n");
}
}
private static String getVariableInitializer(GrVariable variable, PsiType declaredType) {
if (declaredType instanceof PsiPrimitiveType) {
Object eval = GroovyConstantExpressionEvaluator.evaluate(variable.getInitializerGroovy());
if (eval instanceof Float ||
PsiType.FLOAT.equals(TypesUtil.unboxPrimitiveTypeWrapper(variable.getType())) && eval instanceof Number) {
return eval.toString() + "f";
}
else if (eval instanceof Character) {
StringBuilder buffer = new StringBuilder();
buffer.append('\'');
StringUtil.escapeStringCharacters(1, Character.toString(((Character)eval).charValue()), buffer);
buffer.append('\'');
return buffer.toString();
}
if (eval instanceof Number || eval instanceof Boolean) {
return eval.toString();
}
}
return GroovyToJavaGenerator.getDefaultValueText(declaredType.getCanonicalText());
}
@Override
public void writeImplementsList(StringBuilder text, PsiClass typeDefinition) {
final Collection<PsiClassType> implementsTypes = new LinkedHashSet<PsiClassType>();
Collections.addAll(implementsTypes, typeDefinition.getImplementsListTypes());
if (implementsTypes.isEmpty()) return;
text.append(typeDefinition.isInterface() ? "extends " : "implements ");
for (PsiClassType implementsType : implementsTypes) {
TypeWriter.writeType(text, implementsType, typeDefinition, classNameProvider);
text.append(", ");
}
if (!implementsTypes.isEmpty()) text.delete(text.length() - 2, text.length());
text.append(' ');
}
@Override
public void writeExtendsList(StringBuilder text, PsiClass typeDefinition) {
final PsiClassType[] extendsClassesTypes = typeDefinition.getExtendsListTypes();
if (extendsClassesTypes.length > 0) {
text.append("extends ");
TypeWriter.writeType(text, extendsClassesTypes[0], typeDefinition, classNameProvider);
text.append(' ');
}
}
}