Bug 577558: [Ltk-Model] Add support for problem decoration to image of
ExtModelLabelProvider

  - Add DecoratedElementImageDescriptor

Change-Id: I999452f33a36c60c9baaef7f0d99c972efb39ed8
diff --git a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/LtkUIResources.java b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/LtkUIResources.java
index d814ee3..735cd0d 100644
--- a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/LtkUIResources.java
+++ b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/LtkUIResources.java
@@ -27,6 +27,7 @@
 	
 	private static final String NS= LtkUI.BUNDLE_ID;
 	
+	
 	public static final String OBJ_ERROR_IMAGE_ID= NS + "/image/obj/Error"; //$NON-NLS-1$
 	public static final String OBJ_ERROR_AWAY_IMAGE_ID= NS + "/image/obj/Error.away"; //$NON-NLS-1$
 	public static final String OBJ_WARNING_IMAGE_ID= NS + "/image/obj/Warning"; //$NON-NLS-1$
diff --git a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/util/DecoratedElementImageDescriptor.java b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/util/DecoratedElementImageDescriptor.java
new file mode 100644
index 0000000..8e710cc
--- /dev/null
+++ b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/util/DecoratedElementImageDescriptor.java
@@ -0,0 +1,197 @@
+/*=============================================================================#
+ # Copyright (c) 2000, 2021 IBM Corporation and others.
+ # 
+ # This program and the accompanying materials are made available under the
+ # terms of the Eclipse Public License 2.0 which is available at
+ # https://www.eclipse.org/legal/epl-2.0.
+ # 
+ # SPDX-License-Identifier: EPL-2.0
+ # 
+ # Contributors:
+ #     IBM Corporation - org.eclipse.jdt: initial API and implementation
+ #     Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
+ #=============================================================================*/
+
+package org.eclipse.statet.ltk.ui.util;
+
+import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullAssert;
+
+import org.eclipse.jface.resource.CompositeImageDescriptor;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.swt.graphics.Point;
+
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+import org.eclipse.statet.jcommons.lang.Nullable;
+
+import org.eclipse.statet.ecommons.ui.SharedUIResources;
+
+
+@NonNullByDefault
+public class DecoratedElementImageDescriptor extends CompositeImageDescriptor {
+	
+	
+	/** Flag to render the info adornment. */
+	public final static int INFO=                           0b1 << 0;
+	
+	/** Flag to render the warning adornment. */
+	public final static int WARNING=                        0b1 << 1;
+	
+	/** Flag to render the error adornment. */
+	public final static int ERROR=                          0b1 << 2;
+	
+	
+	/**
+	 * Flag to render the 'deprecated' adornment.
+	 */
+	public final static int DEPRECATED=                     0b1 << 4;
+	
+	/**
+	 * Flag to render the 'ignore optional compile problems' adornment.
+	 */
+	public final static int IGNORE_OPTIONAL_PROBLEMS=       0b1 << 8;
+	
+	
+	private final ImageDescriptor baseImage;
+	private final int flags;
+	
+	private @Nullable Point size;
+	
+	
+	/**
+	 * Creates a new JavaElementImageDescriptor.
+	 *
+	 * @param baseImage an image descriptor used as the base image
+	 * @param flags flags indicating which adornments are to be rendered. See {@link #setAdornments(int)}
+	 * 	for valid values.
+	 * @param size the size of the resulting image
+	 */
+	public DecoratedElementImageDescriptor(final ImageDescriptor baseImage, final int flags) {
+		this.baseImage= nonNullAssert(baseImage);
+		this.flags= flags;
+	}
+	
+	
+	protected final ImageDescriptor getBaseImage() {
+		return this.baseImage;
+	}
+	
+	protected final int getFlags() {
+		return this.flags;
+	}
+	
+	@Override
+	protected final Point getSize() {
+		var size= this.size;
+		if (size == null) {
+			final var data= createCachedImageDataProvider(getBaseImage());
+			size= new Point(data.getWidth(), data.getHeight());
+			this.size= size;
+		}
+		return size;
+	}
+	
+	
+	@Override
+	protected void drawCompositeImage(final int width, final int height) {
+		if ((this.flags & DEPRECATED) != 0) { // draw *behind* the full image
+			final Point size= getSize();
+			final var data= createCachedImageDataProvider(
+					SharedUIResources.getInstance().getImageDescriptor(
+							SharedUIResources.OVR_DEPRECATED_IMAGE_ID ));
+			drawImage(data, 0, size.y - data.getHeight());
+		}
+		
+		{	final var data= createCachedImageDataProvider(getBaseImage());
+			drawImage(data, 0, 0);
+		}
+		
+		drawTopRight();
+		drawBottomRight();
+		drawBottomLeft();
+	}
+	
+	private void addTopRightImage(final ImageDescriptor desc, final Point pos) {
+		final var data= createCachedImageDataProvider(desc);
+		final int x= pos.x - data.getWidth();
+		if (x >= 0) {
+			drawImage(data, x, pos.y);
+			pos.x= x;
+		}
+	}
+	
+	private void addBottomRightImage(final ImageDescriptor desc, final Point pos) {
+		final var data= createCachedImageDataProvider(desc);
+		final int x= pos.x - data.getWidth();
+		final int y= pos.y - data.getHeight();
+		if (x >= 0 && y >= 0) {
+			drawImage(data, x, y);
+			pos.x= x;
+		}
+	}
+	
+	private void addBottomLeftImage(final ImageDescriptor desc, final Point pos) {
+		final var data= createCachedImageDataProvider(desc);
+		final int x= pos.x;
+		final int y= pos.y - data.getHeight();
+		final int xEnd= x + data.getWidth();
+		if (xEnd < getSize().x && y >= 0) {
+			drawImage(data, x, y);
+			pos.x= xEnd;
+		}
+	}
+	
+	
+	private void drawTopRight() {
+	}
+	
+	private void drawBottomRight() {
+	}
+	
+	private void drawBottomLeft() {
+		final Point pos= new Point(0, getSize().y);
+		if ((this.flags & ERROR) != 0) {
+			addBottomLeftImage(
+					SharedUIResources.getInstance().getImageDescriptor(
+							SharedUIResources.OVR_ERROR_IMAGE_ID ),
+					pos );
+		}
+		else if ((this.flags & WARNING) != 0) {
+			addBottomLeftImage(
+					SharedUIResources.getInstance().getImageDescriptor(
+							SharedUIResources.OVR_WARNING_IMAGE_ID ),
+					pos );
+		}
+		else if ((this.flags & INFO) != 0) {
+			addBottomLeftImage(
+					SharedUIResources.getInstance().getImageDescriptor(
+							SharedUIResources.OVR_INFO_IMAGE_ID ),
+					pos );
+		}
+		if ((this.flags & IGNORE_OPTIONAL_PROBLEMS) != 0) {
+			addBottomLeftImage(
+					SharedUIResources.getInstance().getImageDescriptor(
+							SharedUIResources.OVR_IGNORE_OPTIONAL_PROBLEMS_IMAGE_ID ),
+					pos );
+		}
+	}
+	
+	
+	@Override
+	public int hashCode() {
+		return (this.baseImage.hashCode() ^ this.flags);
+	}
+	
+	@Override
+	public boolean equals(final @Nullable Object obj) {
+		if (this == obj) {
+			return true;
+		}
+		if (obj != null && getClass() == obj.getClass()) {
+			final DecoratedElementImageDescriptor other= (DecoratedElementImageDescriptor)obj;
+			return (this.baseImage.equals(other.baseImage)
+					&& this.flags == other.flags );
+		}
+		return false;
+	}
+	
+}
diff --git a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/util/ExtModelLabelProvider.java b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/util/ExtModelLabelProvider.java
index c5d4d2b..4eed447 100644
--- a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/util/ExtModelLabelProvider.java
+++ b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/util/ExtModelLabelProvider.java
@@ -14,22 +14,41 @@
 
 package org.eclipse.statet.ltk.ui.util;
 
+import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullElse;
+
 import java.util.IdentityHashMap;
 
+import org.eclipse.jface.text.Position;
+import org.eclipse.jface.text.source.IAnnotationModel;
 import org.eclipse.jface.viewers.ILabelProvider;
 import org.eclipse.jface.viewers.StyledCellLabelProvider;
 import org.eclipse.jface.viewers.StyledString;
 import org.eclipse.jface.viewers.ViewerCell;
 import org.eclipse.swt.graphics.Image;
+import org.eclipse.ui.texteditor.IDocumentProvider;
 
 import org.eclipse.statet.jcommons.lang.Disposable;
 import org.eclipse.statet.jcommons.lang.NonNullByDefault;
 import org.eclipse.statet.jcommons.lang.Nullable;
+import org.eclipse.statet.jcommons.text.core.TextRegion;
 
+import org.eclipse.statet.ecommons.ui.SharedUIResources;
+import org.eclipse.statet.ecommons.ui.jface.resource.ImageImageDescriptor;
+
+import org.eclipse.statet.ltk.ast.core.AstNode;
+import org.eclipse.statet.ltk.ast.core.Asts;
+import org.eclipse.statet.ltk.core.Ltk;
+import org.eclipse.statet.ltk.issues.core.Issue;
+import org.eclipse.statet.ltk.issues.core.Problem;
+import org.eclipse.statet.ltk.model.core.LtkModelUtils;
 import org.eclipse.statet.ltk.model.core.LtkModels;
 import org.eclipse.statet.ltk.model.core.element.EmbeddingForeignElement;
 import org.eclipse.statet.ltk.model.core.element.LtkModelElement;
+import org.eclipse.statet.ltk.model.core.element.SourceElement;
+import org.eclipse.statet.ltk.model.core.element.SourceStructElement;
+import org.eclipse.statet.ltk.model.core.element.SourceUnit;
 import org.eclipse.statet.ltk.ui.ElementLabelProvider;
+import org.eclipse.statet.ltk.ui.sourceediting.SourceIssueEditorAnnotation;
 
 
 @NonNullByDefault
@@ -45,6 +64,11 @@
 	
 	private @Nullable String modelTypeId;
 	
+	private @Nullable String documentProviderModelTypeId;
+	private @Nullable IDocumentProvider documentProvider;
+	
+	private final SharedUIResources sharedResources= SharedUIResources.getInstance();
+	
 	
 	public ExtModelLabelProvider(final String modelTypeId) {
 		this.modelTypeId= modelTypeId;
@@ -124,6 +148,15 @@
 				image= provider.getImage(embeddedElement);
 			}
 		}
+		if (image != null) {
+			final int adornmentFlags= computeAdornmentFlags(element);
+			if (adornmentFlags != 0) {
+				final var imageDescriptor= new DecoratedElementImageDescriptor(
+						new ImageImageDescriptor(image), adornmentFlags );
+				return this.sharedResources.getImageDescriptorRegistry()
+						.get(imageDescriptor);
+			}
+		}
 		return image;
 	}
 	
@@ -180,6 +213,104 @@
 	}
 	
 	
+	protected int computeAdornmentFlags(final LtkModelElement<?> element) {
+		int flags= 0;
+		if (element instanceof SourceElement) {
+			final var sourceElement= (SourceElement<?>)element;
+			final var sourceUnit= sourceElement.getSourceUnit();
+			if (sourceUnit != null) {
+				if (sourceUnit.getWorkingContext() == Ltk.EDITOR_CONTEXT) {
+					if (hasAstError(sourceElement)) {
+						flags|= DecoratedElementImageDescriptor.ERROR;
+					}
+					else if (isUpToDate(sourceElement)) {
+						final IAnnotationModel annotationModel= getAnnotationModel(sourceUnit);
+						if (annotationModel != null) {
+							flags|= getProblemFlag(annotationModel, sourceElement);
+						}
+					}
+				}
+			}
+		}
+		return flags;
+	}
+	
+	private @Nullable IAnnotationModel getAnnotationModel(final SourceUnit sourceUnit) {
+		final String modelTypeId= nonNullElse(this.modelTypeId, sourceUnit.getModelTypeId());
+		final IDocumentProvider documentProvider;
+		if (modelTypeId == this.documentProviderModelTypeId) {
+			documentProvider= this.documentProvider;
+		}
+		else {
+			documentProvider= LtkModels.getModelAdapter(modelTypeId, IDocumentProvider.class);
+			this.documentProviderModelTypeId= modelTypeId;
+			this.documentProvider= documentProvider;
+		}
+		if (documentProvider == null) {
+			return null;
+		}
+		return documentProvider.getAnnotationModel(sourceUnit);
+	}
+	
+	private boolean isUpToDate(final SourceElement<?> element) {
+		if (element instanceof SourceStructElement) {
+			final var containerElement= LtkModelUtils.getSourceContainerElement(
+					(SourceStructElement<?, ?>)element);
+			if (containerElement != null) {
+				final var sourceUnit= element.getSourceUnit();
+				return (containerElement.getStamp().getContentStamp() == sourceUnit.getContentStamp(null));
+			}
+		}
+		return true;
+	}
+	
+	private boolean hasAstError(final SourceElement<?> sourceElement) {
+		final var astNode= sourceElement.getAdapter(AstNode.class);
+		return (astNode != null && Asts.hasErrors(astNode));
+	}
+	
+	private int getProblemFlag(final IAnnotationModel model, final SourceElement<?> sourceElement) {
+		int severity= -1;
+		for (final var iter= model.getAnnotationIterator();
+				(severity != Problem.SEVERITY_ERROR && iter.hasNext()); ) {
+			final var annotation= iter.next();
+			if (annotation instanceof SourceIssueEditorAnnotation) {
+				final var issueAnnotation= (SourceIssueEditorAnnotation)annotation;
+				if (!issueAnnotation.isMarkedDeleted()) {
+					final Issue issue= issueAnnotation.getIssue();
+					if (issue instanceof Problem) {
+						if (isInside(model.getPosition(annotation), sourceElement)) {
+							severity= Math.max(severity, ((Problem)issue).getSeverity());
+						}
+					}
+				}
+				continue;
+			}
+		}
+		switch (severity) {
+		case Problem.SEVERITY_INFO:
+			return DecoratedElementImageDescriptor.INFO;
+		case Problem.SEVERITY_WARNING:
+			return DecoratedElementImageDescriptor.WARNING;
+		case Problem.SEVERITY_ERROR:
+			return DecoratedElementImageDescriptor.ERROR;
+		default:
+			return 0;
+		}
+	}
+	
+	private boolean isInside(final @Nullable Position position,
+			final SourceElement<?> sourceElement) {
+		return (position != null
+				&& (isInside(position, sourceElement.getSourceRange())
+						|| isInside(position, sourceElement.getDocumentationRange()) ));
+	}
+	
+	private boolean isInside(final Position position, final @Nullable TextRegion region) {
+		return (region != null && region.contains(position.getOffset()));
+	}
+	
+	
 	@Override
 	public void update(final ViewerCell cell) {
 		final Object cellElement= cell.getElement();