2 * Copyright 2000-2014 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.codeInsight.intention.impl;
18 import com.intellij.codeInsight.CodeInsightBundle;
19 import com.intellij.codeInsight.FileModificationService;
20 import com.intellij.codeInsight.intention.HighPriorityAction;
21 import com.intellij.ide.util.MemberChooser;
22 import com.intellij.lang.java.JavaLanguage;
23 import com.intellij.openapi.application.ApplicationManager;
24 import com.intellij.openapi.diagnostic.Logger;
25 import com.intellij.openapi.editor.Editor;
26 import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory;
27 import com.intellij.openapi.project.Project;
28 import com.intellij.openapi.ui.DialogWrapper;
29 import com.intellij.openapi.util.Key;
30 import com.intellij.psi.*;
31 import com.intellij.psi.codeStyle.*;
32 import com.intellij.psi.search.LocalSearchScope;
33 import com.intellij.psi.search.searches.ReferencesSearch;
34 import com.intellij.psi.util.PsiTreeUtil;
35 import com.intellij.psi.util.PsiUtil;
36 import com.intellij.util.IncorrectOperationException;
37 import com.intellij.util.containers.ContainerUtil;
38 import com.intellij.util.containers.MultiMap;
39 import org.jetbrains.annotations.NotNull;
40 import org.jetbrains.annotations.Nullable;
45 * @author Danila Ponomarenko
47 public class BindFieldsFromParametersAction extends BaseIntentionAction implements HighPriorityAction {
48 private static final Logger LOG = Logger.getInstance(CreateFieldFromParameterAction.class);
49 private static final Key<Map<SmartPsiElementPointer<PsiParameter>, Boolean>> PARAMS = Key.create("FIELDS_FROM_PARAMS");
51 private static final Object LOCK = new Object();
54 public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
55 PsiParameter psiParameter = FieldFromParameterUtils.findParameterAtCursor(file, editor);
56 PsiMethod method = findMethod(psiParameter, editor, file);
57 if (method == null) return false;
59 final List<PsiParameter> parameters = getAvailableParameters(method);
62 final Collection<SmartPsiElementPointer<PsiParameter>> params = getUnboundedParams(method);
64 for (PsiParameter parameter : parameters) {
65 params.add(SmartPointerManager.getInstance(project).createSmartPsiElementPointer(parameter));
67 if (params.isEmpty()) return false;
68 if (params.size() == 1 && psiParameter != null) return false;
69 if (psiParameter == null) {
70 psiParameter = params.iterator().next().getElement();
71 LOG.assertTrue(psiParameter != null);
74 setText(CodeInsightBundle.message("intention.bind.fields.from.parameters.text", method.isConstructor() ? "constructor" : "method"));
76 return isAvailable(psiParameter);
80 private static PsiMethod findMethod(@Nullable PsiParameter parameter, @NotNull Editor editor, @NotNull PsiFile file) {
81 if (parameter == null) {
82 final PsiElement elementAt = file.findElementAt(editor.getCaretModel().getOffset());
83 if (elementAt instanceof PsiIdentifier) {
84 final PsiElement parent = elementAt.getParent();
85 if (parent instanceof PsiMethod) {
86 return (PsiMethod)parent;
91 final PsiElement declarationScope = parameter.getDeclarationScope();
92 if (declarationScope instanceof PsiMethod) {
93 return (PsiMethod)declarationScope;
101 private static List<PsiParameter> getAvailableParameters(@NotNull PsiMethod method) {
102 final List<PsiParameter> parameters = new ArrayList<>();
103 for (PsiParameter parameter : method.getParameterList().getParameters()) {
104 if (isAvailable(parameter)) {
105 parameters.add(parameter);
111 private static boolean isAvailable(PsiParameter psiParameter) {
112 final PsiType type = FieldFromParameterUtils.getSubstitutedType(psiParameter);
113 final PsiClass targetClass = PsiTreeUtil.getParentOfType(psiParameter, PsiClass.class);
114 return FieldFromParameterUtils.isAvailable(psiParameter, type, targetClass) &&
115 psiParameter.getLanguage().isKindOf(JavaLanguage.INSTANCE);
119 private static Collection<SmartPsiElementPointer<PsiParameter>> getUnboundedParams(PsiMethod psiMethod) {
120 Map<SmartPsiElementPointer<PsiParameter>, Boolean> params = psiMethod.getUserData(PARAMS);
121 if (params == null) psiMethod.putUserData(PARAMS, params = ContainerUtil.createConcurrentWeakMap());
122 final Map<SmartPsiElementPointer<PsiParameter>, Boolean> finalParams = params;
123 return new AbstractCollection<SmartPsiElementPointer<PsiParameter>>() {
125 public boolean add(SmartPsiElementPointer<PsiParameter> psiVariable) {
126 return finalParams.put(psiVariable, Boolean.TRUE) == null;
130 public Iterator<SmartPsiElementPointer<PsiParameter>> iterator() {
131 return finalParams.keySet().iterator();
136 return finalParams.size();
140 public void clear() {
148 public String getFamilyName() {
149 return CodeInsightBundle.message("intention.bind.fields.from.parameters.family");
153 public void invoke(@NotNull Project project, Editor editor, PsiFile file) {
154 invoke(project, editor, file, !ApplicationManager.getApplication().isUnitTestMode());
157 private static void invoke(final Project project, Editor editor, PsiFile file, boolean isInteractive) {
158 PsiParameter psiParameter = FieldFromParameterUtils.findParameterAtCursor(file, editor);
159 if (!FileModificationService.getInstance().prepareFileForWrite(file)) return;
160 final PsiMethod method = psiParameter != null ? (PsiMethod)psiParameter.getDeclarationScope() : PsiTreeUtil.getParentOfType(file.findElementAt(editor.getCaretModel().getOffset()), PsiMethod.class);
161 LOG.assertTrue(method != null);
163 final HashSet<String> usedNames = new HashSet<>();
164 final Iterable<PsiParameter> parameters = selectParameters(project, method, copyUnboundedParamsAndClearOriginal(method), isInteractive);
165 final MultiMap<PsiType, PsiParameter> types = new MultiMap<>();
166 for (PsiParameter parameter : parameters) {
167 types.putValue(parameter.getType(), parameter);
169 final CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(project);
170 final boolean preferLongerNames = settings.PREFER_LONGER_NAMES;
171 for (PsiParameter selected : parameters) {
173 settings.PREFER_LONGER_NAMES = preferLongerNames || types.get(selected.getType()).size() > 1;
174 processParameter(project, selected, usedNames);
176 settings.PREFER_LONGER_NAMES = preferLongerNames;
182 private static Iterable<PsiParameter> selectParameters(@NotNull Project project,
183 @NotNull PsiMethod method,
184 @NotNull Collection<SmartPsiElementPointer<PsiParameter>> unboundedParams,
185 boolean isInteractive) {
186 if (unboundedParams.size() < 2 || !isInteractive) {
187 return revealPointers(unboundedParams);
190 final ParameterClassMember[] members = sortByParameterIndex(toClassMemberArray(unboundedParams), method);
192 final MemberChooser<ParameterClassMember> chooser = showChooser(project, method, members);
194 final List<ParameterClassMember> selectedElements = chooser.getSelectedElements();
195 if (chooser.getExitCode() != DialogWrapper.OK_EXIT_CODE || selectedElements == null) {
196 return Collections.emptyList();
199 return revealParameterClassMembers(selectedElements);
203 private static MemberChooser<ParameterClassMember> showChooser(@NotNull Project project,
204 @NotNull PsiMethod method,
205 @NotNull ParameterClassMember[] members) {
206 final MemberChooser<ParameterClassMember> chooser = new MemberChooser<>(members, false, true, project);
207 chooser.selectElements(getInitialSelection(method, members));
208 chooser.setTitle("Choose " + (method.isConstructor() ? "Constructor" : "Method") + " Parameters");
214 * Exclude parameters passed to super() or this() calls from initial selection
216 private static ParameterClassMember[] getInitialSelection(@NotNull PsiMethod method,
217 @NotNull ParameterClassMember[] members) {
218 final Set<PsiElement> resolvedInSuperOrThis = new HashSet<>();
219 final PsiCodeBlock body = method.getBody();
220 LOG.assertTrue(body != null);
221 final PsiStatement[] statements = body.getStatements();
222 if (statements.length > 0 && statements[0] instanceof PsiExpressionStatement) {
223 final PsiExpression expression = ((PsiExpressionStatement)statements[0]).getExpression();
224 if (expression instanceof PsiMethodCallExpression) {
225 final PsiMethod calledMethod = ((PsiMethodCallExpression)expression).resolveMethod();
226 if (calledMethod != null && calledMethod.isConstructor()) {
227 for (PsiExpression arg : ((PsiMethodCallExpression)expression).getArgumentList().getExpressions()) {
228 if (arg instanceof PsiReferenceExpression) {
229 ContainerUtil.addIfNotNull(((PsiReferenceExpression)arg).resolve(), resolvedInSuperOrThis);
235 return ContainerUtil.findAll(members, member -> !resolvedInSuperOrThis.contains(member.getParameter())).toArray(ParameterClassMember.EMPTY_ARRAY);
239 private static ParameterClassMember[] sortByParameterIndex(@NotNull ParameterClassMember[] members, @NotNull PsiMethod method) {
240 final PsiParameterList parameterList = method.getParameterList();
241 Arrays.sort(members, (o1, o2) -> parameterList.getParameterIndex(o1.getParameter()) -
242 parameterList.getParameterIndex(o2.getParameter()));
247 private static <T extends PsiElement> List<T> revealPointers(@NotNull Iterable<SmartPsiElementPointer<T>> pointers) {
248 final List<T> result = new ArrayList<>();
249 for (SmartPsiElementPointer<T> pointer : pointers) {
250 result.add(pointer.getElement());
256 private static List<PsiParameter> revealParameterClassMembers(@NotNull Iterable<ParameterClassMember> parameterClassMembers) {
257 final List<PsiParameter> result = new ArrayList<>();
258 for (ParameterClassMember parameterClassMember : parameterClassMembers) {
259 result.add(parameterClassMember.getParameter());
265 private static ParameterClassMember[] toClassMemberArray(@NotNull Collection<SmartPsiElementPointer<PsiParameter>> unboundedParams) {
266 final ParameterClassMember[] result = new ParameterClassMember[unboundedParams.size()];
268 for (SmartPsiElementPointer<PsiParameter> pointer : unboundedParams) {
269 result[i++] = new ParameterClassMember(pointer.getElement());
275 private static Collection<SmartPsiElementPointer<PsiParameter>> copyUnboundedParamsAndClearOriginal(@NotNull PsiMethod method) {
276 synchronized (LOCK) {
277 final Collection<SmartPsiElementPointer<PsiParameter>> unboundedParams = getUnboundedParams(method);
278 final Collection<SmartPsiElementPointer<PsiParameter>> result = new ArrayList<>(unboundedParams);
279 unboundedParams.clear();
284 private static void processParameter(final Project project,
285 final PsiParameter parameter,
286 final Set<String> usedNames) {
287 IdeDocumentHistory.getInstance(project).includeCurrentPlaceAsChangePlace();
288 final PsiType type = FieldFromParameterUtils.getSubstitutedType(parameter);
289 final JavaCodeStyleManager styleManager = JavaCodeStyleManager.getInstance(project);
290 final String parameterName = parameter.getName();
291 String propertyName = styleManager.variableNameToPropertyName(parameterName, VariableKind.PARAMETER);
293 final PsiClass targetClass = PsiTreeUtil.getParentOfType(parameter, PsiClass.class);
294 final PsiElement declarationScope = parameter.getDeclarationScope();
295 if (!(declarationScope instanceof PsiMethod)) return;
296 final PsiMethod method = (PsiMethod)declarationScope;
298 final boolean isMethodStatic = method.hasModifierProperty(PsiModifier.STATIC);
300 VariableKind kind = isMethodStatic ? VariableKind.STATIC_FIELD : VariableKind.FIELD;
301 SuggestedNameInfo suggestedNameInfo = styleManager.suggestVariableName(kind, propertyName, null, type);
302 String[] names = suggestedNameInfo.names;
304 final boolean isFinal = !isMethodStatic && method.isConstructor();
305 String name = names[0];
306 if (targetClass != null) {
307 for (String curName : names) {
308 if (!usedNames.contains(curName)) {
309 final PsiField fieldByName = targetClass.findFieldByName(curName, false);
310 if (fieldByName != null && (!method.isConstructor() || !isFieldAssigned(fieldByName, method)) && fieldByName.getType().isAssignableFrom(parameter.getType())) {
318 if (usedNames.contains(name)) {
319 for (String curName : names) {
320 if (!usedNames.contains(curName)) {
327 final String fieldName = usedNames.add(name) ? name
328 : JavaCodeStyleManager.getInstance(project).suggestUniqueVariableName(name, parameter, true);
330 ApplicationManager.getApplication().runWriteAction(() -> {
332 FieldFromParameterUtils.createFieldAndAddAssignment(
342 catch (IncorrectOperationException e) {
348 private static boolean isFieldAssigned(PsiField field, PsiMethod method) {
349 for (PsiReference reference : ReferencesSearch.search(field, new LocalSearchScope(method))) {
350 if (reference instanceof PsiReferenceExpression && PsiUtil.isOnAssignmentLeftHand((PsiReferenceExpression)reference)) {
358 public boolean startInWriteAction() {