mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-10-06 06:21:16 +07:00
329 lines
13 KiB
Java
329 lines
13 KiB
Java
/*
|
|
* 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 com.intellij.codeInsight.intention.impl;
|
|
|
|
import com.intellij.codeInsight.CodeInsightBundle;
|
|
import com.intellij.codeInsight.FileModificationService;
|
|
import com.intellij.codeInsight.intention.HighPriorityAction;
|
|
import com.intellij.ide.util.MemberChooser;
|
|
import com.intellij.lang.java.JavaLanguage;
|
|
import com.intellij.openapi.application.ApplicationManager;
|
|
import com.intellij.openapi.diagnostic.Logger;
|
|
import com.intellij.openapi.editor.Editor;
|
|
import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory;
|
|
import com.intellij.openapi.project.Project;
|
|
import com.intellij.openapi.ui.DialogWrapper;
|
|
import com.intellij.openapi.util.Key;
|
|
import com.intellij.psi.*;
|
|
import com.intellij.psi.codeStyle.*;
|
|
import com.intellij.psi.util.PsiTreeUtil;
|
|
import com.intellij.util.Function;
|
|
import com.intellij.util.IncorrectOperationException;
|
|
import com.intellij.util.containers.ContainerUtil;
|
|
import com.intellij.util.containers.MultiMap;
|
|
import org.jetbrains.annotations.NotNull;
|
|
import org.jetbrains.annotations.Nullable;
|
|
|
|
import java.util.*;
|
|
|
|
/**
|
|
* @author Danila Ponomarenko
|
|
*/
|
|
public class BindFieldsFromParametersAction extends BaseIntentionAction implements HighPriorityAction {
|
|
private static final Logger LOG = Logger.getInstance(CreateFieldFromParameterAction.class);
|
|
private static final Key<Map<SmartPsiElementPointer<PsiParameter>, Boolean>> PARAMS = Key.create("FIELDS_FROM_PARAMS");
|
|
|
|
private static final Object LOCK = new Object();
|
|
|
|
@Override
|
|
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
|
|
PsiParameter psiParameter = FieldFromParameterUtils.findParameterAtCursor(file, editor);
|
|
PsiMethod method = findMethod(psiParameter, editor, file);
|
|
if (method == null) return false;
|
|
|
|
final List<PsiParameter> parameters = getAvailableParameters(method);
|
|
|
|
synchronized (LOCK) {
|
|
final Collection<SmartPsiElementPointer<PsiParameter>> params = getUnboundedParams(method);
|
|
params.clear();
|
|
for (PsiParameter parameter : parameters) {
|
|
params.add(SmartPointerManager.getInstance(project).createSmartPsiElementPointer(parameter));
|
|
}
|
|
if (params.isEmpty()) return false;
|
|
if (params.size() == 1 && psiParameter != null) return false;
|
|
if (psiParameter == null) {
|
|
psiParameter = params.iterator().next().getElement();
|
|
LOG.assertTrue(psiParameter != null);
|
|
}
|
|
|
|
setText(CodeInsightBundle.message("intention.bind.fields.from.parameters.text", method.isConstructor() ? "constructor" : "method"));
|
|
}
|
|
return isAvailable(psiParameter);
|
|
}
|
|
|
|
@Nullable
|
|
private static PsiMethod findMethod(@Nullable PsiParameter parameter, @NotNull Editor editor, @NotNull PsiFile file) {
|
|
if (parameter == null) {
|
|
final PsiElement elementAt = file.findElementAt(editor.getCaretModel().getOffset());
|
|
if (elementAt instanceof PsiIdentifier) {
|
|
final PsiElement parent = elementAt.getParent();
|
|
if (parent instanceof PsiMethod) {
|
|
return (PsiMethod)parent;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
final PsiElement declarationScope = parameter.getDeclarationScope();
|
|
if (declarationScope instanceof PsiMethod) {
|
|
return (PsiMethod)declarationScope;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
@NotNull
|
|
private static List<PsiParameter> getAvailableParameters(@NotNull PsiMethod method) {
|
|
final List<PsiParameter> parameters = new ArrayList<PsiParameter>();
|
|
for (PsiParameter parameter : method.getParameterList().getParameters()) {
|
|
if (isAvailable(parameter)) {
|
|
parameters.add(parameter);
|
|
}
|
|
}
|
|
return parameters;
|
|
}
|
|
|
|
private static boolean isAvailable(PsiParameter psiParameter) {
|
|
final PsiType type = FieldFromParameterUtils.getSubstitutedType(psiParameter);
|
|
final PsiClass targetClass = PsiTreeUtil.getParentOfType(psiParameter, PsiClass.class);
|
|
return FieldFromParameterUtils.isAvailable(psiParameter, type, targetClass) &&
|
|
psiParameter.getLanguage().isKindOf(JavaLanguage.INSTANCE);
|
|
}
|
|
|
|
@NotNull
|
|
private static Collection<SmartPsiElementPointer<PsiParameter>> getUnboundedParams(PsiMethod psiMethod) {
|
|
Map<SmartPsiElementPointer<PsiParameter>, Boolean> params = psiMethod.getUserData(PARAMS);
|
|
if (params == null) psiMethod.putUserData(PARAMS, params = ContainerUtil.createConcurrentWeakMap());
|
|
final Map<SmartPsiElementPointer<PsiParameter>, Boolean> finalParams = params;
|
|
return new AbstractCollection<SmartPsiElementPointer<PsiParameter>>() {
|
|
@Override
|
|
public boolean add(SmartPsiElementPointer<PsiParameter> psiVariable) {
|
|
return finalParams.put(psiVariable, Boolean.TRUE) == null;
|
|
}
|
|
|
|
@Override
|
|
public Iterator<SmartPsiElementPointer<PsiParameter>> iterator() {
|
|
return finalParams.keySet().iterator();
|
|
}
|
|
|
|
@Override
|
|
public int size() {
|
|
return finalParams.size();
|
|
}
|
|
|
|
@Override
|
|
public void clear() {
|
|
finalParams.clear();
|
|
}
|
|
};
|
|
}
|
|
|
|
@Override
|
|
@NotNull
|
|
public String getFamilyName() {
|
|
return CodeInsightBundle.message("intention.bind.fields.from.parameters.family");
|
|
}
|
|
|
|
@Override
|
|
public void invoke(@NotNull Project project, Editor editor, PsiFile file) {
|
|
invoke(project, editor, file, !ApplicationManager.getApplication().isUnitTestMode());
|
|
}
|
|
|
|
private static void invoke(final Project project, Editor editor, PsiFile file, boolean isInteractive) {
|
|
PsiParameter myParameter = FieldFromParameterUtils.findParameterAtCursor(file, editor);
|
|
if (!FileModificationService.getInstance().prepareFileForWrite(file)) return;
|
|
final PsiMethod method = myParameter != null ? (PsiMethod)myParameter.getDeclarationScope() : PsiTreeUtil.getParentOfType(file.findElementAt(editor.getCaretModel().getOffset()), PsiMethod.class);
|
|
LOG.assertTrue(method != null);
|
|
|
|
final HashSet<String> usedNames = new HashSet<String>();
|
|
final Iterable<PsiParameter> parameters = selectParameters(project, method, copyUnboundedParamsAndClearOriginal(method), isInteractive);
|
|
final MultiMap<PsiType, PsiParameter> types = new MultiMap<PsiType, PsiParameter>();
|
|
for (PsiParameter parameter : parameters) {
|
|
types.putValue(parameter.getType(), parameter);
|
|
}
|
|
final CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(project);
|
|
final boolean preferLongerNames = settings.PREFER_LONGER_NAMES;
|
|
for (PsiParameter selected : parameters) {
|
|
try {
|
|
settings.PREFER_LONGER_NAMES = preferLongerNames || types.get(selected.getType()).size() > 1;
|
|
processParameter(project, selected, usedNames);
|
|
} finally {
|
|
settings.PREFER_LONGER_NAMES = preferLongerNames;
|
|
}
|
|
}
|
|
}
|
|
|
|
@NotNull
|
|
private static Iterable<PsiParameter> selectParameters(@NotNull Project project,
|
|
@NotNull PsiMethod method,
|
|
@NotNull Collection<SmartPsiElementPointer<PsiParameter>> unboundedParams,
|
|
boolean isInteractive) {
|
|
if (unboundedParams.size() < 2 || !isInteractive) {
|
|
return revealPointers(unboundedParams);
|
|
}
|
|
|
|
final ParameterClassMember[] members = sortByParameterIndex(toClassMemberArray(unboundedParams), method);
|
|
|
|
final MemberChooser<ParameterClassMember> chooser = showChooser(project, method, members);
|
|
|
|
final List<ParameterClassMember> selectedElements = chooser.getSelectedElements();
|
|
if (chooser.getExitCode() != DialogWrapper.OK_EXIT_CODE || selectedElements == null) {
|
|
return Collections.emptyList();
|
|
}
|
|
|
|
return revealParameterClassMembers(selectedElements);
|
|
}
|
|
|
|
@NotNull
|
|
private static MemberChooser<ParameterClassMember> showChooser(@NotNull Project project,
|
|
@NotNull PsiMethod method,
|
|
@NotNull ParameterClassMember[] members) {
|
|
final MemberChooser<ParameterClassMember> chooser = new MemberChooser<ParameterClassMember>(members, false, true, project);
|
|
chooser.selectElements(members);
|
|
chooser.setTitle("Choose " + (method.isConstructor() ? "Constructor" : "Method") + " Parameters");
|
|
chooser.show();
|
|
return chooser;
|
|
}
|
|
|
|
@NotNull
|
|
private static ParameterClassMember[] sortByParameterIndex(@NotNull ParameterClassMember[] members, @NotNull PsiMethod method) {
|
|
final PsiParameterList parameterList = method.getParameterList();
|
|
Arrays.sort(members, new Comparator<ParameterClassMember>() {
|
|
@Override
|
|
public int compare(ParameterClassMember o1, ParameterClassMember o2) {
|
|
return parameterList.getParameterIndex(o1.getParameter()) -
|
|
parameterList.getParameterIndex(o2.getParameter());
|
|
}
|
|
});
|
|
return members;
|
|
}
|
|
|
|
@NotNull
|
|
private static <T extends PsiElement> List<T> revealPointers(@NotNull Iterable<SmartPsiElementPointer<T>> pointers) {
|
|
final List<T> result = new ArrayList<T>();
|
|
for (SmartPsiElementPointer<T> pointer : pointers) {
|
|
result.add(pointer.getElement());
|
|
}
|
|
return result;
|
|
}
|
|
|
|
@NotNull
|
|
private static List<PsiParameter> revealParameterClassMembers(@NotNull Iterable<ParameterClassMember> parameterClassMembers) {
|
|
final List<PsiParameter> result = new ArrayList<PsiParameter>();
|
|
for (ParameterClassMember parameterClassMember : parameterClassMembers) {
|
|
result.add(parameterClassMember.getParameter());
|
|
}
|
|
return result;
|
|
}
|
|
|
|
@NotNull
|
|
private static ParameterClassMember[] toClassMemberArray(@NotNull Collection<SmartPsiElementPointer<PsiParameter>> unboundedParams) {
|
|
final ParameterClassMember[] result = new ParameterClassMember[unboundedParams.size()];
|
|
int i = 0;
|
|
for (SmartPsiElementPointer<PsiParameter> pointer : unboundedParams) {
|
|
result[i++] = new ParameterClassMember(pointer.getElement());
|
|
}
|
|
return result;
|
|
}
|
|
|
|
@NotNull
|
|
private static Collection<SmartPsiElementPointer<PsiParameter>> copyUnboundedParamsAndClearOriginal(@NotNull PsiMethod method) {
|
|
synchronized (LOCK) {
|
|
final Collection<SmartPsiElementPointer<PsiParameter>> unboundedParams = getUnboundedParams(method);
|
|
final Collection<SmartPsiElementPointer<PsiParameter>> result = new ArrayList<SmartPsiElementPointer<PsiParameter>>(unboundedParams);
|
|
unboundedParams.clear();
|
|
return result;
|
|
}
|
|
}
|
|
|
|
private static void processParameter(final Project project,
|
|
final PsiParameter myParameter,
|
|
final Set<String> usedNames) {
|
|
IdeDocumentHistory.getInstance(project).includeCurrentPlaceAsChangePlace();
|
|
final PsiType type = FieldFromParameterUtils.getSubstitutedType(myParameter);
|
|
final JavaCodeStyleManager styleManager = JavaCodeStyleManager.getInstance(project);
|
|
final String parameterName = myParameter.getName();
|
|
String propertyName = styleManager.variableNameToPropertyName(parameterName, VariableKind.PARAMETER);
|
|
|
|
final PsiClass targetClass = PsiTreeUtil.getParentOfType(myParameter, PsiClass.class);
|
|
final PsiMethod method = (PsiMethod)myParameter.getDeclarationScope();
|
|
|
|
final boolean isMethodStatic = method.hasModifierProperty(PsiModifier.STATIC);
|
|
|
|
VariableKind kind = isMethodStatic ? VariableKind.STATIC_FIELD : VariableKind.FIELD;
|
|
SuggestedNameInfo suggestedNameInfo = styleManager.suggestVariableName(kind, propertyName, null, type);
|
|
String[] names = suggestedNameInfo.names;
|
|
|
|
final boolean isFinal = !isMethodStatic && method.isConstructor();
|
|
String name = names[0];
|
|
if (targetClass != null) {
|
|
for (String curName : names) {
|
|
if (!usedNames.contains(curName) && targetClass.findFieldByName(curName, false) != null) {
|
|
name = curName;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (usedNames.contains(name)) {
|
|
for (String curName : names) {
|
|
if (!usedNames.contains(curName)) {
|
|
name = curName;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
final String fieldName = usedNames.add(name) ? name
|
|
: JavaCodeStyleManager.getInstance(project).suggestUniqueVariableName(name, myParameter, true);
|
|
|
|
ApplicationManager.getApplication().runWriteAction(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
try {
|
|
FieldFromParameterUtils.createFieldAndAddAssignment(
|
|
project,
|
|
targetClass,
|
|
method,
|
|
myParameter,
|
|
type,
|
|
fieldName,
|
|
isMethodStatic,
|
|
isFinal);
|
|
}
|
|
catch (IncorrectOperationException e) {
|
|
LOG.error(e);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public boolean startInWriteAction() {
|
|
return false;
|
|
}
|
|
}
|