| /* |
| * 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.google.common.collect.ImmutableSortedSet; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.psi.*; |
| import com.intellij.psi.impl.light.LightMethodBuilder; |
| import com.intellij.util.containers.ContainerUtil; |
| import com.intellij.util.containers.HashSet; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.jetbrains.plugins.groovy.codeInspection.noReturnMethod.MissingReturnInspection; |
| import org.jetbrains.plugins.groovy.codeInspection.utils.ControlFlowUtils; |
| import org.jetbrains.plugins.groovy.lang.psi.GroovyFile; |
| import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement; |
| import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory; |
| import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult; |
| import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.modifiers.annotation.GrAnnotationMemberValue; |
| import org.jetbrains.plugins.groovy.lang.psi.api.signatures.GrClosureSignature; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrStatement; |
| 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.arguments.GrArgumentList; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameter; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrEnumConstantInitializer; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrReferenceList; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.*; |
| import org.jetbrains.plugins.groovy.lang.psi.api.types.GrCodeReferenceElement; |
| import org.jetbrains.plugins.groovy.lang.psi.impl.signatures.GrClosureSignatureUtil; |
| import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GroovyScriptClass; |
| import org.jetbrains.plugins.groovy.lang.psi.util.GroovyCommonClassNames; |
| |
| import java.util.*; |
| |
| import static org.jetbrains.plugins.groovy.refactoring.convertToJava.GenerationUtil.writeTypeParameters; |
| import static org.jetbrains.plugins.groovy.refactoring.convertToJava.TypeWriter.writeType; |
| |
| /** |
| * @author Maxim.Medvedev |
| */ |
| public class ClassItemGeneratorImpl implements ClassItemGenerator { |
| private static final Logger LOG = Logger.getInstance("#org.jetbrains.plugins.groovy.refactoring.convertToJava.ClassItemGeneratorImpl"); |
| |
| private final ClassNameProvider classNameProvider; |
| private final ExpressionContext context; |
| |
| public ClassItemGeneratorImpl(@NotNull ExpressionContext context) { |
| classNameProvider = new GeneratorClassNameProvider(); |
| this.context = context; |
| } |
| |
| @Override |
| public void writeEnumConstant(StringBuilder builder, GrEnumConstant constant) { |
| GenerationUtil.writeDocComment(builder, constant, false); |
| builder.append(constant.getName()); |
| |
| final GrArgumentList argumentList = constant.getArgumentList(); |
| if (argumentList != null) { |
| final GroovyResolveResult resolveResult = constant.advancedResolve(); |
| GrClosureSignature signature = GrClosureSignatureUtil.createSignature(resolveResult); |
| new ArgumentListGenerator(builder, context.extend()).generate( |
| signature, |
| argumentList.getExpressionArguments(), |
| argumentList.getNamedArguments(), |
| GrClosableBlock.EMPTY_ARRAY, |
| constant |
| ); |
| } |
| |
| final GrEnumConstantInitializer anonymousBlock = constant.getInitializingClass(); |
| if (anonymousBlock != null) { |
| builder.append("{\n"); |
| new ClassGenerator(classNameProvider, this).writeMembers(builder, anonymousBlock); |
| builder.append("\n}"); |
| } |
| } |
| |
| @Override |
| public void writeConstructor(StringBuilder text, PsiMethod constructor, boolean isEnum) { |
| writeMethod(text, constructor); |
| } |
| |
| @Override |
| public void writeMethod(StringBuilder builder, PsiMethod method) { |
| if (method == null) return; |
| |
| GenerationUtil.writeDocComment(builder, method, true); |
| |
| String name = method.getName(); |
| |
| boolean isAbstract = method.hasModifierProperty(PsiModifier.ABSTRACT); |
| |
| PsiModifierList modifierList = method.getModifierList(); |
| |
| final PsiClass containingClass = method.getContainingClass(); |
| if (method.isConstructor() && containingClass != null && containingClass.isEnum()) { |
| ModifierListGenerator.writeModifiers(builder, modifierList, ModifierListGenerator.ENUM_CONSTRUCTOR_MODIFIERS); |
| } |
| else { |
| ModifierListGenerator.writeModifiers(builder, modifierList); |
| } |
| |
| |
| if (method.hasTypeParameters()) { |
| writeTypeParameters(builder, method, classNameProvider); |
| builder.append(' '); |
| } |
| |
| //append return type |
| if (!method.isConstructor()) { |
| PsiType retType = context.typeProvider.getReturnType(method); |
| writeType(builder, retType, method, classNameProvider); |
| builder.append(' '); |
| } |
| builder.append(name); |
| |
| if (method instanceof GroovyPsiElement) { |
| context.searchForLocalVarsToWrap((GroovyPsiElement)method); |
| } |
| GenerationUtil.writeParameterList(builder, method.getParameterList().getParameters(), classNameProvider, context); |
| |
| if (method instanceof GrAnnotationMethod) { |
| GrAnnotationMemberValue defaultValue = ((GrAnnotationMethod)method).getDefaultValue(); |
| if (defaultValue != null) { |
| builder.append("default "); |
| defaultValue.accept(new AnnotationGenerator(builder, context)); |
| } |
| } |
| |
| |
| GenerationUtil.writeThrowsList(builder, method.getThrowsList(), getMethodExceptions(method), classNameProvider); |
| |
| if (!isAbstract) { |
| /************* body **********/ |
| if (method instanceof GrMethod) { |
| if (method instanceof GrReflectedMethod && ((GrReflectedMethod)method).getSkippedParameters().length > 0) { |
| builder.append("{\n").append(generateDelegateCall((GrReflectedMethod)method)).append("\n}\n"); |
| } |
| else { |
| new CodeBlockGenerator(builder, context.extend()).generateMethodBody((GrMethod)method); |
| } |
| } |
| else if (method instanceof GrAccessorMethod) { |
| writeAccessorBody(builder, method); |
| } |
| else if (method instanceof LightMethodBuilder && containingClass instanceof GroovyScriptClass) { |
| if ("main".equals(method.getName())) { |
| writeMainScriptMethodBody(builder, method); |
| } |
| else if ("run".equals(method.getName())) { |
| writeRunScriptMethodBody(builder, method); |
| } |
| } |
| else { |
| builder.append("{//todo\n}"); |
| } |
| } |
| else { |
| builder.append(';'); |
| } |
| } |
| |
| private StringBuilder generateDelegateCall(GrReflectedMethod method) { |
| final GrParameter[] actualParams = method.getParameterList().getParameters(); |
| |
| final GrParameter[] parameters = method.getBaseMethod().getParameters(); |
| |
| Set<String> actual = new HashSet<String>(actualParams.length); |
| for (GrParameter param : actualParams) { |
| actual.add(param.getName()); |
| } |
| |
| StringBuilder builder = new StringBuilder(); |
| if (method.isConstructor()) { |
| builder.append("this"); |
| } |
| else { |
| if (context.typeProvider.getReturnType(method) != PsiType.VOID) { |
| builder.append("return "); |
| } |
| builder.append(method.getName()); |
| } |
| builder.append('('); |
| for (GrParameter parameter : parameters) { |
| if (actual.contains(parameter.getName())) { |
| builder.append(parameter.getName()); |
| } |
| else { |
| LOG.assertTrue(parameter.isOptional()); |
| final GrExpression initializer = parameter.getInitializerGroovy(); |
| LOG.assertTrue(initializer != null); |
| builder.append(initializer.getText()); |
| } |
| builder.append(", "); |
| } |
| builder.delete(builder.length()-2, builder.length()); |
| //builder.removeFromTheEnd(2); |
| builder.append(')'); |
| final GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(context.project); |
| final GrStatement delegateCall; |
| |
| if (method.isConstructor()) { |
| delegateCall = factory.createConstructorInvocation(builder.toString(), method); |
| } |
| else { |
| delegateCall = factory.createStatementFromText(builder.toString(), method); |
| } |
| |
| final StringBuilder result = new StringBuilder(); |
| delegateCall.accept(new CodeBlockGenerator(result, this.context.extend())); |
| return result; |
| } |
| |
| @SuppressWarnings({"MethodMayBeStatic"}) |
| private void writeMainScriptMethodBody(StringBuilder builder, PsiMethod method) { |
| final PsiClass containingClass = method.getContainingClass(); |
| LOG.assertTrue(containingClass instanceof GroovyScriptClass); |
| final PsiParameter[] parameters = method.getParameterList().getParameters(); |
| LOG.assertTrue(parameters.length == 1); |
| builder.append("{\nnew ").append(containingClass.getQualifiedName()).append("(new groovy.lang.Binding(").append(parameters[0].getName()) |
| .append(")).run();\n}\n"); |
| } |
| |
| private void writeRunScriptMethodBody(StringBuilder builder, PsiMethod method) { |
| builder.append("{\n"); |
| final PsiClass containingClass = method.getContainingClass(); |
| LOG.assertTrue(containingClass instanceof GroovyScriptClass); |
| final PsiFile scriptFile = containingClass.getContainingFile(); |
| LOG.assertTrue(scriptFile instanceof GroovyFile); |
| LOG.assertTrue(((GroovyFile)scriptFile).isScript()); |
| final List<GrStatement> exitPoints = ControlFlowUtils.collectReturns(scriptFile); |
| |
| |
| ExpressionContext extended = context.extend(); |
| extended.searchForLocalVarsToWrap((GroovyPsiElement)scriptFile); |
| new CodeBlockGenerator(builder, extended, exitPoints) |
| .visitStatementOwner((GroovyFile)scriptFile, MissingReturnInspection |
| .methodMissesSomeReturns((GroovyFile)scriptFile, MissingReturnInspection.ReturnStatus.mustReturnValue)); |
| builder.append("\n}\n"); |
| } |
| |
| private static void writeAccessorBody(StringBuilder builder, PsiMethod method) { |
| final String propName = ((GrAccessorMethod)method).getProperty().getName(); |
| if (((GrAccessorMethod)method).isSetter()) { |
| final String paramName = method.getParameterList().getParameters()[0].getName(); |
| builder.append("{\n"); |
| if (method.hasModifierProperty(PsiModifier.STATIC)) { |
| PsiClass containingClass = method.getContainingClass(); |
| if (containingClass != null) { |
| builder.append(containingClass.getName()); |
| builder.append('.'); |
| } |
| } |
| else { |
| builder.append("this."); |
| } |
| builder.append(propName); |
| builder.append(" = "); |
| builder.append(paramName); |
| builder.append(";\n}"); |
| } |
| else { |
| builder.append("{\n return "); |
| builder.append(propName); |
| builder.append(";\n}"); |
| } |
| } |
| |
| @SuppressWarnings({"MethodMayBeStatic"}) |
| private PsiClassType[] getMethodExceptions(PsiMethod method) { |
| return method.getThrowsList().getReferencedTypes(); |
| |
| //todo find method exceptions! |
| } |
| |
| @Override |
| public void writeVariableDeclarations(StringBuilder mainBuilder, GrVariableDeclaration variableDeclaration) { |
| ExpressionContext extended = context.extend(); |
| GrVariable[] variables = variableDeclaration.getVariables(); |
| |
| if (variables.length > 0 && variables[0] instanceof PsiField) { |
| GenerationUtil.writeDocComment(mainBuilder, ((PsiField)variables[0]), true); |
| } |
| |
| StringBuilder builder = new StringBuilder(); |
| StringBuilder initBuilder = new StringBuilder(); |
| initBuilder.append("{\n"); |
| for (GrVariable variable : variables) { |
| PsiType type = extended.typeProvider.getVarType(variable); |
| ModifierListGenerator.writeModifiers(builder, variable.getModifierList()); |
| |
| writeType(builder, type, variable); |
| builder.append(' '); |
| |
| builder.append(variable.getName()); |
| final GrExpression initializer = variable.getInitializerGroovy(); |
| |
| if (initializer != null) { |
| int count = extended.myStatements.size(); |
| StringBuilder initializerBuilder = new StringBuilder(); |
| extended.searchForLocalVarsToWrap(initializer); |
| initializer.accept(new ExpressionGenerator(initializerBuilder, extended)); |
| if (extended.myStatements.size() == count) { //didn't use extra statements |
| builder.append(" = ").append(initializerBuilder); |
| } |
| else { |
| StringBuilder assignment = new StringBuilder().append(variable.getName()).append(" = ").append(initializerBuilder).append(';'); |
| GenerationUtil.writeStatement(initBuilder, assignment, null, extended); |
| } |
| } |
| |
| builder.append(";\n"); |
| } |
| |
| if (extended.myStatements.size()>0) { |
| initBuilder.append("}\n"); |
| mainBuilder.append(initBuilder); |
| } |
| |
| mainBuilder.append(builder); |
| } |
| |
| @Override |
| public Collection<PsiMethod> collectMethods(PsiClass typeDefinition) { |
| List<PsiMethod> result = new ArrayList<PsiMethod>(Arrays.asList(typeDefinition.getMethods())); |
| |
| if (typeDefinition instanceof GroovyScriptClass) { |
| final GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(context.project); |
| final String name = typeDefinition.getName(); |
| GrTypeDefinition tempClass = factory.createTypeDefinition("class " + name + " extends groovy.lang.Script {\n" + |
| " def " + name + "(groovy.lang.Binding binding){\n" + |
| " super(binding);\n" + |
| " }\n" + |
| " def " + name + "(){\n" + |
| " super();\n" + |
| " }\n" + |
| "}"); |
| ContainerUtil.addAll(result, tempClass.getCodeConstructors()); |
| } |
| return result; |
| } |
| |
| @Override |
| public boolean generateAnnotations() { |
| return true; |
| } |
| |
| @Override |
| public void writePostponed(StringBuilder builder, PsiClass psiClass) { |
| if (psiClass.getContainingClass() != null) return; |
| if (psiClass instanceof PsiAnonymousClass) return; |
| |
| Map<PsiMethod, String> setters = context.getSetters(); |
| Set<Map.Entry<PsiMethod, String>> entries = setters.entrySet(); |
| if (ApplicationManager.getApplication().isUnitTestMode()) { |
| entries = ImmutableSortedSet.copyOf(new Comparator<Map.Entry<PsiMethod, String>>() { |
| @Override |
| public int compare(Map.Entry<PsiMethod, String> o1, Map.Entry<PsiMethod, String> o2) { |
| return o1.getValue().compareTo(o2.getValue()); |
| } |
| }, entries); |
| } |
| for (Map.Entry<PsiMethod, String> entry : entries) { |
| new SetterWriter(builder, psiClass, entry.getKey(), entry.getValue(), classNameProvider, context).write(); |
| } |
| |
| final String name = context.getRefSetterName(); |
| if (name != null) { |
| builder.append("private static <T> T ").append(name). |
| append("(groovy.lang.Reference<T> ref, T newValue) {\nref.set(newValue);\nreturn newValue;\n}"); |
| } |
| } |
| |
| |
| |
| public void writeImplementsList(StringBuilder text, PsiClass typeDefinition) { |
| final Collection<PsiClassType> implementsTypes = new LinkedHashSet<PsiClassType>(); |
| Collections.addAll(implementsTypes, typeDefinition.getImplementsListTypes()); |
| |
| if (implementsTypes.isEmpty()) return; |
| if (implementsTypes.size() == 1 && shouldSkipInImplements(typeDefinition, implementsTypes.iterator().next())) return; |
| |
| text.append(typeDefinition.isInterface() ? "extends " : "implements "); |
| for (PsiClassType implementsType : implementsTypes) { |
| if (shouldSkipInImplements(typeDefinition, implementsType)) { |
| continue; |
| } |
| writeType(text, implementsType, typeDefinition, classNameProvider); |
| text.append(", "); |
| } |
| if (implementsTypes.size() > 0) text.delete(text.length() - 2, text.length()); |
| text.append(' '); |
| } |
| |
| private static boolean shouldSkipInImplements(PsiClass typeDefinition, PsiClassType implementsType) { |
| return implementsType.equalsToText(GroovyCommonClassNames.GROOVY_OBJECT) && |
| typeDefinition instanceof GrTypeDefinition && |
| !typeDefinition.isInterface() && |
| !GenerationSettings.implementGroovyObjectAlways && |
| !isInList(implementsType, ((GrTypeDefinition)typeDefinition).getImplementsClause()) && |
| !containsMethodsOf((GrTypeDefinition)typeDefinition, GroovyCommonClassNames.GROOVY_OBJECT); |
| } |
| |
| public void writeExtendsList(StringBuilder text, PsiClass typeDefinition) { |
| final PsiClassType[] extendsClassesTypes = typeDefinition.getExtendsListTypes(); |
| |
| if (extendsClassesTypes.length > 0) { |
| PsiClassType type = extendsClassesTypes[0]; |
| |
| if (type.equalsToText(GroovyCommonClassNames.GROOVY_OBJECT_SUPPORT) && |
| typeDefinition instanceof GrTypeDefinition && |
| !GenerationSettings.implementGroovyObjectAlways && |
| !isInList(type, ((GrTypeDefinition)typeDefinition).getExtendsClause()) && |
| !containsMethodsOf((GrTypeDefinition)typeDefinition, GroovyCommonClassNames.GROOVY_OBJECT)) { |
| return; |
| } |
| |
| text.append("extends "); |
| writeType(text, type, typeDefinition, classNameProvider); |
| text.append(' '); |
| } |
| } |
| |
| private static boolean isInList(@NotNull PsiClassType type, @Nullable GrReferenceList list) { |
| if (list == null) return false; |
| |
| PsiClass resolved = type.resolve(); |
| if (resolved == null) return true; |
| |
| PsiManager manager = list.getManager(); |
| GrCodeReferenceElement[] elements = list.getReferenceElementsGroovy(); |
| for (GrCodeReferenceElement element : elements) { |
| if (manager.areElementsEquivalent(resolved, element.resolve())) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private static boolean containsMethodsOf(@NotNull GrTypeDefinition aClass, @NotNull final String fqn) { |
| PsiClass classToSearch = JavaPsiFacade.getInstance(aClass.getProject()).findClass(fqn, aClass.getResolveScope()); |
| if (classToSearch == null) return true; |
| |
| Set<String> methodsToFind = new HashSet<String>(); |
| for (PsiMethod method : classToSearch.getMethods()) { |
| methodsToFind.add(method.getName()); |
| } |
| |
| for (GrMethod method : aClass.getCodeMethods()) { |
| if (!methodsToFind.contains(method.getName())) continue; |
| |
| for (HierarchicalMethodSignature superSignature : method.getHierarchicalMethodSignature().getSuperSignatures()) { |
| PsiClass superClass = superSignature.getMethod().getContainingClass(); |
| if (superClass != null && fqn.equals(superClass.getQualifiedName())) return true; |
| } |
| } |
| |
| return false; |
| } |
| } |