Bug 575354: [Ltk-Model] Add extensible reconciler for Tex/Wikitext
with embedded content

Change-Id: I8f2ab14184be751655fe338ed70aaef4fa06184d
diff --git a/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/model/core/LtkModels.java b/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/model/core/LtkModels.java
index fb83008..ce42610 100644
--- a/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/model/core/LtkModels.java
+++ b/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/model/core/LtkModels.java
@@ -28,7 +28,7 @@
 		return LtkCorePlugin.getSafe().getSourceUnitManager();
 	}
 	
-	public static @Nullable Object getModelAdapter(final String modelTypeId, final Class<?> adapterType) {
+	public static <T> @Nullable T getModelAdapter(final String modelTypeId, final Class<T> adapterType) {
 		return LtkCorePlugin.getSafe().getModelAdapterFactory().get(modelTypeId, adapterType);
 	}
 	
diff --git a/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/model/core/build/ExtensibleReconciler.java b/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/model/core/build/ExtensibleReconciler.java
new file mode 100644
index 0000000..413fcb9
--- /dev/null
+++ b/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/model/core/build/ExtensibleReconciler.java
@@ -0,0 +1,246 @@
+/*=============================================================================#
+ # Copyright (c) 2009, 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.model.core.build;
+
+import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullAssert;
+
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.MultiStatus;
+
+import org.eclipse.statet.jcommons.collections.ImCollection;
+import org.eclipse.statet.jcommons.collections.ImCollections;
+import org.eclipse.statet.jcommons.collections.ImIdentitySet;
+import org.eclipse.statet.jcommons.collections.ImList;
+import org.eclipse.statet.jcommons.lang.NonNull;
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+import org.eclipse.statet.jcommons.lang.Nullable;
+
+import org.eclipse.statet.ltk.ast.core.AstInfo;
+import org.eclipse.statet.ltk.ast.core.EmbeddingAstNode;
+import org.eclipse.statet.ltk.ast.core.util.AstPrinter;
+import org.eclipse.statet.ltk.core.SourceContent;
+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.WorkspaceSourceUnit;
+import org.eclipse.statet.ltk.project.core.LtkProject;
+
+
+@NonNullByDefault
+public abstract class ExtensibleReconciler<TProject extends LtkProject,
+		TModelContainer extends SourceUnitModelContainer<?, ?>,
+		TEmbeddedModelReconciler extends SourceUnitEmbeddedModelReconciler<?>> {
+	
+	
+	protected static final class ExtensionData<TConfig extends ReconcileConfig<?>,
+			TReconciler extends SourceUnitEmbeddedModelReconciler<TConfig>> {
+		
+		public final String modelTypeId;
+		
+		public final TReconciler reconciler;
+		
+		public final TConfig config;
+		
+		
+		public ExtensionData(final TReconciler reconciler,
+				final TConfig config) {
+			this.modelTypeId= reconciler.getModelTypeId();
+			this.reconciler= reconciler;
+			this.config= nonNullAssert(config);
+		}
+		
+		
+		public static <T extends ExtensionData<?, ?>> boolean contains(final ImList<T> list, final String modelTypeId) {
+			for (final var data : list) {
+				if (data.modelTypeId == modelTypeId) {
+					return true;
+				}
+			}
+			return false;
+		}
+		
+		public static <T extends ExtensionData<?, ?>> @Nullable T get(final ImList<T> list, final String modelTypeId) {
+			for (final var data : list) {
+				if (data.modelTypeId == modelTypeId) {
+					return data;
+				}
+			}
+			return null;
+		}
+		
+	}
+	
+	private static class ExtensionItem<TConfig extends ReconcileConfig<?>,
+			TReconciler extends SourceUnitEmbeddedModelReconciler<TConfig>> {
+		
+		private final String modelTypeId;
+		
+		private final @Nullable TReconciler reconciler;
+		
+		private @Nullable ExtensionData<TConfig, TReconciler> projectData;
+		
+		public ExtensionItem(final String modelTypeId, @Nullable final TReconciler reconciler) {
+			this.modelTypeId= modelTypeId;
+			this.reconciler= reconciler;
+		}
+		
+		
+		public void clear() {
+			this.projectData= null;
+		}
+		
+		public ExtensionData<TConfig, TReconciler> init(final LtkProject project) {
+			var projectData= this.projectData;
+			if (projectData == null) {
+				projectData= createData(project.getProject(),
+						ModelManager.MODEL_DEPENDENCIES | ModelManager.RECONCILE );
+				this.projectData= projectData;
+			}
+			return projectData;
+		}
+		
+		public ExtensionData<TConfig, TReconciler> createData(final @Nullable IProject project, final int level) {
+			@SuppressWarnings({ "null" })
+			final TReconciler reconciler= this.reconciler;
+			return new ExtensionData<>(reconciler, reconciler.createConfig(project, level));
+		}
+		
+	}
+	
+	
+	protected boolean stop= false;
+	
+	private final Map<String, ExtensionItem<?, TEmbeddedModelReconciler>> extensions= new HashMap<>();
+	
+	private @Nullable TProject project;
+	private @Nullable MultiStatus statusCollector;
+	
+	private @Nullable AstPrinter raDebugAstPrinter;
+	
+	
+	public ExtensibleReconciler() {
+	}
+	
+	
+	public void init(final TProject project, final MultiStatus statusCollector) {
+		this.project= nonNullAssert(project);
+		this.statusCollector= statusCollector;
+		
+		synchronized (this.extensions) {
+			for (final var extension : this.extensions.values()) {
+				extension.clear();
+			}
+		}
+	}
+	
+	
+	public @Nullable MultiStatus getStatusCollector() {
+		return this.statusCollector;
+	}
+	
+	
+	protected abstract @Nullable TEmbeddedModelReconciler createEmbeddedModelReconciler(
+			final String modelTypeId);
+	
+	protected @Nullable ExtensionItem<?, TEmbeddedModelReconciler> getExtension(final String modelTypeId) {
+		synchronized (this.extensions) {
+			var item= this.extensions.get(modelTypeId);
+			if (item== null) {
+				item= new ExtensionItem(modelTypeId, createEmbeddedModelReconciler(modelTypeId));
+				this.extensions.put(modelTypeId, item);
+			}
+			return (item.reconciler != null) ? item : null;
+		}
+	}
+	
+	protected ImList<ExtensionData<?, TEmbeddedModelReconciler>> initExtensions(
+			final ImCollection<String> modelTypeIds,
+			final TModelContainer adapter, final int flags) {
+		final var items= new ArrayList<ExtensionItem<?, TEmbeddedModelReconciler>>();
+		for (final String modelTypeId : modelTypeIds) {
+			final var extension= getExtension(modelTypeId);
+			if (extension != null) {
+				items.add(extension);
+			}
+		}
+		
+		@SuppressWarnings("unchecked")
+		final @NonNull ExtensionData<?, TEmbeddedModelReconciler>[] dataArray= new @NonNull ExtensionData[items.size()];
+		final var project= this.project;
+		if (project != null) {
+			for (int i= 0; i < dataArray.length; i++) {
+				dataArray[i]= items.get(i).init(project);
+			}
+		}
+		else {
+			final SourceUnit sourceUnit= adapter.getSourceUnit();
+			final IProject wsProject= (sourceUnit instanceof WorkspaceSourceUnit) ?
+					((WorkspaceSourceUnit)sourceUnit).getResource().getProject() :
+					null;
+			for (int i= 0; i < dataArray.length; i++) {
+				dataArray[i]= items.get(i).createData(wsProject, flags);
+			}
+		}
+		return ImCollections.newList(dataArray);
+	}
+	
+	
+	protected ImList<Object> createSourceConfig(final Object mainSourceConfig,
+			final ImList<ExtensionData<?, TEmbeddedModelReconciler>> extensions) {
+		final var configArray= new @NonNull Object[1 + extensions.size()];
+		int i= 0;
+		configArray[i++]= mainSourceConfig;
+		for (final var extensionData : extensions) {
+			configArray[i++]= extensionData.config.getSourceConfig();
+		}
+		return ImCollections.newList(configArray);
+	}
+	
+	protected ImIdentitySet<String> collectEmbeddedTypeIds(final List<? extends EmbeddingAstNode> nodes) {
+		final Map<String, Boolean> ids= new IdentityHashMap<>(4);
+		for (final EmbeddingAstNode node : nodes) {
+			ids.put(node.getForeignTypeId(), Boolean.TRUE);
+		}
+		return ImCollections.toIdentitySet(ids.keySet());
+	}
+	
+	
+	protected void logAst(final AstInfo ast, final SourceContent content) {
+		AstPrinter printer= this.raDebugAstPrinter;
+		if (printer == null) {
+			printer= new AstPrinter(new StringWriter());
+			this.raDebugAstPrinter= printer;
+		}
+		final StringWriter out= (StringWriter)printer.getWriter();
+		out.getBuffer().setLength(0);
+		try {
+			out.append("====\n" + getClass().getSimpleName() + " AST:\n"); //$NON-NLS-1$
+			printer.print(ast.getRoot(), content.getString());
+			out.append("====\n"); //$NON-NLS-1$
+			System.out.println(out.toString());
+		}
+		catch (final Exception e) {
+			System.out.println(out.toString());
+			e.printStackTrace();
+		}
+	}
+	
+}
diff --git a/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/model/core/build/ReconcileConfig.java b/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/model/core/build/ReconcileConfig.java
new file mode 100644
index 0000000..39dc96e
--- /dev/null
+++ b/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/model/core/build/ReconcileConfig.java
@@ -0,0 +1,37 @@
+/*=============================================================================#
+ # 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.model.core.build;
+
+import org.eclipse.statet.jcommons.lang.NonNull;
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+
+
+@NonNullByDefault
+public class ReconcileConfig<@NonNull TSourceConfig> {
+	
+	
+	private final TSourceConfig sourceConfig;
+	
+	
+	public ReconcileConfig(final TSourceConfig sourceConfig) {
+		this.sourceConfig= sourceConfig;
+	}
+	
+	
+	public TSourceConfig getSourceConfig() {
+		return this.sourceConfig;
+	}
+	
+}
diff --git a/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/model/core/build/SourceUnitEmbeddedModelReconciler.java b/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/model/core/build/SourceUnitEmbeddedModelReconciler.java
new file mode 100644
index 0000000..d125ce1
--- /dev/null
+++ b/ltk/org.eclipse.statet.ltk.core/src/org/eclipse/statet/ltk/model/core/build/SourceUnitEmbeddedModelReconciler.java
@@ -0,0 +1,33 @@
+/*=============================================================================#
+ # Copyright (c) 2011, 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.model.core.build;
+
+import org.eclipse.core.resources.IProject;
+
+import org.eclipse.statet.jcommons.lang.NonNull;
+import org.eclipse.statet.jcommons.lang.NonNullByDefault;
+import org.eclipse.statet.jcommons.lang.Nullable;
+
+
+@NonNullByDefault
+public interface SourceUnitEmbeddedModelReconciler<TConfig extends ReconcileConfig<?>> {
+	
+	
+	String getModelTypeId();
+	
+	@NonNull TConfig createConfig(@Nullable IProject project, int level);
+	
+	
+}
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 1cd0c8d..f0708fa 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
@@ -54,10 +54,10 @@
 	}
 	
 	
-	public abstract boolean isContainerFor(String modelTypeId);
-	
 	public abstract Class<?> getAdapterClass();
 	
+	public abstract boolean isContainerFor(String modelTypeId);
+	
 	public @Nullable IssueTypeSet getIssueTypeSet() {
 		return null;
 	}