prototype for class based patterns as entry points (IDEA-34620)

This commit is contained in:
Anna.Kozlova
2016-07-14 12:30:32 +02:00
parent e91fc68156
commit 4cbe5e7873
7 changed files with 408 additions and 8 deletions
@@ -31,13 +31,14 @@ import com.intellij.openapi.extensions.impl.ExtensionPointImpl;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.JDOMExternalizableStringList;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.profile.codeInspection.InspectionProfileManager;
import com.intellij.psi.PsiDocCommentOwner;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiManager;
import com.intellij.psi.PsiModifierListOwner;
import com.intellij.psi.*;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.UIUtil;
import com.intellij.util.xmlb.SkipDefaultsSerializationFilter;
import com.intellij.util.xmlb.XmlSerializer;
import com.intellij.util.xmlb.annotations.*;
import org.jdom.Element;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
@@ -50,6 +51,7 @@ public abstract class EntryPointsManagerBase extends EntryPointsManager implemen
@NonNls private static final String[] STANDARD_ANNOS = {
"javax.ws.rs.*",
};
private static final String PATTERN_SUFFIX = ".*";
// null means uninitialized
private volatile List<String> ADDITIONAL_ANNOS;
@@ -72,6 +74,7 @@ public abstract class EntryPointsManagerBase extends EntryPointsManager implemen
}
public JDOMExternalizableStringList ADDITIONAL_ANNOTATIONS = new JDOMExternalizableStringList();
private final Map<String, SmartRefElementPointer> myPersistentEntryPoints;
private final List<ClassPattern> myPatterns = new ArrayList<>();
private final Set<RefElement> myTemporaryEntryPoints;
private static final String VERSION = "2.0";
@NonNls private static final String VERSION_ATTR = "version";
@@ -135,6 +138,13 @@ public abstract class EntryPointsManagerBase extends EntryPointsManager implemen
}
catch (Throwable ignored) {
}
getPatterns().clear();
for (Element pattern : element.getChildren("pattern")) {
final ClassPattern classPattern = new ClassPattern();
XmlSerializer.deserializeInto(classPattern, pattern);
getPatterns().add(classPattern);
}
}
@Override
@@ -142,6 +152,11 @@ public abstract class EntryPointsManagerBase extends EntryPointsManager implemen
public Element getState() {
Element element = new Element("state");
writeExternal(element, myPersistentEntryPoints, ADDITIONAL_ANNOTATIONS);
if (!getPatterns().isEmpty()) {
for (ClassPattern pattern : getPatterns()) {
element.addContent(XmlSerializer.serialize(pattern, new SkipDefaultsSerializationFilter()));
}
}
return element;
}
@@ -177,6 +192,16 @@ public abstract class EntryPointsManagerBase extends EntryPointsManager implemen
((RefElementImpl)refElement).setPermanentEntry(entryPoint.isPersistent());
}
}
for (ClassPattern pattern : myPatterns) {
final RefEntity refClass = manager.getReference(RefJavaManager.CLASS, pattern.pattern);
if (refClass != null) {
for (RefMethod constructor : ((RefClass)refClass).getConstructors()) {
((RefMethodImpl)constructor).setEntry(true);
((RefMethodImpl)constructor).setPermanentEntry(true);
}
}
}
});
}
}
@@ -192,6 +217,21 @@ public abstract class EntryPointsManagerBase extends EntryPointsManager implemen
@Override
public void addEntryPoint(@NotNull RefElement newEntryPoint, boolean isPersistent) {
if (!newEntryPoint.isValid()) return;
if (isPersistent) {
if (newEntryPoint instanceof RefMethod && ((RefMethod)newEntryPoint).isConstructor() || newEntryPoint instanceof RefClass) {
final ClassPattern classPattern = new ClassPattern();
classPattern.pattern = new SmartRefElementPointerImpl(newEntryPoint, true).getFQName();
getPatterns().add(classPattern);
final EntryPointsManager entryPointsManager = getInstance(newEntryPoint.getElement().getProject());
if (this != entryPointsManager) {
entryPointsManager.addEntryPoint(newEntryPoint, true);
}
return;
}
}
if (newEntryPoint instanceof RefClass) {
RefClass refClass = (RefClass)newEntryPoint;
@@ -248,8 +288,8 @@ public abstract class EntryPointsManagerBase extends EntryPointsManager implemen
if (key != null) {
myPersistentEntryPoints.remove(key);
((RefElementImpl)anEntryPoint).setEntry(false);
}
((RefElementImpl)anEntryPoint).setEntry(false);
if (anEntryPoint.isPermanentEntry() && anEntryPoint.isValid()) {
final Project project = anEntryPoint.getElement().getProject();
@@ -258,6 +298,17 @@ public abstract class EntryPointsManagerBase extends EntryPointsManager implemen
entryPointsManager.removeEntryPoint(anEntryPoint);
}
}
if (anEntryPoint instanceof RefMethod && ((RefMethod)anEntryPoint).isConstructor() || anEntryPoint instanceof RefClass) {
final RefClass aClass = anEntryPoint instanceof RefClass ? (RefClass)anEntryPoint : ((RefMethod)anEntryPoint).getOwnerClass();
final String qualifiedName = aClass.getQualifiedName();
for (Iterator<ClassPattern> iterator = getPatterns().iterator(); iterator.hasNext(); ) {
if (Comparing.equal(iterator.next().pattern, qualifiedName)) {
//todo if inheritance or pattern?
iterator.remove();
}
}
}
}
@NotNull
@@ -321,6 +372,7 @@ public abstract class EntryPointsManagerBase extends EntryPointsManager implemen
public void addAllPersistentEntries(EntryPointsManagerBase manager) {
myPersistentEntryPoints.putAll(manager.myPersistentEntryPoints);
myPatterns.addAll(manager.getPatterns());
}
public static void convert(Element element, final Map<String, SmartRefElementPointer> persistentEntryPoints) {
@@ -380,7 +432,74 @@ public abstract class EntryPointsManagerBase extends EntryPointsManager implemen
return true;
}
if (element instanceof PsiClass) {
final String qualifiedName = ((PsiClass)element).getQualifiedName();
if (qualifiedName != null) {
for (ClassPattern pattern : getPatterns()) {
if (isAcceptedByPattern((PsiClass)element, qualifiedName, pattern, new HashSet<>())) {
return true;
}
}
}
}
return AnnotationUtil.checkAnnotatedUsingPatterns(owner, ADDITIONAL_ANNOTATIONS) ||
AnnotationUtil.checkAnnotatedUsingPatterns(owner, getAdditionalAnnotations());
}
private static boolean isAcceptedByPattern(@NotNull PsiClass element, String qualifiedName, ClassPattern pattern, Set<PsiClass> visited) {
if (qualifiedName == null) {
return false;
}
if (qualifiedName.equals(pattern.pattern)) {
return true;
}
if (pattern.pattern.endsWith(PATTERN_SUFFIX) && qualifiedName.startsWith(StringUtil.trimEnd(pattern.pattern, PATTERN_SUFFIX))) {
return true;
}
if (pattern.hierarchically) {
for (PsiClass superClass : element.getSupers()) {
final String superClassQualifiedName = superClass.getQualifiedName();
if (visited.add(superClass) && isAcceptedByPattern(superClass, superClassQualifiedName, pattern, visited)) {
return true;
}
}
}
return false;
}
public List<ClassPattern> getPatterns() {
return myPatterns;
}
@Tag("pattern")
public static class ClassPattern {
@Attribute("value")
public String pattern;
@Attribute("hierarchically")
public boolean hierarchically = false;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ClassPattern otherPattern = (ClassPattern)o;
if (hierarchically != otherPattern.hierarchically) return false;
if (!pattern.equals(otherPattern.pattern)) return false;
return true;
}
@Override
public int hashCode() {
int result = pattern.hashCode();
result = 31 * result + (hierarchically ? 1 : 0);
return result;
}
}
}
@@ -372,6 +372,9 @@ public class RefJavaManagerImpl extends RefJavaManager {
}
@Override
public void configureEntryClassPatterns() {}
@Override
public JButton createConfigureAnnotationsBtn() {
return null;
@@ -128,10 +128,13 @@ public class UnusedDeclarationInspection extends UnusedDeclarationInspectionBase
gc.gridy++;
add(myNonJavaCheckbox, gc);
JButton configureAnnotations = EntryPointsManagerImpl.createConfigureAnnotationsButton();
final JButton configureClassPatternsButton = EntryPointsManagerImpl.createConfigureClassPatternsButton();
gc.fill = GridBagConstraints.NONE;
gc.gridy++;
gc.insets.top = 10;
add(configureClassPatternsButton, gc);
JButton configureAnnotations = EntryPointsManagerImpl.createConfigureAnnotationsButton();
gc.gridy++;
gc.weighty = 1;
add(configureAnnotations, gc);
@@ -0,0 +1,172 @@
/*
* Copyright 2000-2016 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.codeInspection.ex;
import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.ui.*;
import com.intellij.ui.table.JBTable;
import com.intellij.util.ui.ItemRemovable;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableColumn;
import javax.swing.table.TableModel;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;
class ConfigureClassPatternsDialog extends DialogWrapper {
private final List<EntryPointsManagerBase.ClassPattern> myModifiedPatterns;
private final List<EntryPointsManagerBase.ClassPattern> myPatterns;
private final Project myProject;
public ConfigureClassPatternsDialog(List<EntryPointsManagerBase.ClassPattern> patterns, Project project) {
super(project);
myModifiedPatterns = new ArrayList<>(patterns);
myPatterns = patterns;
myProject = project;
init();
setTitle("Configure Class Patterns");
}
@Override
protected JComponent createCenterPanel() {
final JBTable table = createTableForPatterns();
final ToolbarDecorator toolbarDecorator = ToolbarDecorator.createDecorator(table)
.setAddAction(new AnActionButtonRunnable() {
@Override
public void run(AnActionButton button) {
myModifiedPatterns.add(new EntryPointsManagerBase.ClassPattern());
AbstractTableModel model = (AbstractTableModel)table.getModel();
final int row = myModifiedPatterns.size() - 1;
model.fireTableRowsInserted(row, row);
table.setRowSelectionInterval(row, row);
table.editCellAt(row, 1);
}
}).setRemoveAction(new AnActionButtonRunnable() {
@Override
public void run(AnActionButton button) {
TableUtil.removeSelectedItems(table);
table.repaint();
}
})
.setRemoveActionUpdater(new AnActionButtonUpdater() {
@Override
public boolean isEnabled(AnActionEvent e) {
return table.getSelectedRow() >= 0;
}
})
.setButtonComparator("Add", "Remove");
JPanel panel = new JPanel(new BorderLayout());
panel.add(SeparatorFactory.createSeparator("Mark class as reachable if name matches", null), BorderLayout.NORTH);
panel.add(toolbarDecorator.createPanel(), BorderLayout.CENTER);
return panel;
}
@Override
protected void doOKAction() {
myPatterns.clear();
myPatterns.addAll(myModifiedPatterns);
DaemonCodeAnalyzer.getInstance(myProject).restart();
super.doOKAction();
}
private JBTable createTableForPatterns() {
TableModel dataModel = new MyTableModel();
final JBTable result = new JBTable(dataModel);
result.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
TableCellEditor editor = result.getDefaultEditor(String.class);
if (editor instanceof DefaultCellEditor) {
((DefaultCellEditor)editor).setClickCountToStart(1);
}
final TableColumn column = result.getTableHeader().getColumnModel().getColumn(0);
column.setResizable(false);
final int width = 15 + result.getTableHeader().getFontMetrics(result.getTableHeader().getFont()).stringWidth(result.getColumnName(0));
column.setMaxWidth(width);
column.setMinWidth(width);
return result;
}
private class MyTableModel extends AbstractTableModel implements ItemRemovable {
private final String[] myNames;
public MyTableModel() {
myNames = new String[] {"With Subclasses", "Class"};
}
public int getColumnCount() {
return 2;
}
public int getRowCount() {
return myModifiedPatterns.size();
}
@Nullable
public Object getValueAt(int row, int col) {
if (row < 0) return null;
final EntryPointsManagerBase.ClassPattern classPattern = myModifiedPatterns.get(row);
if (classPattern == null) return null;
if (col == 0) {
return classPattern.hierarchically;
}
return classPattern.pattern;
}
public String getColumnName(int column) {
return myNames[column];
}
public Class getColumnClass(int col) {
if (col == 0) {
return Boolean.class;
}
if (col == 1) {
return String.class;
}
throw new IllegalArgumentException(String.valueOf(col));
}
public boolean isCellEditable(int row, int col) {
return true;
}
public void setValueAt(Object aValue, int row, int col) {
EntryPointsManagerBase.ClassPattern classPattern = myModifiedPatterns.get(row);
if (classPattern == null) return;
if (col == 0) {
classPattern.hierarchically = (boolean)aValue;
}
else {
classPattern.pattern = (String)aValue;
}
fireTableRowsUpdated(row, row);
}
@Override
public void removeRow(int idx) {
myModifiedPatterns.remove(idx);
}
}
}
@@ -70,6 +70,11 @@ public class EntryPointsManagerImpl extends EntryPointsManagerBase implements Pe
}.show();
}
@Override
public void configureEntryClassPatterns() {
new ConfigureClassPatternsDialog(getPatterns(), myProject).show();
}
@Override
public JButton createConfigureAnnotationsBtn() {
return createConfigureAnnotationsButton();
@@ -85,4 +90,15 @@ public class EntryPointsManagerImpl extends EntryPointsManagerBase implements Pe
});
return configureAnnotations;
}
}
public static JButton createConfigureClassPatternsButton() {
final JButton configureAnnotations = new JButton("Configure class patterns...");
configureAnnotations.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
getInstance(ProjectUtil.guessCurrentProject(configureAnnotations)).configureEntryClassPatterns();
}
});
return configureAnnotations;
}
}
@@ -0,0 +1,85 @@
/*
* Copyright 2000-2016 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.codeInspection;
import com.intellij.codeInspection.deadCode.UnusedDeclarationInspection
import com.intellij.codeInspection.ex.EntryPointsManagerBase
import com.intellij.codeInspection.ex.InspectionManagerEx
import com.intellij.codeInspection.reference.RefClass
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase
class UnusedDeclarationClassPatternsTest : LightCodeInsightFixtureTestCase() {
fun testClassPattern() {
val unusedDeclarationInspection = UnusedDeclarationInspection(true)
myFixture.enableInspections(unusedDeclarationInspection)
val classPattern = EntryPointsManagerBase.ClassPattern()
classPattern.hierarchically = true;
classPattern.pattern = "java.lang.Runnable"
val patterns = EntryPointsManagerBase.getInstance(project).patterns
try {
patterns.add(classPattern)
myFixture.configureByText("C.java", "public abstract class C implements Runnable {}")
myFixture.checkHighlighting()
}
finally {
patterns.remove(classPattern)
myFixture.disableInspections(unusedDeclarationInspection)
}
}
fun testNoClassPattern() {
val unusedDeclarationInspection = UnusedDeclarationInspection(true)
try {
myFixture.enableInspections(unusedDeclarationInspection)
myFixture.configureByText("C.java", "public abstract class <warning descr=\"Class 'C' is never used\">C</warning> implements Runnable {}")
myFixture.checkHighlighting()
}
finally {
myFixture.disableInspections(unusedDeclarationInspection)
}
}
fun testAddEntryPoint() {
val aClass = myFixture.addClass("public class Foo {}")
val entryPointsManager = EntryPointsManagerBase.getInstance(project)
val context = (InspectionManager.getInstance(project) as InspectionManagerEx).createNewGlobalContext(false)
try {
val refClass = context.refManager.getReference(aClass)
assertNotNull(refClass)
val patterns = entryPointsManager.patterns
assertEmpty(patterns)
//add class as entry point
entryPointsManager.addEntryPoint(refClass!!, true)
assertSize(1, patterns)
assertEquals("Foo", patterns[0].pattern)
assertEmpty(entryPointsManager.entryPoints)
//remove class entry point with constructors - ensure nothing is left in the entries
entryPointsManager.removeEntryPoint(refClass)
for (constructor in (refClass as RefClass).constructors) {
entryPointsManager.removeEntryPoint(constructor)
}
assertEmpty(patterns)
assertEmpty(entryPointsManager.entryPoints)
}
finally {
context.cleanup()
}
}
}
@@ -52,6 +52,8 @@ public abstract class EntryPointsManager implements Disposable {
public abstract void configureAnnotations();
public abstract void configureEntryClassPatterns();
/**
* {@link com.intellij.codeInspection.ex.EntryPointsManagerImpl#createConfigureAnnotationsButton()} should be used instead
*/