/*******************************************************************************
 * 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 java.util.concurrent.atomic.AtomicBoolean;

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
					|| errorCode == IResourceStatus.PROJECT_NOT_OPEN) {
				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;
			}
			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;
				}
			}
			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) {
			AtomicBoolean shouldSchedule = new AtomicBoolean(false);
			synchronized (queue) {
				queue.compute(task, (k,v) -> {
					if (v == null) {
						v = new LinkedHashSet<>();
					}
					if (v.add(job)) {
						shouldSchedule.set(true);
					}
					return v;
				});
			}
			if (shouldSchedule.get()) {
				schedule(100);
			}
		}
	}

	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
							|| errorCode == IResourceStatus.PROJECT_NOT_OPEN) {
						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;
		}
	}

}
