| /* |
| * 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.codeInsight.daemon.impl.quickfix; |
| |
| import com.intellij.codeInsight.FileModificationService; |
| import com.intellij.codeInsight.NullableNotNullManager; |
| import com.intellij.codeInsight.daemon.QuickFixBundle; |
| import com.intellij.codeInsight.daemon.impl.analysis.JavaHighlightUtil; |
| import com.intellij.codeInsight.generation.PsiElementClassMember; |
| import com.intellij.codeInsight.generation.PsiFieldMember; |
| import com.intellij.codeInsight.generation.PsiMethodMember; |
| import com.intellij.codeInsight.intention.IntentionAction; |
| import com.intellij.codeInsight.intention.impl.AssignFieldFromParameterAction; |
| import com.intellij.codeInsight.intention.impl.FieldFromParameterUtils; |
| import com.intellij.codeInspection.ex.GlobalInspectionContextBase; |
| import com.intellij.ide.util.MemberChooser; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.editor.Editor; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.ui.DialogWrapper; |
| import com.intellij.openapi.util.Comparing; |
| import com.intellij.openapi.util.Computable; |
| import com.intellij.openapi.util.Key; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.psi.*; |
| import com.intellij.psi.codeStyle.JavaCodeStyleManager; |
| import com.intellij.psi.codeStyle.SuggestedNameInfo; |
| import com.intellij.psi.codeStyle.VariableKind; |
| import com.intellij.psi.search.LocalSearchScope; |
| import com.intellij.psi.search.searches.ReferencesSearch; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.psi.util.PsiUtil; |
| import com.intellij.refactoring.changeSignature.ParameterInfoImpl; |
| import com.intellij.refactoring.util.RefactoringUtil; |
| import com.intellij.util.Function; |
| import com.intellij.util.IncorrectOperationException; |
| import com.intellij.util.containers.ConcurrentWeakHashMap; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.*; |
| |
| public class CreateConstructorParameterFromFieldFix implements IntentionAction { |
| private static final Key<Map<SmartPsiElementPointer<PsiField>, Boolean>> FIELDS = Key.create("CONSTRUCTOR_PARAMS"); |
| |
| private final SmartPsiElementPointer<PsiField> myField; |
| private final PsiClass myClass; |
| |
| public CreateConstructorParameterFromFieldFix(@NotNull PsiField field) { |
| myClass = field.getContainingClass(); |
| myField = SmartPointerManager.getInstance(field.getProject()).createSmartPsiElementPointer(field); |
| if (myClass != null) { |
| getFieldsToFix().add(myField); |
| } |
| } |
| |
| @Override |
| @NotNull |
| public String getText() { |
| if (getFieldsToFix().size() > 1 && myClass.getConstructors().length <= 1) return "Add constructor parameters"; |
| return QuickFixBundle.message("add.constructor.parameter.name"); |
| } |
| |
| @Override |
| @NotNull |
| public String getFamilyName() { |
| return getText(); |
| } |
| |
| @Override |
| public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { |
| return isAvailable(getField()); |
| } |
| |
| private static boolean isAvailable(PsiField field) { |
| PsiClass containingClass = field == null ? null : field.getContainingClass(); |
| return field != null |
| && field.getManager().isInProject(field) |
| && !field.hasModifierProperty(PsiModifier.STATIC) |
| && containingClass != null |
| && !(containingClass instanceof PsiSyntheticClass) |
| && containingClass.getName() != null; |
| } |
| |
| @Override |
| public void invoke(@NotNull final Project project, final Editor editor, final PsiFile file) throws IncorrectOperationException { |
| if (!FileModificationService.getInstance().prepareFileForWrite(file)) return; |
| |
| PsiMethod[] constructors = myClass.getConstructors(); |
| if (constructors.length == 0) { |
| final AddDefaultConstructorFix defaultConstructorFix = new AddDefaultConstructorFix(myClass); |
| ApplicationManager.getApplication().runWriteAction(new Runnable() { |
| @Override |
| public void run() { |
| defaultConstructorFix.invoke(project, editor, file); |
| } |
| }); |
| constructors = myClass.getConstructors(); |
| } |
| Arrays.sort(constructors, new Comparator<PsiMethod>() { |
| @Override |
| public int compare(PsiMethod c1, PsiMethod c2) { |
| final PsiMethod cc1 = RefactoringUtil.getChainedConstructor(c1); |
| final PsiMethod cc2 = RefactoringUtil.getChainedConstructor(c2); |
| if (cc1 == c2) return 1; |
| if (cc2 == c1) return -1; |
| if (cc1 == null) { |
| return cc2 == null ? 0 : compare(c1, cc2); |
| } else { |
| return cc2 == null ? compare(cc1, c2) : compare(cc1, cc2); |
| } |
| } |
| }); |
| final List<PsiElement> cleanupElements = new ArrayList<PsiElement>(); |
| final ArrayList<PsiMethod> constrs = filterConstructorsIfFieldAlreadyAssigned(constructors, getField()); |
| if (constrs.size() > 1) { |
| final PsiMethodMember[] members = new PsiMethodMember[constrs.size()]; |
| int i = 0; |
| for (PsiMethod constructor : constrs) { |
| members[i++] = new PsiMethodMember(constructor); |
| } |
| final List<PsiMethodMember> elements; |
| if (ApplicationManager.getApplication().isUnitTestMode()) { |
| elements = Arrays.asList(members); |
| } else { |
| final MemberChooser<PsiMethodMember> chooser = new MemberChooser<PsiMethodMember>(members, false, true, project); |
| chooser.setTitle("Choose constructors to add parameter to"); |
| chooser.show(); |
| elements = chooser.getSelectedElements(); |
| if (elements == null) return; |
| } |
| |
| for (PsiMethodMember member : elements) { |
| if (!addParameterToConstructor(project, file, editor, member.getElement(), new PsiField[] {getField()}, cleanupElements)) break; |
| } |
| |
| } else if (!constrs.isEmpty()) { |
| final Collection<SmartPsiElementPointer<PsiField>> fieldsToFix = getFieldsToFix(); |
| try { |
| final PsiMethod constructor = constrs.get(0); |
| final LinkedHashSet<PsiField> fields = new LinkedHashSet<PsiField>(); |
| getFieldsToFix().add(myField); |
| for (SmartPsiElementPointer<PsiField> elementPointer : fieldsToFix) { |
| final PsiField field = elementPointer.getElement(); |
| if (field != null && isAvailable(field) && filterConstructorsIfFieldAlreadyAssigned(new PsiMethod[]{constructor}, field).contains(constructor)) { |
| fields.add(field); |
| } |
| } |
| if (constrs.size() == constructors.length && fields.size() > 1 && !ApplicationManager.getApplication().isUnitTestMode()) { |
| PsiFieldMember[] members = new PsiFieldMember[fields.size()]; |
| int i = 0; |
| for (PsiField field : fields) { |
| members[i++] = new PsiFieldMember(field); |
| } |
| MemberChooser<PsiElementClassMember> chooser = new MemberChooser<PsiElementClassMember>(members, false, true, project); |
| chooser.setTitle("Choose Fields to Generate Constructor Parameters for"); |
| chooser.show(); |
| if (chooser.getExitCode() != DialogWrapper.OK_EXIT_CODE) return; |
| final List<PsiElementClassMember> selectedElements = chooser.getSelectedElements(); |
| if (selectedElements == null) return; |
| fields.clear(); |
| for (PsiElementClassMember member : selectedElements) { |
| fields.add((PsiField)member.getElement()); |
| } |
| } |
| |
| addParameterToConstructor(project, file, editor, constructor, constrs.size() == constructors.length |
| ? fields.toArray(new PsiField[fields.size()]) |
| : new PsiField[]{getField()}, cleanupElements); |
| } |
| finally { |
| fieldsToFix.clear(); |
| } |
| } |
| GlobalInspectionContextBase.cleanupElements(project, null, cleanupElements.toArray(new PsiElement[cleanupElements.size()])); |
| } |
| |
| @NotNull |
| private Collection<SmartPsiElementPointer<PsiField>> getFieldsToFix() { |
| Map<SmartPsiElementPointer<PsiField>, Boolean> fields = myClass.getUserData(FIELDS); |
| if (fields == null) myClass.putUserData(FIELDS, fields = new ConcurrentWeakHashMap<SmartPsiElementPointer<PsiField>,Boolean>(1)); |
| final Map<SmartPsiElementPointer<PsiField>, Boolean> finalFields = fields; |
| return new AbstractCollection<SmartPsiElementPointer<PsiField>>() { |
| @Override |
| public boolean add(SmartPsiElementPointer<PsiField> psiVariable) { |
| PsiField field = psiVariable.getElement(); |
| if (field == null || !isAvailable(field)) return false; |
| return finalFields.put(psiVariable, Boolean.TRUE) == null; |
| } |
| |
| @NotNull |
| @Override |
| public Iterator<SmartPsiElementPointer<PsiField>> iterator() { |
| return finalFields.keySet().iterator(); |
| } |
| |
| @Override |
| public int size() { |
| return finalFields.size(); |
| } |
| |
| @Override |
| public void clear() { |
| finalFields.clear(); |
| } |
| }; |
| } |
| |
| private static ArrayList<PsiMethod> filterConstructorsIfFieldAlreadyAssigned(PsiMethod[] constructors, PsiField field) { |
| final ArrayList<PsiMethod> result = new ArrayList<PsiMethod>(Arrays.asList(constructors)); |
| for (PsiReference reference : ReferencesSearch.search(field, new LocalSearchScope(constructors))) { |
| final PsiElement element = reference.getElement(); |
| if (element instanceof PsiReferenceExpression && PsiUtil.isOnAssignmentLeftHand((PsiExpression)element)) { |
| result.remove(PsiTreeUtil.getParentOfType(element, PsiMethod.class)); |
| } |
| } |
| return result; |
| } |
| |
| private static boolean addParameterToConstructor(final Project project, |
| final PsiFile file, |
| final Editor editor, |
| final PsiMethod constructor, |
| final PsiField[] fields, final List<PsiElement> cleanupElements) throws IncorrectOperationException { |
| final PsiParameterList parameterList = constructor.getParameterList(); |
| final PsiParameter[] parameters = parameterList.getParameters(); |
| ParameterInfoImpl[] newParamInfos = new ParameterInfoImpl[parameters.length + fields.length]; |
| final List<PsiVariable> params = new ArrayList<PsiVariable>(Arrays.asList(parameters)); |
| Collections.addAll(params, fields); |
| Collections.sort(params, new FieldParameterComparator(parameterList)); |
| |
| int i = 0; |
| final HashMap<PsiField, String> usedFields = new HashMap<PsiField, String>(); |
| for (PsiVariable param : params) { |
| final PsiType paramType = param.getType(); |
| if (param instanceof PsiParameter) { |
| newParamInfos[i++] = new ParameterInfoImpl(parameterList.getParameterIndex((PsiParameter)param), param.getName(), paramType, param.getName()); |
| } else { |
| final String uniqueParameterName = getUniqueParameterName(parameters, param, usedFields); |
| usedFields.put((PsiField)param, uniqueParameterName); |
| newParamInfos[i++] = new ParameterInfoImpl(-1, uniqueParameterName, paramType, uniqueParameterName); |
| } |
| } |
| final SmartPointerManager manager = SmartPointerManager.getInstance(project); |
| final SmartPsiElementPointer constructorPointer = manager.createSmartPsiElementPointer(constructor); |
| |
| final PsiMethod fromText = JavaPsiFacade.getElementFactory(project).createMethodFromText(createDummyMethod(constructor, newParamInfos), |
| constructor); |
| final PsiClass containingClass = constructor.getContainingClass(); |
| if (containingClass == null) return false; |
| final int minUsagesNumber = containingClass.findMethodsBySignature(fromText, false).length > 0 ? 0 : 1; |
| final List<ParameterInfoImpl> parameterInfos = |
| ChangeMethodSignatureFromUsageFix.performChange(project, editor, file, constructor, minUsagesNumber, newParamInfos, true, true); |
| |
| final ParameterInfoImpl[] resultParams = parameterInfos != null ? parameterInfos.toArray(new ParameterInfoImpl[parameterInfos.size()]) : |
| newParamInfos; |
| return ApplicationManager.getApplication().runWriteAction(new Computable<Boolean>() { |
| @Override |
| public Boolean compute() { |
| return doCreate(project, editor, parameters, constructorPointer, resultParams, usedFields, cleanupElements); |
| } |
| }); |
| } |
| |
| private static String createDummyMethod(PsiMethod constructor, ParameterInfoImpl[] newParamInfos) { |
| return constructor.getName() + "(" + StringUtil.join(newParamInfos, new Function<ParameterInfoImpl, String>() { |
| @Override |
| public String fun(ParameterInfoImpl info) { |
| return info.getTypeText() + " " + info.getName(); |
| } |
| }, ", ") + "){}"; |
| } |
| |
| private static String getUniqueParameterName(PsiParameter[] parameters, PsiVariable variable, HashMap<PsiField, String> usedNames) { |
| final JavaCodeStyleManager styleManager = JavaCodeStyleManager.getInstance(variable.getProject()); |
| final SuggestedNameInfo nameInfo = styleManager |
| .suggestVariableName(VariableKind.PARAMETER, |
| styleManager.variableNameToPropertyName(variable.getName(), VariableKind.FIELD), |
| null, variable.getType()); |
| String newName = nameInfo.names[0]; |
| int n = 1; |
| while (true) { |
| if (isUnique(parameters, newName, usedNames)) { |
| break; |
| } |
| newName = nameInfo.names[0] + n++; |
| } |
| return newName; |
| } |
| |
| private static boolean isUnique(PsiParameter[] params, String newName, HashMap<PsiField, String> usedNames) { |
| if (usedNames.containsValue(newName)) return false; |
| for (PsiParameter parameter : params) { |
| if (Comparing.strEqual(parameter.getName(), newName)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private static boolean doCreate(Project project, Editor editor, PsiParameter[] parameters, SmartPsiElementPointer constructorPointer, |
| ParameterInfoImpl[] parameterInfos, HashMap<PsiField, String> fields, List<PsiElement> cleanupElements) { |
| PsiMethod constructor = (PsiMethod)constructorPointer.getElement(); |
| assert constructor != null; |
| PsiParameter[] newParameters = constructor.getParameterList().getParameters(); |
| if (newParameters == parameters) return false; //user must have canceled dialog |
| boolean created = false; |
| // do not introduce assignment in chanined constructor |
| if (JavaHighlightUtil.getChainedConstructors(constructor) == null) { |
| for (PsiField field : fields.keySet()) { |
| final String defaultParamName = fields.get(field); |
| PsiParameter parameter = findParamByName(defaultParamName, field.getType(), newParameters, parameterInfos); |
| if (parameter == null) { |
| continue; |
| } |
| notNull(project, field, parameter); |
| cleanupElements.add(parameter); |
| final PsiElement assignmentStatement = AssignFieldFromParameterAction.addFieldAssignmentStatement(project, field, parameter, editor); |
| if (assignmentStatement != null) { |
| cleanupElements.add(assignmentStatement); |
| } |
| created = true; |
| } |
| } |
| return created; |
| } |
| |
| private static void notNull(Project project, PsiField field, PsiParameter parameter) { |
| final String notNull = NullableNotNullManager.getInstance(field.getProject()).getNotNull(field); |
| if (notNull != null) { |
| final PsiAnnotation annotation = JavaPsiFacade.getElementFactory(project).createAnnotationFromText("@" + notNull, field); |
| parameter.getModifierList().addBefore(annotation, null); |
| } |
| } |
| |
| @Nullable |
| private static PsiParameter findParamByName(String newName, |
| PsiType type, |
| PsiParameter[] newParameters, |
| ParameterInfoImpl[] parameterInfos) { |
| for (PsiParameter newParameter : newParameters) { |
| if (Comparing.strEqual(newName, newParameter.getName())) { |
| return newParameter; |
| } |
| } |
| for (int i = 0; i < newParameters.length; i++) { |
| if (parameterInfos[i].getOldIndex() == -1) { |
| final PsiParameter parameter = newParameters[i]; |
| final PsiType paramType = parameterInfos[i].getTypeWrapper().getType(parameter, parameter.getManager()); |
| if (type.isAssignableFrom(paramType)){ |
| return parameter; |
| } |
| } |
| } |
| return null; |
| } |
| |
| private PsiField getField() { |
| return myField.getElement(); |
| } |
| |
| @Override |
| public boolean startInWriteAction() { |
| return false; |
| } |
| |
| private static class FieldParameterComparator implements Comparator<PsiVariable> { |
| private final PsiParameterList myParameterList; |
| |
| public FieldParameterComparator(PsiParameterList parameterList) { |
| myParameterList = parameterList; |
| } |
| |
| @Override |
| public int compare(PsiVariable o1, PsiVariable o2) { |
| |
| if (o1 instanceof PsiParameter && ((PsiParameter)o1).isVarArgs()) return 1; |
| if (o2 instanceof PsiParameter && ((PsiParameter)o2).isVarArgs()) return -1; |
| |
| if (o1 instanceof PsiField && o2 instanceof PsiField) { |
| return o1.getTextOffset() - o2.getTextOffset(); |
| } |
| if (o1 instanceof PsiParameter && o2 instanceof PsiParameter) { |
| return myParameterList.getParameterIndex((PsiParameter)o1) - myParameterList.getParameterIndex((PsiParameter)o2); |
| } |
| |
| if (o1 instanceof PsiField && o2 instanceof PsiParameter) { |
| final PsiField field = FieldFromParameterUtils.getParameterAssignedToField((PsiParameter)o2); |
| if (field == null) return 1; |
| return o1.getTextOffset() - field.getTextOffset(); |
| } |
| if (o1 instanceof PsiParameter && o2 instanceof PsiField) { |
| final PsiField field = FieldFromParameterUtils.getParameterAssignedToField((PsiParameter)o1); |
| if (field == null) return -1; |
| return field.getTextOffset() - o2.getTextOffset(); |
| } |
| |
| return 0; |
| } |
| } |
| } |