Bug 575365: [Ltk-Issues] Add issue requestor for persistence context

  - Add IssueTypeSet
  - Make use of IssueTypeSet in SourceAnnotationIssueRequestor
  - Add TaskTagReporter reporting tasks to issue requestors

Change-Id: Ieb429e6e874f8dc96b9d323a8c9d3ffb651addd2
diff --git a/ltk/org.eclipse.statet.ltk.core/META-INF/MANIFEST.MF b/ltk/org.eclipse.statet.ltk.core/META-INF/MANIFEST.MF
index 64760c4..4f6150a 100644
--- a/ltk/org.eclipse.statet.ltk.core/META-INF/MANIFEST.MF
+++ b/ltk/org.eclipse.statet.ltk.core/META-INF/MANIFEST.MF
@@ -24,6 +24,7 @@
  org.eclipse.statet.jcommons.collections;version="4.5.0",
  org.eclipse.statet.jcommons.lang;version="4.5.0",
  org.eclipse.statet.jcommons.status;version="4.5.0",
+ org.eclipse.statet.jcommons.status.eplatform;version="4.5.0",
  org.eclipse.statet.jcommons.string;version="4.5.0",
  org.eclipse.statet.jcommons.text.core;version="4.5.0",
  org.eclipse.statet.jcommons.text.core.util;version="4.5.0"
diff --git a/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/issues/core/IssueRequestor.java b/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/issues/core/IssueRequestor.java
index b52bd78..7f4f8d4 100644
--- a/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/issues/core/IssueRequestor.java
+++ b/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/issues/core/IssueRequestor.java
@@ -31,6 +31,8 @@
 public interface IssueRequestor {
 	
 	
+	boolean isInterestedInProblems(String categoryId);
+	
 	/**
 	 * Notification of a discovered problem.
 	 * 
@@ -46,6 +48,9 @@
 	 */
 	void acceptProblems(String categoryId, List<Problem> problems);
 	
+	
+	boolean isInterestedInTasks();
+	
 	/**
 	 * Notification of a discovered task.
 	 * 
@@ -53,6 +58,7 @@
 	 */
 	void acceptTask(Task task);
 	
+	
 	void finish() throws StatusException;
 	
 }
diff --git a/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/issues/core/IssueTypeSet.java b/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/issues/core/IssueTypeSet.java
new file mode 100644
index 0000000..02197b0
--- /dev/null
+++ b/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/issues/core/IssueTypeSet.java
@@ -0,0 +1,150 @@
+/*=============================================================================#
+ # Copyright (c) 2021 Stephan Wahlbrink 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, or the Apache License, Version 2.0
+ # which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ # 
+ # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ # 
+ # Contributors:
+ #     Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
+ #=============================================================================*/
+
+package org.eclipse.statet.ltk.issues.core;
+
+import org.eclipse.statet.jcommons.collections.ImList;
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+import org.eclipse.statet.jcommons.lang.Nullable;
+
+import org.eclipse.statet.ltk.core.Ltk;
+import org.eclipse.statet.ltk.core.WorkingContext;
+
+
+@NonNullByDefault
+public class IssueTypeSet {
+	
+	
+	public final static class ProblemTypes {
+		
+		private final String errorType;
+		private final String warningType;
+		private final String infoType;
+		
+		public ProblemTypes(final String errorType, final String warningType, final String infoType) {
+			this.errorType= errorType;
+			this.warningType= warningType;
+			this.infoType= infoType;
+		}
+		
+		public ProblemTypes(final String type) {
+			this.errorType= type;
+			this.warningType= type;
+			this.infoType= type;
+		}
+		
+		public String getType(final int priority) {
+			switch (priority) {
+			case Problem.SEVERITY_ERROR:
+				return this.errorType;
+			case Problem.SEVERITY_WARNING:
+				return this.warningType;
+			default:
+				return this.infoType;
+			}
+		}
+		
+	}
+	
+	public static class ProblemTypeDef {
+		
+		private final String id;
+		
+		private final @Nullable ProblemTypes persistenceTypes;
+		private final @Nullable ProblemTypes editorTypes;
+		
+		public ProblemTypeDef(final String id,
+				@Nullable final ProblemTypes persistenceTypes, @Nullable final ProblemTypes editorTypes) {
+			this.id= id;
+			this.persistenceTypes= persistenceTypes;
+			this.editorTypes= editorTypes;
+		}
+		
+		public String getId() {
+			return this.id;
+		}
+		
+		public @Nullable ProblemTypes getTypes(final WorkingContext context) {
+			if (context == Ltk.PERSISTENCE_CONTEXT) {
+				return this.persistenceTypes;
+			}
+			if (context == Ltk.EDITOR_CONTEXT) {
+				return this.editorTypes;
+			}
+			return null;
+		}
+		
+	}
+	
+	public static class TaskTypeDef {
+		
+		private final @Nullable String persistenceType;
+		private final @Nullable String editorType;
+		
+		public TaskTypeDef(@Nullable final String persistenceType, final @Nullable String editorType) {
+			this.persistenceType= persistenceType;
+			this.editorType= editorType;
+		}
+		
+		public @Nullable String getType(final WorkingContext context) {
+			if (context == Ltk.PERSISTENCE_CONTEXT) {
+				return this.persistenceType;
+			}
+			if (context == Ltk.EDITOR_CONTEXT) {
+				return this.editorType;
+			}
+			return null;
+		}
+		
+	}
+	
+	
+	private final String sourceId;
+	
+	private final TaskTypeDef taskTypeDef;
+	
+	private final ImList<ProblemTypeDef> problemTypeDefs;
+	
+	
+	public IssueTypeSet(final String sourceId,
+			final TaskTypeDef taskTypeDef,
+			final ImList<ProblemTypeDef> problemTypeDefs) {
+		this.sourceId= sourceId;
+		this.taskTypeDef= taskTypeDef;
+		this.problemTypeDefs= problemTypeDefs;
+	}
+	
+	
+	public String getSourceId() {
+		return this.sourceId;
+	}
+	
+	public TaskTypeDef getTaskCategory() {
+		return this.taskTypeDef;
+	}
+	
+	public ImList<ProblemTypeDef> getProblemCategories() {
+		return this.problemTypeDefs;
+	}
+	
+	public @Nullable ProblemTypeDef getProblemCategory(final String categoryId) {
+		for (final var problemCategory : this.problemTypeDefs) {
+			if (problemCategory.id == categoryId) {
+				return problemCategory;
+			}
+		}
+		return null;
+	}
+	
+}
diff --git a/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/issues/core/impl/BasicIssueRequestor.java b/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/issues/core/impl/BasicIssueRequestor.java
new file mode 100644
index 0000000..4157a6e
--- /dev/null
+++ b/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/issues/core/impl/BasicIssueRequestor.java
@@ -0,0 +1,237 @@
+/*=============================================================================#
+ # Copyright (c) 2021 Stephan Wahlbrink 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, or the Apache License, Version 2.0
+ # which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ # 
+ # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ # 
+ # Contributors:
+ #     Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
+ #=============================================================================*/
+
+package org.eclipse.statet.ltk.issues.core.impl;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.core.runtime.CoreException;
+
+import org.eclipse.statet.jcommons.collections.ImCollections;
+import org.eclipse.statet.jcommons.collections.ImList;
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+import org.eclipse.statet.jcommons.lang.Nullable;
+import org.eclipse.statet.jcommons.status.StatusException;
+import org.eclipse.statet.jcommons.status.eplatform.EStatusUtils;
+
+import org.eclipse.statet.ltk.core.WorkingContext;
+import org.eclipse.statet.ltk.issues.core.IssueRequestor;
+import org.eclipse.statet.ltk.issues.core.IssueTypeSet;
+import org.eclipse.statet.ltk.issues.core.IssueTypeSet.ProblemTypeDef;
+import org.eclipse.statet.ltk.issues.core.IssueTypeSet.TaskTypeDef;
+import org.eclipse.statet.ltk.issues.core.Problem;
+import org.eclipse.statet.ltk.issues.core.Task;
+
+
+@NonNullByDefault
+public abstract class BasicIssueRequestor implements IssueRequestor {
+	
+	private static final int STATE_OPEN= 1;
+	private static final int STATE_FINISHING= 2;
+	private static final int STATE_FINISHED= 3;
+	
+	
+	public final static class ProblemCategory {
+		
+		private final String id;
+		private final ProblemTypeDef def;
+		
+		private final boolean isEnabled;
+		
+		private final List<Problem> acceptedIssues= new ArrayList<>();
+		
+		
+		public ProblemCategory(final ProblemTypeDef def, final boolean isEnabled) {
+			this.id= def.getId();
+			this.def= def;
+			this.isEnabled= isEnabled;
+		}
+		
+		
+		public String getId() {
+			return this.id;
+		}
+		
+		public ProblemTypeDef getDef() {
+			return this.def;
+		}
+		
+		public List<Problem> getAcceptedIssues() {
+			return this.acceptedIssues;
+		}
+		
+	}
+	
+	public final static class TaskCategory {
+		
+		private final TaskTypeDef def;
+		
+		private final boolean isEnabled;
+		
+		private final List<Task> acceptedIssues= new ArrayList<>();
+		
+		
+		public TaskCategory(final TaskTypeDef def, final boolean isEnabled) {
+			this.def= def;
+			this.isEnabled= isEnabled;
+		}
+		
+		
+		public TaskTypeDef getDef() {
+			return this.def;
+		}
+		
+		public List<Task> getAcceptedIssues() {
+			return this.acceptedIssues;
+		}
+		
+	}
+	
+	
+	private final IssueTypeSet issueTypeSet;
+	
+	private final ImList<ProblemCategory> problemCategories;
+	private final @Nullable TaskCategory taskCategory;
+	
+	private int state= 1;
+	
+	
+	public BasicIssueRequestor(final IssueTypeSet issueTypeSet,
+			final WorkingContext workingContext) {
+		this.issueTypeSet= issueTypeSet;
+		
+		this.problemCategories= createProblemCategories(issueTypeSet.getProblemCategories(),
+				workingContext );
+		this.taskCategory= createTaskCategories(issueTypeSet.getTaskCategory(),
+				workingContext );
+	}
+	
+	
+	protected ImList<ProblemCategory> createProblemCategories(final ImList<ProblemTypeDef> defs,
+			final WorkingContext requiredWorkingSet) {
+		final var list= new ArrayList<ProblemCategory>(defs.size());
+		for (final var problemTypeDef : defs) {
+			if (problemTypeDef.getTypes(requiredWorkingSet) != null) {
+				list.add(new ProblemCategory(problemTypeDef, shouldAccept(problemTypeDef)));
+			}
+		}
+		return ImCollections.toList(list);
+	}
+	
+	protected boolean shouldAccept(final ProblemTypeDef def) {
+		return true;
+	}
+	
+	protected @Nullable TaskCategory createTaskCategories(final @Nullable TaskTypeDef def,
+			final WorkingContext requiredWorkingSet) {
+		if (def != null && def.getType(requiredWorkingSet) != null) {
+			return new TaskCategory(def, shouldAccept(def));
+		}
+		return null;
+	}
+	
+	protected boolean shouldAccept(final TaskTypeDef def) {
+		return true;
+	}
+	
+	
+	public IssueTypeSet getIssueTypeSet() {
+		return this.issueTypeSet;
+	}
+	
+	protected final ImList<ProblemCategory> getProblemCategories() {
+		return this.problemCategories;
+	}
+	
+	protected final @Nullable ProblemCategory getProblemCategory(final String id) {
+		for (final var category : this.problemCategories) {
+			if (category.id == id) {
+				return category;
+			}
+		}
+		return null;
+	}
+	
+	protected final @Nullable TaskCategory getTaskCategory() {
+		return this.taskCategory;
+	}
+	
+	
+	@Override
+	public boolean isInterestedInProblems(final String categoryId) {
+		final var category= getProblemCategory(categoryId);
+		return (category != null && category.isEnabled);
+	}
+	
+	@Override
+	public void acceptProblems(final Problem problem) {
+		final var category= getProblemCategory(problem.getCategoryId());
+		if (category != null && category.isEnabled) {
+			category.acceptedIssues.add(problem);
+		}
+	}
+	
+	@Override
+	public void acceptProblems(final String categoryId, final List<Problem> problems) {
+		final var category= getProblemCategory(categoryId);
+		if (category != null && category.isEnabled) {
+			category.acceptedIssues.addAll(problems);
+		}
+	}
+	
+	
+	@Override
+	public boolean isInterestedInTasks() {
+		final var category= getTaskCategory();
+		return (category != null && category.isEnabled);
+	}
+	
+	@Override
+	public void acceptTask(final Task task) {
+		final var category= getTaskCategory();
+		if (category != null && category.isEnabled) {
+			category.acceptedIssues.add(task);
+		}
+	}
+	
+	
+	@Override
+	public void finish() throws StatusException {
+		if (this.state != STATE_OPEN) {
+			throw new IllegalStateException("Already finished");
+		}
+		this.state= STATE_FINISHING;
+		
+		try {
+			reportProblems(this.problemCategories);
+			reportTasks(this.taskCategory);
+		}
+		catch (final CoreException e) {
+			throw EStatusUtils.convert(e);
+		}
+		finally {
+			this.state= STATE_FINISHED;
+		}
+	}
+	
+	
+	protected abstract void reportProblems(final ImList<ProblemCategory> problemCategories)
+			throws CoreException;
+	
+	protected abstract void reportTasks(final @Nullable TaskCategory taskCategory)
+			throws CoreException;
+	
+	
+}
diff --git a/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/issues/core/impl/ResourceMarkerIssueRequestor.java b/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/issues/core/impl/ResourceMarkerIssueRequestor.java
new file mode 100644
index 0000000..16d63c6
--- /dev/null
+++ b/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/issues/core/impl/ResourceMarkerIssueRequestor.java
@@ -0,0 +1,121 @@
+/*=============================================================================#
+ # Copyright (c) 2021 Stephan Wahlbrink 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, or the Apache License, Version 2.0
+ # which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ # 
+ # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ # 
+ # Contributors:
+ #     Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
+ #=============================================================================*/
+
+package org.eclipse.statet.ltk.issues.core.impl;
+
+import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullAssert;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.runtime.CoreException;
+
+import org.eclipse.statet.jcommons.collections.ImList;
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+import org.eclipse.statet.jcommons.lang.Nullable;
+
+import org.eclipse.statet.ltk.core.Ltk;
+import org.eclipse.statet.ltk.issues.core.IssueMarkers;
+import org.eclipse.statet.ltk.issues.core.IssueTypeSet;
+import org.eclipse.statet.ltk.issues.core.IssueTypeSet.ProblemTypes;
+import org.eclipse.statet.ltk.issues.core.Problem;
+import org.eclipse.statet.ltk.issues.core.Task;
+
+
+@NonNullByDefault
+public class ResourceMarkerIssueRequestor extends BasicIssueRequestor {
+	
+	
+	public static @Nullable ResourceMarkerIssueRequestor create(final @Nullable Object resource,
+			final IssueTypeSet issueTypeSet) {
+		if (resource instanceof IFile) {
+			return new ResourceMarkerIssueRequestor((IFile)resource, issueTypeSet);
+		}
+		return null;
+	}
+	
+	
+	private final IFile resource;
+	
+	
+	public ResourceMarkerIssueRequestor(final IFile file, final IssueTypeSet issueTypeSet) {
+		super(issueTypeSet, Ltk.PERSISTENCE_CONTEXT);
+		
+		this.resource= file;
+	}
+	
+	
+	@Override
+	protected void reportProblems(final ImList<ProblemCategory> problemCategories) throws CoreException {
+		for (final var problemCategory : problemCategories) {
+			final ProblemTypes types= nonNullAssert(
+					problemCategory.getDef().getTypes(Ltk.PERSISTENCE_CONTEXT) );
+			for (final var problem : problemCategory.getAcceptedIssues()) {
+				final var attributes= createProblemAttributes(problem);
+				this.resource.createMarker(types.getType(problem.getSeverity()), attributes);
+			}
+		}
+	}
+	
+	protected Map<String, Object> createProblemAttributes(final Problem problem) {
+		final var attributes= new HashMap<String, Object>(10);
+		attributes.put(IMarker.SOURCE_ID, getIssueTypeSet().getSourceId());
+		attributes.put(IssueMarkers.CATEGORY_ID_ATTR_NAME, problem.getCategoryId());
+		attributes.put(IMarker.SEVERITY, IssueMarkers.toMarkerSeverity(problem.getSeverity()));
+		attributes.put(IssueMarkers.CODE_ATTR_NAME, problem.getCode());
+		attributes.put(IMarker.MESSAGE, problem.getMessage());
+		
+		attributes.put(IMarker.LINE_NUMBER, (problem.getSourceLine() > 0) ? problem.getSourceLine() : 1);
+		if (problem.getSourceStartOffset() >= 0) {
+			attributes.put(IMarker.CHAR_START, problem.getSourceStartOffset());
+			attributes.put(IMarker.CHAR_END, problem.getSourceEndOffset());
+		}
+		attributes.put(IMarker.USER_EDITABLE, false);
+		
+		return attributes;
+	}
+	
+	
+	@Override
+	protected void reportTasks(final @Nullable TaskCategory taskCategory) throws CoreException {
+		if (taskCategory != null) {
+			final String type= nonNullAssert(
+					taskCategory.getDef().getType(Ltk.PERSISTENCE_CONTEXT) );
+			for (final var task : taskCategory.getAcceptedIssues()) {
+				final var attributes= createTaskAttributes(task);
+				this.resource.createMarker(type, attributes);
+			}
+		}
+	}
+	
+	protected Map<String, Object> createTaskAttributes(final Task task) {
+		final var attributes= new HashMap<String, Object>(10);
+		attributes.put(IMarker.SOURCE_ID, getIssueTypeSet().getSourceId());
+		attributes.put(IMarker.PRIORITY, IssueMarkers.toMarkerPriority(task.getPriority()));
+		attributes.put(IMarker.MESSAGE, task.getMessage());
+		
+		attributes.put(IMarker.LINE_NUMBER, (task.getSourceLine() > 0) ? task.getSourceLine() : 1);
+		if (task.getSourceStartOffset() >= 0) {
+			attributes.put(IMarker.CHAR_START, task.getSourceStartOffset());
+			attributes.put(IMarker.CHAR_END, task.getSourceEndOffset());
+		}
+		attributes.put(IMarker.USER_EDITABLE, false);
+		
+		return attributes;
+	}
+	
+	
+}
diff --git a/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/issues/core/impl/TaskTagReporter.java b/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/issues/core/impl/TaskTagReporter.java
new file mode 100644
index 0000000..70765ef
--- /dev/null
+++ b/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/issues/core/impl/TaskTagReporter.java
@@ -0,0 +1,120 @@
+/*=============================================================================#
+ # Copyright (c) 2005, 2021 Stephan Wahlbrink 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, or the Apache License, Version 2.0
+ # which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ # 
+ # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ # 
+ # Contributors:
+ #     Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
+ #=============================================================================*/
+
+package org.eclipse.statet.ltk.issues.core.impl;
+
+import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullAssert;
+import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullLateInit;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+import org.eclipse.statet.jcommons.lang.Nullable;
+
+import org.eclipse.statet.ltk.core.SourceContent;
+import org.eclipse.statet.ltk.issues.core.IssueRequestor;
+import org.eclipse.statet.ltk.issues.core.TaskPriority;
+import org.eclipse.statet.ltk.issues.core.TaskTag;
+
+
+@NonNullByDefault
+public abstract class TaskTagReporter {
+	
+	
+	private @Nullable Pattern taskTagPattern;
+	private final Map<String, TaskPriority> taskTagMap= new HashMap<>();
+	
+	private SourceContent sourceContent= nonNullLateInit();
+	
+	private IssueRequestor requestor= nonNullLateInit();
+	
+	private @Nullable Matcher taskTagMatcher;
+	
+	
+	public TaskTagReporter() {
+	}
+	
+	
+	protected void initTaskPattern(final List<TaskTag> taskTags) {
+		this.taskTagPattern= null;
+		this.taskTagMap.clear();
+		
+		if (taskTags.isEmpty()) {
+			return;
+		}
+		
+		final String separatorRegex= "[^\\p{L}\\p{N}]"; //$NON-NLS-1$
+		final StringBuilder regex= new StringBuilder(separatorRegex);
+		regex.append('(');
+		for (final TaskTag taskTag : taskTags) {
+			regex.append(Pattern.quote(taskTag.getKeyword()));
+			regex.append('|');
+			this.taskTagMap.put(taskTag.getKeyword(), taskTag.getPriority());
+		}
+		regex.setCharAt(regex.length() - 1, ')');
+		regex.append("(?:\\z|").append(separatorRegex).append(")"); //$NON-NLS-1$ //$NON-NLS-2$
+ 		this.taskTagPattern= Pattern.compile(regex.toString());
+	}
+	
+	
+	public void setup(final SourceContent sourceContent, final IssueRequestor requestor) {
+		this.sourceContent= nonNullAssert(sourceContent);
+		this.requestor= nonNullAssert(requestor);
+		
+		final Pattern taskTagPattern= this.taskTagPattern;
+		this.taskTagMatcher= (taskTagPattern != null && sourceContent.getStartOffset() == 0) ?
+				taskTagPattern.matcher(sourceContent.getString()) :
+				null;
+	}
+	
+	public void addTask(final String match, final String message,
+			final int offset, final int lineNumber) {
+		final TaskPriority priority= this.taskTagMap.get(match);
+		if (priority == null) {
+			return;
+		}
+		
+		final var task= new BasicTask(priority, message,
+				lineNumber, offset, offset + message.length() );
+		this.requestor.acceptTask(task);
+	}
+	
+	public void checkForTasks(final int startOffset, int endOffset) {
+		final Matcher matcher= this.taskTagMatcher;
+		if (matcher == null) {
+			return;
+		}
+		if (matcher.region(startOffset, endOffset).find()) {
+			final int tagStartOffset= matcher.start(1);
+			final int tagEndOffset= matcher.end(1);
+			while (endOffset > tagEndOffset) {
+				if (this.sourceContent.getChar(endOffset - 1) <= ' ') {
+					endOffset--;
+					continue;
+				}
+				else {
+					break;
+				}
+			}
+			final String text= this.sourceContent.getString(tagStartOffset, endOffset);
+			addTask(nonNullAssert(matcher.group(1)), text, tagStartOffset,
+					this.sourceContent.getStringLines().getLineOfOffset(tagStartOffset) + 1 );
+		}
+	}
+	
+}
diff --git a/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/model/core/build/SourceUnitModelContainer.java b/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/model/core/build/SourceUnitModelContainer.java
index 60016fc..031e5b5 100644
--- a/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/model/core/build/SourceUnitModelContainer.java
+++ b/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/model/core/build/SourceUnitModelContainer.java
@@ -26,6 +26,8 @@
 import org.eclipse.statet.ltk.core.SourceContent;
 import org.eclipse.statet.ltk.core.WorkingContext;
 import org.eclipse.statet.ltk.issues.core.IssueRequestor;
+import org.eclipse.statet.ltk.issues.core.IssueTypeSet;
+import org.eclipse.statet.ltk.issues.core.impl.ResourceMarkerIssueRequestor;
 import org.eclipse.statet.ltk.model.core.ModelManager;
 import org.eclipse.statet.ltk.model.core.element.SourceUnit;
 import org.eclipse.statet.ltk.model.core.element.SourceUnitModelInfo;
@@ -55,6 +57,10 @@
 	
 	public abstract Class<?> getAdapterClass();
 	
+	public @Nullable IssueTypeSet getIssueTypeSet() {
+		return null;
+	}
+	
 	
 	protected @Nullable WorkingContext getMode(final TSourceUnit su) {
 		if (su instanceof WorkspaceSourceUnit) {
@@ -147,6 +153,13 @@
 	
 	
 	public @Nullable IssueRequestor createIssueRequestor() {
+				final var mode= getMode();
+		if (mode == Ltk.PERSISTENCE_CONTEXT) {
+			final IssueTypeSet issueTypeSet= getIssueTypeSet();
+			if (issueTypeSet != null) {
+				return ResourceMarkerIssueRequestor.create(getSourceUnit().getResource(), issueTypeSet);
+			}
+		}
 		return null;
 	}
 	
diff --git a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/SourceAnnotationModel.java b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/SourceAnnotationModel.java
index 0b6951a..bffda2a 100644
--- a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/SourceAnnotationModel.java
+++ b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/SourceAnnotationModel.java
@@ -14,23 +14,34 @@
 
 package org.eclipse.statet.ltk.ui.sourceediting;
 
+import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullAssert;
+
 import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import org.eclipse.core.resources.IResource;
+import org.eclipse.core.runtime.CoreException;
 import org.eclipse.jface.text.BadLocationException;
 import org.eclipse.jface.text.Position;
 import org.eclipse.ui.texteditor.ResourceMarkerAnnotationModel;
 
+import org.eclipse.statet.jcommons.collections.ImList;
 import org.eclipse.statet.jcommons.lang.NonNullByDefault;
 import org.eclipse.statet.jcommons.lang.Nullable;
 
+import org.eclipse.statet.ltk.core.Ltk;
+import org.eclipse.statet.ltk.core.WorkingContext;
 import org.eclipse.statet.ltk.issues.core.Issue;
 import org.eclipse.statet.ltk.issues.core.IssueRequestor;
+import org.eclipse.statet.ltk.issues.core.IssueTypeSet;
+import org.eclipse.statet.ltk.issues.core.IssueTypeSet.ProblemTypeDef;
+import org.eclipse.statet.ltk.issues.core.IssueTypeSet.ProblemTypes;
+import org.eclipse.statet.ltk.issues.core.IssueTypeSet.TaskTypeDef;
 import org.eclipse.statet.ltk.issues.core.Problem;
-import org.eclipse.statet.ltk.issues.core.Task;
+import org.eclipse.statet.ltk.issues.core.impl.BasicIssueRequestor;
+import org.eclipse.statet.ltk.ui.sourceediting.SourceProblemAnnotation.PresentationConfig;
 
 
 /**
@@ -41,50 +52,51 @@
 public abstract class SourceAnnotationModel extends ResourceMarkerAnnotationModel {
 	
 	
-	protected class SourceAnnotationIssueRequestor implements IssueRequestor {
+	protected class SourceAnnotationIssueRequestor extends BasicIssueRequestor {
 		
 		
-		protected final List<Problem> reportedProblems= new ArrayList<>();
-		
-		protected final boolean handleTemporaryProblems;
-		
-		private int state= 1;
+		protected boolean handleTemporaryProblems;
 		
 		
-		public SourceAnnotationIssueRequestor() {
+		public SourceAnnotationIssueRequestor(final IssueTypeSet issueTypeSet) {
+			super(issueTypeSet, Ltk.EDITOR_CONTEXT);
+		}
+		
+		
+		@Override
+		protected ImList<ProblemCategory> createProblemCategories(final ImList<ProblemTypeDef> defs,
+				final WorkingContext requiredWorkingSet) {
 			this.handleTemporaryProblems= isHandlingTemporaryProblems();
+			return super.createProblemCategories(defs, requiredWorkingSet);
+		}
+		
+		@Override
+		protected boolean shouldAccept(final ProblemTypeDef def) {
+			return this.handleTemporaryProblems;
+		}
+		
+		@Override
+		protected boolean shouldAccept(final TaskTypeDef def) {
+			return false;
 		}
 		
 		
 		@Override
-		public void acceptProblems(final Problem problem) {
-			if (this.handleTemporaryProblems) {
-				this.reportedProblems.add(problem);
-			}
+		protected void reportProblems(final ImList<ProblemCategory> problemCategories)
+				throws CoreException {
+			SourceAnnotationModel.this.reportProblems(problemCategories);
 		}
 		
 		@Override
-		public void acceptProblems(final String modelTypeId, final List<Problem> problems) {
-			if (this.handleTemporaryProblems) {
-				this.reportedProblems.addAll(problems);
-			}
-		}
-		
-		@Override
-		public void acceptTask(final Task task) {
-		}
-		
-		@Override
-		public void finish() {
-			if (this.state < 0) {
-				throw new IllegalStateException("Already finished");
-			}
-			this.state= -1;
-			reportProblems(this.reportedProblems);
+		protected void reportTasks(final @Nullable TaskCategory taskCategory)
+				throws CoreException {
 		}
 		
 	}
 	
+	
+	private final IssueTypeSet issueTypeSet;
+	
 	private final AtomicInteger reportingCounter= new AtomicInteger();
 	
 	private final List<SourceProblemAnnotation> problemAnnotations= new ArrayList<>();
@@ -94,8 +106,14 @@
 //	private List currentlyOverlaid= new ArrayList();
 	
 	
-	public SourceAnnotationModel(final IResource resource) {
+	public SourceAnnotationModel(final IResource resource, final IssueTypeSet issueTypeSet) {
 		super(resource);
+		this.issueTypeSet= issueTypeSet;
+	}
+	
+	
+	protected IssueTypeSet getIssueTypeSet() {
+		return this.issueTypeSet;
 	}
 	
 	protected abstract boolean isHandlingTemporaryProblems();
@@ -118,7 +136,7 @@
 	}
 	
 	protected IssueRequestor doCreateIssueRequestor() {
-		return new SourceAnnotationIssueRequestor();
+		return new SourceAnnotationIssueRequestor(getIssueTypeSet());
 	}
 	
 	public void clearProblems(final @Nullable String category) {
@@ -149,7 +167,7 @@
 		}
 	}
 	
-	private void reportProblems(final List<Problem> reportedProblems) {
+	private void reportProblems(final ImList<BasicIssueRequestor.ProblemCategory> problemCategories) {
 		boolean reportedIssuesChanged= false;
 		
 		synchronized (getLockObject()) {
@@ -165,12 +183,16 @@
 				this.problemAnnotations.clear();
 			}
 			
-			if (reportedProblems != null && reportedProblems.size() > 0) {
-				for (final Problem problem : reportedProblems) {
+			for (final var problemCategory : problemCategories) {
+				final ProblemTypes types= nonNullAssert(
+						problemCategory.getDef().getTypes(Ltk.EDITOR_CONTEXT) );
+				for (final Problem problem : problemCategory.getAcceptedIssues()) {
 					final Position position= createPosition(problem);
 					if (position != null) {
 						try {
-							final SourceProblemAnnotation annotation= createAnnotation(problem);
+							final var annotation= new SourceProblemAnnotation(
+									types.getType(problem.getSeverity()), problem,
+									getPresentationConfig(problem) );
 //							overlayMarkers(position, annotation);
 							if (annotation != null) {
 								addAnnotation(annotation, position, false);
@@ -237,8 +259,15 @@
 		return new Position(start, end-start);
 	}
 	
-	protected @Nullable SourceProblemAnnotation createAnnotation(final Problem problem) {
-		return null;
+	protected PresentationConfig getPresentationConfig(final Problem problem) {
+		switch (problem.getSeverity()) {
+		case Problem.SEVERITY_ERROR:
+			return SourceProblemAnnotation.ERROR_CONFIG;
+		case Problem.SEVERITY_WARNING:
+			return SourceProblemAnnotation.WARNING_CONFIG;
+		default:
+			return SourceProblemAnnotation.INFO_CONFIG;
+		}
 	}
 	
 //	@Override
diff --git a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/SourceProblemAnnotation.java b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/SourceProblemAnnotation.java
index 49a595a..ebe629a 100644
--- a/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/SourceProblemAnnotation.java
+++ b/ltk/org.eclipse.statet.ltk.ui/src/org/eclipse/statet/ltk/ui/sourceediting/SourceProblemAnnotation.java
@@ -29,12 +29,16 @@
 import org.eclipse.ui.texteditor.AnnotationPreference;
 import org.eclipse.ui.texteditor.DefaultMarkerAnnotationAccess;
 
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+import org.eclipse.statet.jcommons.lang.Nullable;
+
 import org.eclipse.statet.ltk.issues.core.Problem;
 
 
 /**
  * Annotation representing an {@link Problem}.
  */
+@NonNullByDefault
 public class SourceProblemAnnotation extends Annotation
 		implements IAnnotationPresentation, IQuickFixableAnnotation {
 	
@@ -46,8 +50,8 @@
 		
 		private final int level;
 		
-		private String imageKey;
-		private Image image;
+		private @Nullable String imageKey;
+		private @Nullable Image image;
 		
 		
 		private PresentationConfig(final String referenceType, final int levelDiff) {
@@ -75,11 +79,14 @@
 			return this.level;
 		}
 		
-		public final Image getImage() {
-			if (this.image == null && this.imageKey != null) {
-				this.image= PlatformUI.getWorkbench().getSharedImages().getImage(this.imageKey);
+		public final @Nullable Image getImage() {
+			Image image= this.image;
+			final String key;
+			if (image == null && (key= this.imageKey) != null) {
+				image= PlatformUI.getWorkbench().getSharedImages().getImage(key);
+				this.image= image;
 			}
-			return this.image;
+			return image;
 		}
 		
 	}