| /******************************************************************************* |
| * 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; |
| } |
| } |
| |
| } |