blob: fc2bee0722dee543e06ef9290793f5aaa037b960 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2017 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.ui;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceStatus;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.IBaseLabelProvider;
import org.eclipse.jface.viewers.IDecoration;
import org.eclipse.jface.viewers.ILabelDecorator;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ILightweightLabelDecorator;
import org.eclipse.jface.viewers.LabelProviderChangedEvent;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.progress.WorkbenchJob;
import org.eclipse.ui.texteditor.MarkerAnnotation;
import org.eclipse.jdt.core.IClasspathAttribute;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaModelMarker;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.ISourceRange;
import org.eclipse.jdt.core.ISourceReference;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.launching.JavaRuntime;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.JavaPluginImages;
import org.eclipse.jdt.internal.ui.viewsupport.IProblemChangedListener;
import org.eclipse.jdt.internal.ui.viewsupport.ImageDescriptorRegistry;
import org.eclipse.jdt.internal.ui.viewsupport.ImageImageDescriptor;
/**
* LabelDecorator that decorates an element's image with error and warning overlays that
* represent the severity of markers attached to the element's underlying resource. To see
* a problem decoration for a marker, the marker needs to be a subtype of <code>IMarker.PROBLEM</code>.
* <p>
* <b>Important</b>: Although this decorator implements ILightweightLabelDecorator, do not contribute this
* class as a decorator to the <code>org.eclipse.ui.decorators</code> extension. Only use this class in your
* own views and label providers.
*
* @since 2.0
*/
public class ProblemsLabelDecorator implements ILabelDecorator, ILightweightLabelDecorator {
/**
* This is a special <code>LabelProviderChangedEvent</code> carrying additional
* information whether the event origins from a maker change.
* <p>
* <code>ProblemsLabelChangedEvent</code>s are only generated by <code>
* ProblemsLabelDecorator</code>s.
* </p>
*/
public static class ProblemsLabelChangedEvent extends LabelProviderChangedEvent {
private static final long serialVersionUID= 1L;
private boolean fMarkerChange;
/**
* @param eventSource the base label provider
* @param changedResource the changed resources
* @param isMarkerChange <code>true</code> if the change is a marker change; otherwise
* <code>false</code>
*/
public ProblemsLabelChangedEvent(IBaseLabelProvider eventSource, IResource[] changedResource, boolean isMarkerChange) {
super(eventSource, changedResource);
fMarkerChange= isMarkerChange;
}
/**
* Returns whether this event origins from marker changes. If <code>false</code> an annotation
* model change is the origin. In this case viewers not displaying working copies can ignore these
* events.
*
* @return if this event origins from a marker change.
*/
public boolean isMarkerChange() {
return fMarkerChange;
}
}
private static final int ERRORTICK_WARNING= JavaElementImageDescriptor.WARNING;
private static final int ERRORTICK_ERROR= JavaElementImageDescriptor.ERROR;
private static final int ERRORTICK_BUILDPATH_ERROR= JavaElementImageDescriptor.BUILDPATH_ERROR;
private static final int ERRORTICK_IGNORE_OPTIONAL_PROBLEMS= JavaElementImageDescriptor.IGNORE_OPTIONAL_PROBLEMS;
private static final int ERRORTICK_INFO= JavaElementImageDescriptor.INFO;
static final boolean DEBUG = false;
private ImageDescriptorRegistry fRegistry;
private boolean fUseNewRegistry= false;
private IProblemChangedListener fProblemChangedListener;
private ListenerList<ILabelProviderListener> fListeners;
private ISourceRange fCachedRange;
/** job to update adornments for container resources in UI thread */
private final AdornmentUpdateJob adornmentUpdateJob;
/**
* Creates a new <code>ProblemsLabelDecorator</code>.
*/
public ProblemsLabelDecorator() {
this(null);
fUseNewRegistry= true;
}
/**
* Note: This constructor is for internal use only. Clients should not call this constructor.
*
* @param registry The registry to use or <code>null</code> to use the Java plugin's image
* registry
* @noreference This constructor is not intended to be referenced by clients.
*/
public ProblemsLabelDecorator(ImageDescriptorRegistry registry) {
fRegistry= registry;
adornmentUpdateJob = new AdornmentUpdateJob();
AdornmentCacheManager.register(this);
}
private ImageDescriptorRegistry getRegistry() {
if (fRegistry == null) {
fRegistry= fUseNewRegistry ? new ImageDescriptorRegistry() : JavaPlugin.getImageDescriptorRegistry();
}
return fRegistry;
}
@Override
public String decorateText(String text, Object element) {
return text;
}
@Override
public Image decorateImage(Image image, Object obj) {
if (image == null)
return null;
int adornmentFlags= computeAdornmentFlags(obj);
if (adornmentFlags != 0) {
ImageDescriptor baseImage= new ImageImageDescriptor(image);
Rectangle bounds= image.getBounds();
return getRegistry().get(new JavaElementImageDescriptor(baseImage, adornmentFlags, new Point(bounds.width, bounds.height)));
}
return image;
}
/**
* Computes the adornment flags for the given element.
*
* @param obj the element to compute the flags for
*
* @return the adornment flags
*/
protected int computeAdornmentFlags(Object obj) {
try {
if (obj instanceof IJavaElement) {
IJavaElement element= (IJavaElement) obj;
int type= element.getElementType();
switch (type) {
case IJavaElement.JAVA_MODEL:
case IJavaElement.JAVA_PROJECT:
case IJavaElement.PACKAGE_FRAGMENT_ROOT:
int flags= computeContainerAdornmentFlags(element.getResource());
switch (type) {
case IJavaElement.PACKAGE_FRAGMENT_ROOT:
IPackageFragmentRoot root= (IPackageFragmentRoot) element;
if (flags != ERRORTICK_ERROR && root.getKind() == IPackageFragmentRoot.K_SOURCE && isIgnoringOptionalProblems(root.getRawClasspathEntry())) {
flags= ERRORTICK_IGNORE_OPTIONAL_PROBLEMS;
}
break;
case IJavaElement.JAVA_PROJECT:
IJavaProject project= (IJavaProject) element;
if (flags != ERRORTICK_ERROR && flags != ERRORTICK_BUILDPATH_ERROR && isIgnoringOptionalProblems(project)) {
flags= ERRORTICK_IGNORE_OPTIONAL_PROBLEMS;
}
break;
}
return flags;
case IJavaElement.PACKAGE_FRAGMENT:
return getPackageErrorTicksFromMarkers((IPackageFragment) element);
case IJavaElement.COMPILATION_UNIT:
case IJavaElement.CLASS_FILE:
return getErrorTicksFromMarkers(element.getResource(), IResource.DEPTH_ONE);
case IJavaElement.PACKAGE_DECLARATION:
case IJavaElement.IMPORT_DECLARATION:
case IJavaElement.IMPORT_CONTAINER:
case IJavaElement.TYPE:
case IJavaElement.INITIALIZER:
case IJavaElement.METHOD:
case IJavaElement.FIELD:
case IJavaElement.LOCAL_VARIABLE:
ICompilationUnit cu= (ICompilationUnit) element.getAncestor(IJavaElement.COMPILATION_UNIT);
if (cu != null) {
ISourceReference ref= (type == IJavaElement.COMPILATION_UNIT) ? null : (ISourceReference) element;
// The assumption is that only source elements in compilation unit can have markers
IAnnotationModel model= isInJavaAnnotationModel(cu);
int result= 0;
if (model != null) {
// open in Java editor: look at annotation model
result= getErrorTicksFromAnnotationModel(model, ref);
} else {
if (ref == null) {
result= getErrorTicksFromMarkers(cu.getResource(), IResource.DEPTH_ONE);
} else {
result= getErrorTicksFromMarkers(cu.getResource(), IResource.DEPTH_ONE, ref);
}
}
fCachedRange= null;
return result;
}
break;
default:
}
} else if (obj instanceof IResource) {
if (obj instanceof IProject || obj instanceof IWorkspaceRoot) {
return computeContainerAdornmentFlags((IResource) obj);
}
if (obj instanceof IFolder) {
IFolder folder = (IFolder) obj;
// Only cache top level directories to avoid caching everything
if (folder.getParent() instanceof IProject) {
return computeContainerAdornmentFlags((IResource) obj);
}
}
return getErrorTicksFromMarkers((IResource) obj, IResource.DEPTH_INFINITE);
}
} catch (CoreException e) {
if (e instanceof JavaModelException) {
if (((JavaModelException) e).isDoesNotExist()) {
return 0;
}
}
int errorCode = e.getStatus().getCode();
if (errorCode == IResourceStatus.MARKER_NOT_FOUND || errorCode == IResourceStatus.RESOURCE_NOT_FOUND) {
return 0;
}
JavaPlugin.log(e);
}
return 0;
}
final class AdornmentUpdateJob extends WorkbenchJob {
private final Set<IResource> queue;
public AdornmentUpdateJob() {
super("Java problems decoration update..."); //$NON-NLS-1$
this.queue = ConcurrentHashMap.newKeySet();
setSystem(true);
setPriority(DECORATE);
}
@Override
public boolean belongsTo(Object family) {
return ProblemsLabelDecorator.class == family;
}
@Override
public IStatus runInUIThread(IProgressMonitor monitor) {
List<IResource> changed = new ArrayList<>(queue);
queue.removeAll(changed);
IResource[] changes = changed.toArray(IResource[]::new);
if (changes.length > 0 && !monitor.isCanceled()) {
if (DEBUG) {
String prefix = ProblemsLabelDecorator.this.toString();
prefix = prefix.substring(prefix.lastIndexOf('.') + 1);
System.err.println(prefix + " : " + " :show: " + Arrays.toString(changes)); //$NON-NLS-1$ //$NON-NLS-2$
}
fireProblemsChanged(changes, true);
}
if (monitor.isCanceled()) {
queue.clear();
return Status.CANCEL_STATUS;
} else if (!queue.isEmpty()) {
schedule(100);
}
return Status.OK_STATUS;
}
void schedule(Set<IResource> tasks) {
if (queue.addAll(tasks)) {
schedule(100);
}
}
}
static final class AdornmentCacheManager {
static final AdornmentCacheManager instance = new AdornmentCacheManager();
final Set<ProblemsLabelDecorator> listeners;
/**
* Cache for projects and source folders status, key is resource, value is known adornment flags
*/
final Map<IResource, Integer> adornmentCache;
/** Job to compute adornments for container resources in background */
final AdornmentCalculationJob adornmentJob;
public AdornmentCacheManager() {
adornmentCache = new ConcurrentHashMap<>();
adornmentJob = new AdornmentCalculationJob();
listeners = Collections.synchronizedSet(new LinkedHashSet<>());
}
static void scheduleTask(IResource resource, AdornmentUpdateJob uiUpdate) {
instance.adornmentJob.schedule(new AdornmentTask(resource), uiUpdate);
}
static Integer getAdornment(IResource resource) {
return instance.adornmentCache.get(resource);
}
static Integer setAdornment(IResource resource, int adornment) {
return instance.adornmentCache.put(resource, Integer.valueOf(adornment));
}
static void register(ProblemsLabelDecorator decorator) {
instance.listeners.add(decorator);
}
static void deregister(ProblemsLabelDecorator decorator) {
instance.listeners.remove(decorator);
if(instance.listeners.isEmpty()) {
instance.adornmentJob.cancel();
instance.adornmentCache.clear();
}
}
}
static final class AdornmentCalculationJob extends Job {
private final LinkedHashMap<AdornmentTask, Set<AdornmentUpdateJob>> queue;
public AdornmentCalculationJob() {
super("Java problems decoration calculation..."); //$NON-NLS-1$
this.queue = new LinkedHashMap<>();
setSystem(true);
setPriority(DECORATE);
}
@Override
public boolean belongsTo(Object family) {
return ProblemsLabelDecorator.class == family;
}
@Override
protected IStatus run(IProgressMonitor monitor) {
Map<AdornmentUpdateJob, Set<IResource>> changed = new LinkedHashMap<>();
Entry<AdornmentTask, Set<AdornmentUpdateJob>> next;
while ((next = poll()) != null && !monitor.isCanceled()) {
AdornmentTask task = next.getKey();
task.run();
if (DEBUG) {
System.out.println("calc : " + AdornmentCacheManager.instance.adornmentCache.size() + " : " + task.resource); //$NON-NLS-1$ //$NON-NLS-2$
}
if (task.isAdornmentChanged()) {
final IResource resource = task.resource;
Set<AdornmentUpdateJob> jobs = next.getValue();
for (AdornmentUpdateJob job : jobs) {
changed.compute(job, (k, v) -> {
if(v == null) {
v = new LinkedHashSet<>();
}
v.add(resource);
return v;
});
}
}
}
if (!changed.isEmpty() && !monitor.isCanceled()) {
for (Entry<AdornmentUpdateJob, Set<IResource>> entry : changed.entrySet()) {
AdornmentUpdateJob job = entry.getKey();
Set<IResource> resources = entry.getValue();
job.schedule(resources);
}
}
synchronized (queue) {
if (monitor.isCanceled()) {
queue.clear();
return Status.CANCEL_STATUS;
} else if (!queue.isEmpty()) {
schedule(100);
}
}
return Status.OK_STATUS;
}
private Entry<AdornmentTask, Set<AdornmentUpdateJob>> poll() {
Entry<AdornmentTask, Set<AdornmentUpdateJob>> next = null;
synchronized (queue) {
if (!queue.isEmpty()) {
Iterator<Entry<AdornmentTask, Set<AdornmentUpdateJob>>> iterator = queue.entrySet().iterator();
next = iterator.next();
iterator.remove();
}
}
return next;
}
void schedule(AdornmentTask task, AdornmentUpdateJob job) {
synchronized (queue) {
queue.compute(task, (k,v) -> {
if (v == null) {
v = new LinkedHashSet<>();
}
if (v.add(job)) {
schedule(100);
}
return v;
});
}
}
}
static final class AdornmentTask {
final IResource resource;
volatile int oldAdornment;
volatile int newAdornment;
public AdornmentTask(IResource resource){
this.resource = resource;
}
@Override
public int hashCode() {
return resource.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof AdornmentTask)) {
return false;
}
AdornmentTask other = (AdornmentTask) obj;
return resource.equals(other.resource);
}
void run() {
try {
newAdornment = getErrorTicksFromMarkers(resource, IResource.DEPTH_INFINITE);
} catch (CoreException e) {
boolean shouldLog = true;
if (e instanceof JavaModelException) {
if (((JavaModelException) e).isDoesNotExist()) {
newAdornment = 0;
shouldLog = false;
}
} else {
int errorCode = e.getStatus().getCode();
if (errorCode == IResourceStatus.MARKER_NOT_FOUND || errorCode == IResourceStatus.RESOURCE_NOT_FOUND) {
newAdornment = 0;
shouldLog = false;
}
}
if (shouldLog) {
JavaPlugin.log(e);
}
} finally {
Integer old = AdornmentCacheManager.setAdornment(resource, newAdornment);
if (old != null) {
oldAdornment = old.intValue();
}
}
}
boolean isAdornmentChanged() {
return newAdornment != oldAdornment;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("AdornmentTask ["); //$NON-NLS-1$
if (resource != null) {
builder.append("resource="); //$NON-NLS-1$
builder.append(resource);
builder.append(", "); //$NON-NLS-1$
}
builder.append("newAdornment="); //$NON-NLS-1$
builder.append(newAdornment);
builder.append(", oldAdornment="); //$NON-NLS-1$
builder.append(oldAdornment);
builder.append("]"); //$NON-NLS-1$
return builder.toString();
}
}
private int computeContainerAdornmentFlags(IResource resource) {
if (resource == null) {
return 0;
}
Integer cachedAdornment = AdornmentCacheManager.getAdornment(resource);
int adornment = cachedAdornment != null ? cachedAdornment.intValue() : 0;
AdornmentCacheManager.scheduleTask(resource, adornmentUpdateJob);
return adornment;
}
private boolean isIgnoringOptionalProblems(IClasspathEntry entry) {
if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
for (IClasspathAttribute attrib : entry.getExtraAttributes()) {
if (IClasspathAttribute.IGNORE_OPTIONAL_PROBLEMS.equals(attrib.getName())) {
return "true".equals(attrib.getValue()); //$NON-NLS-1$
}
}
}
return false;
}
private boolean isIgnoringOptionalProblems(IJavaProject project) throws JavaModelException {
IPath projectPath= project.getPath();
IClasspathEntry[] rawClasspath= project.getRawClasspath();
for (IClasspathEntry entry : rawClasspath) {
if (entry.getEntryKind() == IClasspathEntry.CPE_SOURCE && projectPath.equals(entry.getPath()) && isIgnoringOptionalProblems(entry))
return true;
}
return false;
}
private static int getErrorTicksFromMarkers(IResource res, int depth) throws CoreException {
if (res == null || !res.isAccessible()) {
return 0;
}
int severity= -1;
if (res instanceof IProject) {
severity= res.findMaxProblemSeverity(IJavaModelMarker.BUILDPATH_PROBLEM_MARKER, true, IResource.DEPTH_ZERO);
if (severity == IMarker.SEVERITY_ERROR) {
return ERRORTICK_BUILDPATH_ERROR;
}
severity= res.findMaxProblemSeverity(JavaRuntime.JRE_CONTAINER_MARKER, true, IResource.DEPTH_ZERO);
if (severity == IMarker.SEVERITY_ERROR) {
return ERRORTICK_BUILDPATH_ERROR;
}
}
severity= res.findMaxProblemSeverity(IMarker.PROBLEM, true, depth);
return convertToTick(severity);
}
private int getErrorTicksFromMarkers(IResource res, int depth, ISourceReference sourceElement) throws CoreException {
if (res == null || !res.isAccessible()) {
return 0;
}
int severity= -1;
IMarker[] markers= res.findMarkers(IMarker.PROBLEM, true, depth);
if (markers != null && markers.length > 0) {
for (int i= 0; i < markers.length && (severity != IMarker.SEVERITY_ERROR); i++) {
IMarker curr= markers[i];
if (isMarkerInRange(curr, sourceElement)) {
int val= curr.getAttribute(IMarker.SEVERITY, -1);
if (val == IMarker.SEVERITY_INFO || val == IMarker.SEVERITY_WARNING || val == IMarker.SEVERITY_ERROR) {
severity= Math.max(severity, val);
}
}
}
}
return convertToTick(severity);
}
private static int convertToTick(int severity) {
switch (severity) {
case IMarker.SEVERITY_ERROR:
return ERRORTICK_ERROR;
case IMarker.SEVERITY_WARNING:
return ERRORTICK_WARNING;
case IMarker.SEVERITY_INFO:
return ERRORTICK_INFO;
default:
return 0;
}
}
private int getPackageErrorTicksFromMarkers(IPackageFragment pack) throws CoreException {
// Packages are special: They must not consider markers on subpackages.
IResource res= pack.getResource();
if (res == null || !res.isAccessible()) {
return 0;
}
// markers on package itself (e.g. missing @NonNullByDefault)
int severity= findMaxProblemSeverity(res, IMarker.PROBLEM, true, IResource.DEPTH_ZERO);
if (severity == IMarker.SEVERITY_ERROR)
return ERRORTICK_ERROR;
// markers on CUs
for (ICompilationUnit cu : pack.getCompilationUnits()) {
severity= Math.max(severity, findMaxProblemSeverity(cu.getResource(), IMarker.PROBLEM, true, IResource.DEPTH_ZERO));
if (severity == IMarker.SEVERITY_ERROR)
return ERRORTICK_ERROR;
}
// markers on files and folders
for (Object object : pack.getNonJavaResources()) {
if (object instanceof IResource) {
IResource resource= (IResource) object;
severity= Math.max(severity, findMaxProblemSeverity(resource, IMarker.PROBLEM, true, IResource.DEPTH_INFINITE));
if (severity == IMarker.SEVERITY_ERROR)
return ERRORTICK_ERROR;
}
}
// SEVERITY_ERROR already handled above
if (severity == IMarker.SEVERITY_WARNING) {
return ERRORTICK_WARNING;
}
if (severity == IMarker.SEVERITY_INFO) {
return ERRORTICK_INFO;
}
return 0;
}
private int findMaxProblemSeverity (IResource res, String type, boolean includeSubtypes, int depth) throws CoreException {
try {
return res.findMaxProblemSeverity(type, includeSubtypes, depth);
} catch (CoreException e) {
if (e.getStatus().getCode() == IResourceStatus.RESOURCE_NOT_FOUND) {
// Ignore failure in the case of concurrent deletion
return -1;
}
throw e;
}
}
private boolean isMarkerInRange(IMarker marker, ISourceReference sourceElement) throws CoreException {
if (marker.isSubtypeOf(IMarker.TEXT)) {
int pos= marker.getAttribute(IMarker.CHAR_START, -1);
return isInside(pos, sourceElement);
}
return false;
}
private IAnnotationModel isInJavaAnnotationModel(ICompilationUnit original) {
if (original.isWorkingCopy()) {
FileEditorInput editorInput= new FileEditorInput((IFile) original.getResource());
return JavaPlugin.getDefault().getCompilationUnitDocumentProvider().getAnnotationModel(editorInput);
}
return null;
}
private int getErrorTicksFromAnnotationModel(IAnnotationModel model, ISourceReference sourceElement) throws CoreException {
int info= 0;
int priority= -1;
Iterator<Annotation> iter= model.getAnnotationIterator();
while ((info != ERRORTICK_ERROR) && iter.hasNext()) {
Annotation annot= iter.next();
IMarker marker= isAnnotationInRange(model, annot, sourceElement);
if (marker != null) {
priority= Math.max(priority, marker.getAttribute(IMarker.SEVERITY, -1));
switch (priority) {
case IMarker.SEVERITY_INFO:
info= ERRORTICK_INFO;
break;
case IMarker.SEVERITY_WARNING:
info= ERRORTICK_WARNING;
break;
case IMarker.SEVERITY_ERROR:
info= ERRORTICK_ERROR;
break;
default:
break;
}
}
}
return info;
}
private IMarker isAnnotationInRange(IAnnotationModel model, Annotation annot, ISourceReference sourceElement) throws CoreException {
if (annot instanceof MarkerAnnotation) {
if (sourceElement == null || isInside(model.getPosition(annot), sourceElement)) {
IMarker marker= ((MarkerAnnotation) annot).getMarker();
if (marker.exists() && marker.isSubtypeOf(IMarker.PROBLEM)) {
return marker;
}
}
}
return null;
}
private boolean isInside(Position pos, ISourceReference sourceElement) throws CoreException {
return pos != null && isInside(pos.getOffset(), sourceElement);
}
/**
* Tests if a position is inside the source range of an element.
* @param pos Position to be tested.
* @param sourceElement Source element (must be a IJavaElement)
* @return boolean Return <code>true</code> if position is located inside the source element.
* @throws CoreException Exception thrown if element range could not be accessed.
*
* @since 2.1
*/
protected boolean isInside(int pos, ISourceReference sourceElement) throws CoreException {
if (fCachedRange == null) {
fCachedRange= sourceElement.getSourceRange();
}
ISourceRange range= fCachedRange;
if (range != null) {
int rangeOffset= range.getOffset();
return (rangeOffset <= pos && rangeOffset + range.getLength() > pos);
}
return false;
}
@Override
public void dispose() {
if (fProblemChangedListener != null) {
JavaPlugin.getDefault().getProblemMarkerManager().removeListener(fProblemChangedListener);
fProblemChangedListener= null;
}
if (fRegistry != null && fUseNewRegistry) {
fRegistry.dispose();
}
if (fListeners != null) {
fListeners.clear();
}
AdornmentCacheManager.deregister(this);
adornmentUpdateJob.cancel();
}
@Override
public boolean isLabelProperty(Object element, String property) {
return true;
}
@Override
public void addListener(ILabelProviderListener listener) {
if (fListeners == null) {
fListeners= new ListenerList<>();
}
fListeners.add(listener);
if (fProblemChangedListener == null) {
fProblemChangedListener= this::fireProblemsChanged;
JavaPlugin.getDefault().getProblemMarkerManager().addListener(fProblemChangedListener);
}
}
@Override
public void removeListener(ILabelProviderListener listener) {
if (fListeners != null) {
fListeners.remove(listener);
if (fListeners.isEmpty() && fProblemChangedListener != null) {
JavaPlugin.getDefault().getProblemMarkerManager().removeListener(fProblemChangedListener);
fProblemChangedListener= null;
}
}
}
private void fireProblemsChanged(IResource[] changedResources, boolean isMarkerChange) {
if (fListeners != null && !fListeners.isEmpty()) {
LabelProviderChangedEvent event= new ProblemsLabelChangedEvent(this, changedResources, isMarkerChange);
for (ILabelProviderListener listener : fListeners) {
listener.labelProviderChanged(event);
}
}
}
@Override
public void decorate(Object element, IDecoration decoration) {
int adornmentFlags= computeAdornmentFlags(element);
switch (adornmentFlags) {
case ERRORTICK_ERROR:
decoration.addOverlay(JavaPluginImages.DESC_OVR_ERROR);
break;
case ERRORTICK_BUILDPATH_ERROR:
decoration.addOverlay(JavaPluginImages.DESC_OVR_BUILDPATH_ERROR);
break;
case ERRORTICK_WARNING:
decoration.addOverlay(JavaPluginImages.DESC_OVR_WARNING);
break;
case ERRORTICK_INFO:
decoration.addOverlay(JavaPluginImages.DESC_OVR_INFO);
break;
default:
break;
}
}
}