IDEA-68536 I want Live Templates to automatically add static imports (Use Case: templates for org.junit.Assert.assertXXX)

1. Corresponding template optional processor is added;
2. 'Add static import' actions are refactored in order to provide ability to execute their logic from the template processor;
This commit is contained in:
Denis Zhdanov
2011-04-28 13:07:52 +04:00
parent 62e53bdc98
commit c16276e8a4
8 changed files with 276 additions and 30 deletions
@@ -31,6 +31,7 @@ import com.intellij.util.IncorrectOperationException;
import gnu.trove.TIntArrayList;
import gnu.trove.TIntProcedure;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author ven
@@ -43,41 +44,63 @@ public class AddOnDemandStaticImportAction extends PsiElementBaseIntentionAction
return CodeInsightBundle.message("intention.add.on.demand.static.import.family");
}
public boolean isAvailable(@NotNull Project project, Editor editor, @NotNull PsiElement element) {
if (!PsiUtil.isLanguageLevel5OrHigher(element)) return false;
/**
* Allows to check if static import may be performed for the given element.
*
* @param element element to check
* @return target class that may be statically imported if any; <code>null</code> otherwise
*/
@Nullable
public static PsiClass getClassToPerformStaticImport(@NotNull PsiElement element) {
if (!PsiUtil.isLanguageLevel5OrHigher(element)) return null;
if (!(element instanceof PsiIdentifier) || !(element.getParent() instanceof PsiReferenceExpression)) {
return false;
return null;
}
PsiReferenceExpression refExpr = (PsiReferenceExpression)element.getParent();
if (refExpr.getParent() instanceof PsiReferenceExpression &&
isParameterizedReference((PsiReferenceExpression)refExpr.getParent())) return false;
isParameterizedReference((PsiReferenceExpression)refExpr.getParent())) return null;
PsiElement resolved = refExpr.resolve();
if (!(resolved instanceof PsiClass)) {
return false;
return null;
}
PsiClass psiClass = (PsiClass)resolved;
PsiFile file = refExpr.getContainingFile();
if (!(file instanceof PsiJavaFile)) return false;
if (!(file instanceof PsiJavaFile)) return null;
PsiImportList importList = ((PsiJavaFile)file).getImportList();
if (importList == null) return false;
if (importList == null) return null;
for (PsiImportStaticStatement statement : importList.getImportStaticStatements()) {
PsiClass staticResolve = statement.resolveTargetClass();
if (psiClass == staticResolve) return false; //already imported
if (psiClass == staticResolve) return null; //already imported
}
String text = CodeInsightBundle.message("intention.add.on.demand.static.import.text", psiClass.getQualifiedName());
setText(text);
return true;
return psiClass;
}
public boolean isAvailable(@NotNull Project project, Editor editor, @NotNull PsiElement element) {
PsiClass classToImport = getClassToPerformStaticImport(element);
if (classToImport != null) {
String text = CodeInsightBundle.message("intention.add.on.demand.static.import.text", classToImport.getQualifiedName());
setText(text);
}
return classToImport != null;
}
public void invoke(@NotNull final Project project, final Editor editor, PsiFile file) throws IncorrectOperationException {
public static void invoke(final Project project, PsiFile file, final Editor editor, PsiElement element) {
if (!CodeInsightUtilBase.prepareFileForWrite(file)) return;
PsiElement element = file.findElementAt(editor.getCaretModel().getOffset());
final PsiReferenceExpression refExpr = (PsiReferenceExpression)element.getParent();
final PsiClass aClass = (PsiClass)refExpr.resolve();
if (aClass == null) {
return;
}
PsiImportStaticStatement importStaticStatement =
JavaPsiFacade.getInstance(file.getProject()).getElementFactory().createImportStaticStatement(aClass, "*");
((PsiJavaFile)file).getImportList().add(importStaticStatement);
PsiImportList importList = ((PsiJavaFile)file).getImportList();
if (importList == null) {
return;
}
importList.add(importStaticStatement);
PsiFile[] roots = file.getPsiRoots();
for (final PsiFile root : roots) {
@@ -113,6 +136,9 @@ public class AddOnDemandStaticImportAction extends PsiElementBaseIntentionAction
expressionToDequalifyOffsets.forEachDescending(new TIntProcedure() {
public boolean execute(int offset) {
PsiReferenceExpression expression = PsiTreeUtil.findElementOfClassAtOffset(root, offset, PsiReferenceExpression.class, false);
if (expression == null) {
return false;
}
PsiExpression qualifierExpression = expression.getQualifierExpression();
if (qualifierExpression instanceof PsiReferenceExpression && ((PsiReferenceExpression)qualifierExpression).isReferenceTo(aClass)) {
qualifierExpression.delete();
@@ -127,8 +153,17 @@ public class AddOnDemandStaticImportAction extends PsiElementBaseIntentionAction
});
}
}
public void invoke(@NotNull final Project project, final Editor editor, PsiFile file) throws IncorrectOperationException {
PsiElement element = file.findElementAt(editor.getCaretModel().getOffset());
invoke(project, file, editor, element);
}
private static boolean isParameterizedReference(final PsiReferenceExpression expression) {
return expression.getParameterList() != null && expression.getParameterList().getFirstChild() != null;
if (expression.getParameterList() == null) {
return false;
}
PsiReferenceParameterList parameterList = expression.getParameterList();
return parameterList != null && parameterList.getFirstChild() != null;
}
}
@@ -32,6 +32,7 @@ import com.intellij.psi.util.PsiUtil;
import com.intellij.refactoring.util.RefactoringUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class AddSingleMemberStaticImportAction extends PsiElementBaseIntentionAction {
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.intention.impl.AddSingleMemberStaticImportAction");
@@ -42,26 +43,32 @@ public class AddSingleMemberStaticImportAction extends PsiElementBaseIntentionAc
return CodeInsightBundle.message("intention.add.single.member.static.import.family");
}
public boolean isAvailable(@NotNull Project project, Editor editor, @NotNull PsiElement element) {
if (!PsiUtil.isLanguageLevel5OrHigher(element)) return false;
/**
* Allows to check if it's possible to perform static import for the target element.
*
* @param element target element that is static import candidate
* @return not-null qualified name of the class which method may be statically imported if any; <code>null</code> otherwise
*/
@Nullable
public static String getStaticImportClass(@NotNull PsiElement element) {
if (!PsiUtil.isLanguageLevel5OrHigher(element)) return null;
PsiFile file = element.getContainingFile();
if (element instanceof PsiIdentifier && element.getParent() instanceof PsiReferenceExpression &&
((PsiReferenceExpression)element.getParent()).getQualifierExpression() != null) {
PsiReferenceExpression refExpr = (PsiReferenceExpression)element.getParent();
if (refExpr.getParameterList() != null &&
refExpr.getParameterList().getFirstChild() != null) return false;
PsiReferenceParameterList parameterList = refExpr.getParameterList();
if (parameterList != null && parameterList.getFirstChild() != null) return null;
PsiElement resolved = refExpr.resolve();
if (resolved instanceof PsiMember &&
((PsiModifierListOwner)resolved).hasModifierProperty(PsiModifier.STATIC)) {
if (resolved instanceof PsiMember && ((PsiModifierListOwner)resolved).hasModifierProperty(PsiModifier.STATIC)) {
PsiClass aClass = ((PsiMember)resolved).getContainingClass();
if (aClass != null && !PsiTreeUtil.isAncestor(aClass, element, true)) {
String qName = aClass.getQualifiedName();
if (qName != null) {
qName = qName + "." +refExpr.getReferenceName();
if (file instanceof PsiJavaFile) {
if (((PsiJavaFile)file).getImportList().findSingleImportStatement(refExpr.getReferenceName()) == null) {
setText(CodeInsightBundle.message("intention.add.single.member.static.import.text", qName));
return true;
PsiImportList importList = ((PsiJavaFile)file).getImportList();
if (importList != null && importList.findSingleImportStatement(refExpr.getReferenceName()) == null) {
return qName;
}
}
}
@@ -69,20 +76,28 @@ public class AddSingleMemberStaticImportAction extends PsiElementBaseIntentionAc
}
}
return false;
return null;
}
public boolean isAvailable(@NotNull Project project, Editor editor, @NotNull PsiElement element) {
String classQName = getStaticImportClass(element);
if (classQName != null) {
setText(CodeInsightBundle.message("intention.add.single.member.static.import.text", classQName));
}
return classQName != null;
}
public void invoke(@NotNull final Project project, final Editor editor, PsiFile file) throws IncorrectOperationException {
public static void invoke(PsiFile file, PsiElement element) {
if (!CodeInsightUtilBase.prepareFileForWrite(file)) return;
PsiElement element = file.findElementAt(editor.getCaretModel().getOffset());
final PsiReferenceExpression refExpr = (PsiReferenceExpression)element.getParent();
final PsiElement resolved = refExpr.resolve();
file.accept(new JavaRecursiveElementWalkingVisitor() {
@Override public void visitReferenceExpression(PsiReferenceExpression expression) {
super.visitReferenceExpression(expression);
if (refExpr.getReferenceName().equals(expression.getReferenceName())) {
String referenceName = refExpr.getReferenceName();
if (referenceName != null && referenceName.equals(expression.getReferenceName())) {
PsiElement resolved = expression.resolve();
if (resolved != null) {
expression.putUserData(TEMP_REFERENT_USER_DATA, resolved);
@@ -91,7 +106,11 @@ public class AddSingleMemberStaticImportAction extends PsiElementBaseIntentionAc
}
});
RefactoringUtil.bindToElementViaStaticImport(((PsiMember)resolved).getContainingClass(), ((PsiNamedElement)resolved).getName(), ((PsiJavaFile)file).getImportList());
if (resolved != null) {
RefactoringUtil.bindToElementViaStaticImport(
((PsiMember)resolved).getContainingClass(), ((PsiNamedElement)resolved).getName(), ((PsiJavaFile)file).getImportList()
);
}
file.accept(new JavaRecursiveElementVisitor() {
@Override public void visitReferenceExpression(PsiReferenceExpression expression) {
@@ -133,5 +152,11 @@ public class AddSingleMemberStaticImportAction extends PsiElementBaseIntentionAc
super.visitReferenceExpression(expression);
}
});
}
public void invoke(@NotNull final Project project, final Editor editor, PsiFile file) throws IncorrectOperationException {
PsiElement element = file.findElementAt(editor.getCaretModel().getOffset());
invoke(file, element);
}
}
@@ -0,0 +1,139 @@
/*
* Copyright 2000-2011 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.template.impl;
import com.intellij.codeInsight.CodeInsightBundle;
import com.intellij.codeInsight.intention.impl.AddOnDemandStaticImportAction;
import com.intellij.codeInsight.intention.impl.AddSingleMemberStaticImportAction;
import com.intellij.codeInsight.template.Template;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.RangeMarker;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiUtilBase;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import static java.util.Arrays.asList;
/**
* @author Denis Zhdanov
* @since 4/27/11 3:07 PM
*/
public class ShortenToStaticImportProcessor implements TemplateOptionalProcessor {
private static final List<StaticImporter> IMPORTERS = asList(new SingleMemberStaticImporter(), new OnDemandStaticImporter());
@Override
public void processText(Project project, Template template, Document document, RangeMarker templateRange, Editor editor) {
if (!template.getValue(Template.Property.USE_STATIC_IMPORT_IF_POSSIBLE)) {
return;
}
PsiDocumentManager.getInstance(project).commitDocument(document);
final PsiFile file = PsiUtilBase.getPsiFileInEditor(editor, project);
if (file == null) {
return;
}
List<Pair<PsiElement, StaticImporter>> staticImportTargets = new ArrayList<Pair<PsiElement, StaticImporter>>();
for (
PsiElement element = PsiUtilBase.getElementAtOffset(file, templateRange.getStartOffset());
element != null && element.getTextRange().getStartOffset() < templateRange.getEndOffset();
element = getNext(element))
{
for (StaticImporter importer : IMPORTERS) {
if (importer.canPerform(element)) {
staticImportTargets.add(new Pair<PsiElement, StaticImporter>(element, importer));
break;
}
}
}
for (Pair<PsiElement, StaticImporter> pair : staticImportTargets) {
if (pair.first.isValid()) {
pair.second.perform(project, file, editor, pair.first);
}
}
}
@Nullable
private static PsiElement getNext(@NotNull PsiElement element) {
PsiElement result = element.getNextSibling();
for (PsiElement current = element; current != null && result == null; current = current.getParent()) {
result = current.getNextSibling();
}
return result;
}
@Nls
@Override
public String getOptionName() {
return CodeInsightBundle.message("dialog.edit.template.checkbox.use.static.import");
}
@Override
public boolean isEnabled(Template template) {
return template.getValue(Template.Property.USE_STATIC_IMPORT_IF_POSSIBLE);
}
@Override
public void setEnabled(Template template, boolean value) {
template.setValue(Template.Property.USE_STATIC_IMPORT_IF_POSSIBLE, value);
}
@Override
public boolean isVisible(Template template) {
return true;
}
private interface StaticImporter {
boolean canPerform(@NotNull PsiElement element);
void perform(Project project, PsiFile file, Editor editor, PsiElement element);
}
private static class SingleMemberStaticImporter implements StaticImporter {
@Override
public boolean canPerform(@NotNull PsiElement element) {
return AddSingleMemberStaticImportAction.getStaticImportClass(element) != null;
}
@Override
public void perform(Project project, PsiFile file, Editor editor, PsiElement element) {
AddSingleMemberStaticImportAction.invoke(file, element);
}
}
private static class OnDemandStaticImporter implements StaticImporter {
@Override
public boolean canPerform(@NotNull PsiElement element) {
return AddOnDemandStaticImportAction.getClassToPerformStaticImport(element) != null;
}
@Override
public void perform(Project project, PsiFile file, Editor editor, PsiElement element) {
AddOnDemandStaticImportAction.invoke(project, file, editor, element);
}
}
}
@@ -21,7 +21,21 @@ import com.intellij.openapi.editor.RangeMarker;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import java.util.EnumMap;
import java.util.Map;
public abstract class Template {
public enum Property {
USE_STATIC_IMPORT_IF_POSSIBLE
}
private static final Map<Property, Boolean> DEFAULT_PROPERTIES = new EnumMap<Property, Boolean>(Property.class);
static {
DEFAULT_PROPERTIES.put(Property.USE_STATIC_IMPORT_IF_POSSIBLE, false);
}
private final Map<Property, Boolean> myProperties = new EnumMap<Property, Boolean>(Property.class);
public abstract void addTextSegment(@NotNull String text);
public abstract void addVariableSegment(@NonNls String name);
@@ -66,4 +80,18 @@ public abstract class Template {
public abstract boolean isToShortenLongNames();
public abstract void setToShortenLongNames(boolean toShortenLongNames);
public boolean getValue(@NotNull Property key) {
Boolean result = myProperties.get(key);
return result == null ? getDefaultValue(key) : result;
}
public void setValue(@NotNull Property key, boolean value) {
myProperties.put(key, value);
}
public static boolean getDefaultValue(@NotNull Property key) {
Boolean result = DEFAULT_PROPERTIES.get(key);
return result == null ? false : result;
}
}
@@ -27,6 +27,7 @@ import com.intellij.psi.tree.IElementType;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import javax.xml.bind.annotation.XmlElement;
import java.util.*;
/**
@@ -183,6 +184,12 @@ public class TemplateImpl extends Template implements SchemeElement {
template.myIsInline = myIsInline;
template.myTemplateContext = myTemplateContext.createCopy();
template.isDeactivated = isDeactivated;
for (Property property : Property.values()) {
boolean value = getValue(property);
if (value != Template.getDefaultValue(property)) {
template.setValue(property, value);
}
}
for (Variable variable : myVariables) {
template.addVariable(variable.getName(), variable.getExpressionString(), variable.getDefaultValueString(), variable.isAlwaysStopAt());
}
@@ -86,6 +86,7 @@ public class TemplateSettings implements PersistentStateComponent<Element>, Expo
private static final @NonNls String CONTEXT = "context";
private static final @NonNls String TO_REFORMAT = "toReformat";
private static final @NonNls String TO_SHORTEN_FQ_NAMES = "toShortenFQNames";
private static final @NonNls String USE_STATIC_IMPORT = "useStaticImport";
private static final @NonNls String DEFAULT_SHORTCUT = "defaultShortcut";
private static final @NonNls String DEACTIVATED = "deactivated";
@@ -589,6 +590,10 @@ public class TemplateSettings implements PersistentStateComponent<Element>, Expo
template.setToShortenLongNames(Boolean.parseBoolean(element.getAttributeValue(TO_SHORTEN_FQ_NAMES)));
template.setDeactivated(Boolean.parseBoolean(element.getAttributeValue(DEACTIVATED)));
String useStaticImport = element.getAttributeValue(USE_STATIC_IMPORT);
if (useStaticImport != null) {
template.setValue(TemplateImpl.Property.USE_STATIC_IMPORT_IF_POSSIBLE, Boolean.parseBoolean(useStaticImport));
}
for (final Object o : element.getChildren(VARIABLE)) {
Element e = (Element)o;
@@ -627,6 +632,11 @@ public class TemplateSettings implements PersistentStateComponent<Element>, Expo
}
element.setAttribute(TO_REFORMAT, Boolean.toString(template.isToReformat()));
element.setAttribute(TO_SHORTEN_FQ_NAMES, Boolean.toString(template.isToShortenLongNames()));
if (template.getValue(Template.Property.USE_STATIC_IMPORT_IF_POSSIBLE)
!= Template.getDefaultValue(Template.Property.USE_STATIC_IMPORT_IF_POSSIBLE))
{
element.setAttribute(USE_STATIC_IMPORT, Boolean.toString(template.getValue(Template.Property.USE_STATIC_IMPORT_IF_POSSIBLE)));
}
if (template.isDeactivated()) {
element.setAttribute(DEACTIVATED, Boolean.toString(true));
}
@@ -295,6 +295,7 @@ dialog.edit.template.options.title=Options
dialog.edit.template.label.expand.with=Expand with
dialog.edit.template.checkbox.reformat.according.to.style=&Reformat according to style
dialog.edit.template.checkbox.shorten.fq.names=Shorten &FQ names
dialog.edit.template.checkbox.use.static.import=Use static import if possible
dialog.edit.template.context.title=Context
dialog.edit.template.checkbox.java.code=&Java code
dialog.edit.template.checkbox.html=&HTML
+1
View File
@@ -947,6 +947,7 @@
<highlightErrorFilter implementation="com.intellij.codeInsight.daemon.impl.analysis.JavadocErrorFilter"/>
<daemon.changeLocalityDetector implementation="com.intellij.codeInsight.daemon.impl.JavaChangeLocalityDetector"/>
<liveTemplateOptionalProcessor implementation="com.intellij.codeInsight.template.impl.ShortenToStaticImportProcessor"/>
<liveTemplateOptionalProcessor implementation="com.intellij.codeInsight.template.impl.ShortenFQNamesProcessor"/>
<projectService serviceInterface="com.intellij.ide.todo.nodes.TodoTreeHelper"
serviceImplementation="com.intellij.ide.todo.nodes.TodoJavaTreeHelper"/>