blob: 4eed3a20ae67e3ec99bb76873b424c02e1e5ae5b [file] [log] [blame]
/*
* 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.codeInspection.ex;
import com.intellij.ToolExtensionPoints;
import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.codeInspection.reference.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.*;
import com.intellij.openapi.extensions.ExtensionPoint;
import com.intellij.openapi.extensions.ExtensionPointListener;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.extensions.PluginDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.*;
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.util.containers.ContainerUtil;
import com.intellij.util.ui.UIUtil;
import org.jdom.Element;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
@State(
name = "EntryPointsManager",
storages = {@Storage( file = StoragePathMacros.PROJECT_FILE)}
)
public abstract class EntryPointsManagerBase extends EntryPointsManager implements PersistentStateComponent<Element> {
@NonNls private static final String[] STANDARD_ANNOS = {
"javax.ws.rs.*",
};
// null means uninitialized
private volatile List<String> ADDITIONAL_ANNOS;
public Collection<String> getAdditionalAnnotations() {
List<String> annos = ADDITIONAL_ANNOS;
if (annos == null) {
annos = new ArrayList<String>();
Collections.addAll(annos, STANDARD_ANNOS);
final EntryPoint[] extensions = Extensions.getExtensions(ToolExtensionPoints.DEAD_CODE_TOOL, null);
for (EntryPoint extension : extensions) {
final String[] ignoredAnnotations = extension.getIgnoreAnnotations();
if (ignoredAnnotations != null) {
ContainerUtil.addAll(annos, ignoredAnnotations);
}
}
ADDITIONAL_ANNOS = annos = Collections.unmodifiableList(annos);
}
return annos;
}
public JDOMExternalizableStringList ADDITIONAL_ANNOTATIONS = new JDOMExternalizableStringList();
private final Map<String, SmartRefElementPointer> myPersistentEntryPoints;
private final Set<RefElement> myTemporaryEntryPoints;
private static final String VERSION = "2.0";
@NonNls private static final String VERSION_ATTR = "version";
@NonNls private static final String ENTRY_POINT_ATTR = "entry_point";
private boolean myAddNonJavaEntries = true;
private boolean myResolved = false;
protected final Project myProject;
private long myLastModificationCount = -1;
public EntryPointsManagerBase(Project project) {
myProject = project;
myTemporaryEntryPoints = new HashSet<RefElement>();
myPersistentEntryPoints =
new LinkedHashMap<String, SmartRefElementPointer>(); // To keep the order between readExternal to writeExternal
Disposer.register(project, this);
final ExtensionPoint<EntryPoint> point = Extensions.getRootArea().getExtensionPoint(ToolExtensionPoints.DEAD_CODE_TOOL);
point.addExtensionPointListener(new ExtensionPointListener<EntryPoint>() {
@Override
public void extensionAdded(@NotNull EntryPoint extension, @Nullable PluginDescriptor pluginDescriptor) {
extensionRemoved(extension, pluginDescriptor);
}
@Override
public void extensionRemoved(@NotNull EntryPoint extension, @Nullable PluginDescriptor pluginDescriptor) {
if (ADDITIONAL_ANNOS != null) {
ADDITIONAL_ANNOS = null;
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
if (ApplicationManager.getApplication().isDisposed()) return;
InspectionProfileManager.getInstance().fireProfileChanged(null);
}
});
}
}
}, this);
}
public static EntryPointsManagerBase getInstance(Project project) {
return (EntryPointsManagerBase)ServiceManager.getService(project, EntryPointsManager.class);
}
@Override
@SuppressWarnings({"HardCodedStringLiteral"})
public void loadState(Element element) {
Element entryPointsElement = element.getChild("entry_points");
if (entryPointsElement != null) {
final String version = entryPointsElement.getAttributeValue(VERSION_ATTR);
if (!Comparing.strEqual(version, VERSION)) {
convert(entryPointsElement, myPersistentEntryPoints);
}
else {
List content = entryPointsElement.getChildren();
for (final Object aContent : content) {
Element entryElement = (Element)aContent;
if (ENTRY_POINT_ATTR.equals(entryElement.getName())) {
SmartRefElementPointerImpl entryPoint = new SmartRefElementPointerImpl(entryElement);
myPersistentEntryPoints.put(entryPoint.getFQName(), entryPoint);
}
}
}
}
try {
ADDITIONAL_ANNOTATIONS.readExternal(element);
}
catch (InvalidDataException ignored) {
}
}
@Override
@SuppressWarnings({"HardCodedStringLiteral"})
public Element getState() {
Element element = new Element("state");
writeExternal(element, myPersistentEntryPoints, ADDITIONAL_ANNOTATIONS);
return element;
}
@SuppressWarnings({"HardCodedStringLiteral"})
public static void writeExternal(final Element element,
final Map<String, SmartRefElementPointer> persistentEntryPoints,
final JDOMExternalizableStringList additional_annotations) {
Element entryPointsElement = new Element("entry_points");
entryPointsElement.setAttribute(VERSION_ATTR, VERSION);
for (SmartRefElementPointer entryPoint : persistentEntryPoints.values()) {
assert entryPoint.isPersistent();
entryPoint.writeExternal(entryPointsElement);
}
element.addContent(entryPointsElement);
if (!additional_annotations.isEmpty()) {
try {
additional_annotations.writeExternal(element);
}
catch (WriteExternalException ignored) {
}
}
}
@Override
public void resolveEntryPoints(@NotNull final RefManager manager) {
if (!myResolved) {
myResolved = true;
cleanup();
validateEntryPoints();
ApplicationManager.getApplication().runReadAction(new Runnable() {
@Override
public void run() {
for (SmartRefElementPointer entryPoint : myPersistentEntryPoints.values()) {
if (entryPoint.resolve(manager)) {
RefEntity refElement = entryPoint.getRefElement();
((RefElementImpl)refElement).setEntry(true);
((RefElementImpl)refElement).setPermanentEntry(entryPoint.isPersistent());
}
}
}
});
}
}
private void purgeTemporaryEntryPoints() {
for (RefElement entryPoint : myTemporaryEntryPoints) {
((RefElementImpl)entryPoint).setEntry(false);
}
myTemporaryEntryPoints.clear();
}
@Override
public void addEntryPoint(@NotNull RefElement newEntryPoint, boolean isPersistent) {
if (!newEntryPoint.isValid()) return;
if (newEntryPoint instanceof RefClass) {
RefClass refClass = (RefClass)newEntryPoint;
if (refClass.isAnonymous()) {
// Anonymous class cannot be an entry point.
return;
}
List<RefMethod> refConstructors = refClass.getConstructors();
if (refConstructors.size() == 1) {
addEntryPoint(refConstructors.get(0), isPersistent);
return;
}
else if (refConstructors.size() > 1) {
// Many constructors here. Need to ask user which ones are used
for (int i = 0; i < refConstructors.size(); i++) {
addEntryPoint(refConstructors.get(i), isPersistent);
}
return;
}
}
if (!isPersistent) {
myTemporaryEntryPoints.add(newEntryPoint);
((RefElementImpl)newEntryPoint).setEntry(true);
}
else {
if (myPersistentEntryPoints.get(newEntryPoint.getExternalName()) == null) {
final SmartRefElementPointerImpl entry = new SmartRefElementPointerImpl(newEntryPoint, true);
myPersistentEntryPoints.put(entry.getFQName(), entry);
((RefElementImpl)newEntryPoint).setEntry(true);
((RefElementImpl)newEntryPoint).setPermanentEntry(true);
if (entry.isPersistent()) { //do save entry points
final EntryPointsManager entryPointsManager = getInstance(newEntryPoint.getElement().getProject());
if (this != entryPointsManager) {
entryPointsManager.addEntryPoint(newEntryPoint, true);
}
}
}
}
}
@Override
public void removeEntryPoint(@NotNull RefElement anEntryPoint) {
if (anEntryPoint instanceof RefClass) {
RefClass refClass = (RefClass)anEntryPoint;
if (!refClass.isInterface()) {
anEntryPoint = refClass.getDefaultConstructor();
}
}
if (anEntryPoint == null) return;
myTemporaryEntryPoints.remove(anEntryPoint);
Set<Map.Entry<String, SmartRefElementPointer>> set = myPersistentEntryPoints.entrySet();
String key = null;
for (Map.Entry<String, SmartRefElementPointer> entry : set) {
SmartRefElementPointer value = entry.getValue();
if (value.getRefElement() == anEntryPoint) {
key = entry.getKey();
break;
}
}
if (key != null) {
myPersistentEntryPoints.remove(key);
((RefElementImpl)anEntryPoint).setEntry(false);
}
if (anEntryPoint.isPermanentEntry() && anEntryPoint.isValid()) {
final Project project = anEntryPoint.getElement().getProject();
final EntryPointsManager entryPointsManager = getInstance(project);
if (this != entryPointsManager) {
entryPointsManager.removeEntryPoint(anEntryPoint);
}
}
}
@NotNull
@Override
public RefElement[] getEntryPoints() {
validateEntryPoints();
List<RefElement> entries = new ArrayList<RefElement>();
Collection<SmartRefElementPointer> collection = myPersistentEntryPoints.values();
for (SmartRefElementPointer refElementPointer : collection) {
final RefEntity elt = refElementPointer.getRefElement();
if (elt instanceof RefElement) {
entries.add((RefElement)elt);
}
}
entries.addAll(myTemporaryEntryPoints);
return entries.toArray(new RefElement[entries.size()]);
}
@Override
public void dispose() {
cleanup();
}
private void validateEntryPoints() {
long count = PsiManager.getInstance(myProject).getModificationTracker().getModificationCount();
if (count != myLastModificationCount) {
myLastModificationCount = count;
Collection<SmartRefElementPointer> collection = myPersistentEntryPoints.values();
SmartRefElementPointer[] entries = collection.toArray(new SmartRefElementPointer[collection.size()]);
for (SmartRefElementPointer entry : entries) {
RefElement refElement = (RefElement)entry.getRefElement();
if (refElement != null && !refElement.isValid()) {
myPersistentEntryPoints.remove(entry.getFQName());
}
}
final Iterator<RefElement> it = myTemporaryEntryPoints.iterator();
while (it.hasNext()) {
RefElement refElement = it.next();
if (!refElement.isValid()) {
it.remove();
}
}
}
}
@Override
public void cleanup() {
purgeTemporaryEntryPoints();
Collection<SmartRefElementPointer> entries = myPersistentEntryPoints.values();
for (SmartRefElementPointer entry : entries) {
entry.freeReference();
}
}
@Override
public boolean isAddNonJavaEntries() {
return myAddNonJavaEntries;
}
public void addAllPersistentEntries(EntryPointsManagerBase manager) {
myPersistentEntryPoints.putAll(manager.myPersistentEntryPoints);
}
public static void convert(Element element, final Map<String, SmartRefElementPointer> persistentEntryPoints) {
List content = element.getChildren();
for (final Object aContent : content) {
Element entryElement = (Element)aContent;
if (ENTRY_POINT_ATTR.equals(entryElement.getName())) {
String fqName = entryElement.getAttributeValue(SmartRefElementPointerImpl.FQNAME_ATTR);
final String type = entryElement.getAttributeValue(SmartRefElementPointerImpl.TYPE_ATTR);
if (Comparing.strEqual(type, RefJavaManager.METHOD)) {
int spaceIdx = fqName.indexOf(' ');
int lastDotIdx = fqName.lastIndexOf('.');
int parenIndex = fqName.indexOf('(');
while (lastDotIdx > parenIndex) lastDotIdx = fqName.lastIndexOf('.', lastDotIdx - 1);
boolean notype = false;
if (spaceIdx < 0 || spaceIdx + 1 > lastDotIdx || spaceIdx > parenIndex) {
notype = true;
}
final String className = fqName.substring(notype ? 0 : spaceIdx + 1, lastDotIdx);
final String methodSignature =
notype ? fqName.substring(lastDotIdx + 1) : fqName.substring(0, spaceIdx) + ' ' + fqName.substring(lastDotIdx + 1);
fqName = className + " " + methodSignature;
}
else if (Comparing.strEqual(type, RefJavaManager.FIELD)) {
final int lastDotIdx = fqName.lastIndexOf('.');
if (lastDotIdx > 0 && lastDotIdx < fqName.length() - 2) {
String className = fqName.substring(0, lastDotIdx);
String fieldName = fqName.substring(lastDotIdx + 1);
fqName = className + " " + fieldName;
}
else {
continue;
}
}
SmartRefElementPointerImpl entryPoint = new SmartRefElementPointerImpl(type, fqName);
persistentEntryPoints.put(entryPoint.getFQName(), entryPoint);
}
}
}
public void setAddNonJavaEntries(final boolean addNonJavaEntries) {
myAddNonJavaEntries = addNonJavaEntries;
}
@Override
public boolean isEntryPoint(@NotNull PsiElement element) {
if (!(element instanceof PsiModifierListOwner)) return false;
PsiModifierListOwner owner = (PsiModifierListOwner)element;
if (!ADDITIONAL_ANNOTATIONS.isEmpty() && ADDITIONAL_ANNOTATIONS.contains(Deprecated.class.getName()) &&
element instanceof PsiDocCommentOwner && ((PsiDocCommentOwner)element).isDeprecated()) {
return true;
}
return AnnotationUtil.checkAnnotatedUsingPatterns(owner, ADDITIONAL_ANNOTATIONS) ||
AnnotationUtil.checkAnnotatedUsingPatterns(owner, getAdditionalAnnotations());
}
}