From dea3ad31a4117ea47efa5df8a6bd4335a67f929d Mon Sep 17 00:00:00 2001
From: Simon Barner <barner@fortiss.org>
Date: Thu, 19 Dec 2019 16:31:13 +0100
Subject: [PATCH] Add DanglingReferencesCleanupXMIHandler

*  XMIHandler that cleans up dangling references when loading models
   with unknown types instead of throwing an exception.

Issue-Ref: 3865
Issue-Url: https://af3-developer.fortiss.org/issues/3865

Signed-off-by: Simon Barner <barner@fortiss.org>
---
 .../org/fortiss/tooling/kernel/utils/.ratings |   2 +-
 .../tooling/kernel/utils/ResourceUtils.java   | 127 +++++++++++++++---
 2 files changed, 111 insertions(+), 18 deletions(-)

diff --git a/org.fortiss.tooling.kernel/src/org/fortiss/tooling/kernel/utils/.ratings b/org.fortiss.tooling.kernel/src/org/fortiss/tooling/kernel/utils/.ratings
index d85861e2e..7cc1b6750 100644
--- a/org.fortiss.tooling.kernel/src/org/fortiss/tooling/kernel/utils/.ratings
+++ b/org.fortiss.tooling.kernel/src/org/fortiss/tooling/kernel/utils/.ratings
@@ -10,6 +10,6 @@ JavaUtils.java 65cdadfb9137a240ad59992eacf53a15b7f20804 GREEN
 KernelModelElementUtils.java fded09befe7e543fc04ea5184ffc1c8a309d7a66 GREEN
 LoggingUtils.java 0e0aa5d466d80ea29cfc7e91178b23a5cdd4ddf7 GREEN
 PrototypesUtils.java ec75bed75cfc5103f1f38e3a29df86f729428775 GREEN
-ResourceUtils.java 698c7db34acb4f1a258a1953e6afcca9823763a8 GREEN
+ResourceUtils.java e397569bf8d128b3391f1bdc09b941502a8da321 YELLOW
 TransformationUtils.java 552d3a9d56d34450be781af828efe0b8aa5d359e GREEN
 UniqueIDUtils.java 665955b1790c1bd1c2087e23114da920bfec2265 GREEN
diff --git a/org.fortiss.tooling.kernel/src/org/fortiss/tooling/kernel/utils/ResourceUtils.java b/org.fortiss.tooling.kernel/src/org/fortiss/tooling/kernel/utils/ResourceUtils.java
index 698c7db34..e397569bf 100644
--- a/org.fortiss.tooling.kernel/src/org/fortiss/tooling/kernel/utils/ResourceUtils.java
+++ b/org.fortiss.tooling.kernel/src/org/fortiss/tooling/kernel/utils/ResourceUtils.java
@@ -51,6 +51,11 @@ import org.eclipse.emf.ecore.resource.ResourceSet;
 import org.eclipse.emf.ecore.resource.impl.ResourceFactoryImpl;
 import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
 import org.eclipse.emf.ecore.xmi.PackageNotFoundException;
+import org.eclipse.emf.ecore.xmi.XMLHelper;
+import org.eclipse.emf.ecore.xmi.XMLLoad;
+import org.eclipse.emf.ecore.xmi.XMLResource;
+import org.eclipse.emf.ecore.xmi.impl.SAXXMIHandler;
+import org.eclipse.emf.ecore.xmi.impl.XMILoadImpl;
 import org.eclipse.emf.ecore.xmi.impl.XMIResourceImpl;
 import org.eclipse.emf.ecore.xmi.impl.XMLResourceFactoryImpl;
 import org.eclipse.emf.ecore.xml.type.AnyType;
@@ -58,6 +63,7 @@ import org.fortiss.tooling.kernel.ToolingKernelActivator;
 import org.fortiss.tooling.kernel.extension.data.ITopLevelElement;
 import org.fortiss.tooling.kernel.model.IIdLabeled;
 import org.fortiss.tooling.kernel.service.IPersistencyService;
+import org.xml.sax.helpers.DefaultHandler;
 
 /**
  * Utility class for dealing with EMF {@link Resource}s.
@@ -77,12 +83,79 @@ public final class ResourceUtils {
 	 */
 	public static class KernelResourceFactory extends ResourceFactoryImpl {
 
+		/**
+		 * XMIHandler that cleans up dangling references when loading models with unknown types
+		 * instead of throwing an exception.
+		 */
+		private final class DanglingReferencesCleanupXMIHandler extends SAXXMIHandler {
+			/** Constructor. */
+			private DanglingReferencesCleanupXMIHandler(XMLResource xmiResource, XMLHelper helper,
+					Map<?, ?> options) {
+				super(xmiResource, helper, options);
+			}
+
+			/** {@inheritDoc} */
+			@Override
+			protected void handleForwardReferences(boolean isEndDocument) {
+				List<SingleReference> toRemoveFwdSingleRefs = new ArrayList<>();
+				for(SingleReference ref : forwardSingleReferences) {
+					EObject obj;
+					try {
+						obj = xmlResource.getEObject((String)ref.getValue());
+					} catch(RuntimeException exception) {
+						obj = null;
+					}
+					if(obj == null || obj instanceof AnyType) {
+						warnRefWithUnknownType((AnyType)obj, ref.getLineNumber(),
+								ref.getColumnNumber());
+						toRemoveFwdSingleRefs.add(ref);
+					}
+				}
+				forwardSingleReferences.removeAll(toRemoveFwdSingleRefs);
+
+				List<ManyReference> toRemoveFwdManyRefs = new ArrayList<>();
+				for(ManyReference ref : forwardManyReferences) {
+					Object[] values = ref.getValues();
+
+					for(String id : (String[])values) {
+
+						EObject obj;
+						try {
+							obj = xmlResource.getEObject(id);
+						} catch(RuntimeException exception) {
+							obj = null;
+						}
+						if(obj == null || obj instanceof AnyType) {
+							warnRefWithUnknownType((AnyType)obj, ref.getLineNumber(),
+									ref.getColumnNumber());
+							toRemoveFwdManyRefs.add(ref);
+							break;
+						}
+					}
+				}
+				forwardManyReferences.removeAll(toRemoveFwdManyRefs);
+
+				super.handleForwardReferences(isEndDocument);
+			}
+
+			/** Issues a warning for a given {@link AnyType}d object. */
+			private void warnRefWithUnknownType(AnyType obj, int line, int col) {
+				String message = "Removing reference to element with unknown type" +
+						(obj != null ? " \"" + obj.eClass().getName() + "\"" : "") + " in line " +
+						line + ", column " + col + ".";
+
+				// Static inline not possible: name clash with XMLHandler::warning()
+				LoggingUtils.warning(ToolingKernelActivator.getDefault(), message);
+			}
+		}
+
 		/**
 		 * {@link XMIResourceImpl} used to persist models managed by the tooling kernel.
 		 * 
 		 * @author barner
 		 */
 		private final class KernelXMIResource extends XMIResourceImpl {
+
 			/** Constructs a new {@link KernelXMIResource}. */
 			private KernelXMIResource(URI uri) {
 				super(uri);
@@ -140,6 +213,18 @@ public final class ResourceUtils {
 				}
 				return eObjectToExtensionMap;
 			}
+
+			/** {@inheritDoc} */
+			@Override
+			protected XMLLoad createXMLLoad() {
+				return new XMILoadImpl(createXMLHelper()) {
+					/** {@inheritDoc} */
+					@Override
+					protected DefaultHandler makeDefaultHandler() {
+						return new DanglingReferencesCleanupXMIHandler(resource, helper, options);
+					}
+				};
+			}
 		}
 
 		/** {@inheritDoc} */
@@ -147,6 +232,7 @@ public final class ResourceUtils {
 		public Resource createResource(URI uri) {
 			return new KernelXMIResource(uri);
 		}
+
 	}
 
 	/**
@@ -196,13 +282,13 @@ public final class ResourceUtils {
 
 	/**
 	 * <p>
-	 * Determines the {@link ITopLevelElement} which shares the same {@link ResourceSet} as the a
-	 * {@link Resource} with the specified {@link URI}. If no such {@link Resource} has been loaded,
-	 * {@code null} is returned.
+	 * Determines the {@link ITopLevelElement} which shares the same {@link ResourceSet} as the
+	 * a {@link Resource} with the specified {@link URI}. If no such {@link Resource} has been
+	 * loaded, {@code null} is returned.
 	 * </p>
 	 * <p>
-	 * This is useful to determine to which model managed by the Kernel a given (external) model is
-	 * linked.
+	 * This is useful to determine to which model managed by the Kernel a given (external) model
+	 * is linked.
 	 * </p>
 	 * 
 	 * @param uri
@@ -226,8 +312,8 @@ public final class ResourceUtils {
 	}
 
 	/**
-	 * Obtains a model of a given type from a {@code resourceSet}, or {@code null} if it could not
-	 * be found.
+	 * Obtains a model of a given type from a {@code resourceSet}, or {@code null} if it could
+	 * not be found.
 	 * 
 	 * @param resourceSet
 	 *            {@link ResourceSet} from which model of given type should be determined
@@ -251,25 +337,30 @@ public final class ResourceUtils {
 	}
 
 	/**
-	 * Obtains a model of a given type from a {@code resourceSet}, and creates the model if it does
-	 * not exist yet (and adds it to the given {@code resourceSet}). Returns {@code null} if the
-	 * model could not be created.
+	 * Obtains a model of a given type from a {@code resourceSet}, and creates the model if it
+	 * does not exist yet (and adds it to the given {@code resourceSet}). Returns {@code null} if
+	 * the model could not be created.
 	 * 
 	 * @param resourceSet
 	 *            {@link ResourceSet} from which model of given type should be determined
 	 * @param clazz
 	 *            Type of model to be returned.
 	 * @param factory
-	 *            {@link EFactory} to create root model element in case the model does not exist in
+	 *            {@link EFactory} to create root model element in case the model does not exist
+	 *            in
 	 *            the {@code resourceSet} yet.
 	 * @param fileExtension
-	 *            File extension to be used in case a new resource has to be created and added to
-	 *            the given given {@code resourceSet}. In case this parameter is {@code null}, this
+	 *            File extension to be used in case a new resource has to be created and added
+	 *            to
+	 *            the given given {@code resourceSet}. In case this parameter is {@code null},
+	 *            this
 	 *            method effectively behaves like {@link #getModel(ResourceSet, Class)}.
 	 * @param referenceClazz
-	 *            Type of root model element whose {@link Resource} {@link URI} should be used to
+	 *            Type of root model element whose {@link Resource} {@link URI} should be used
+	 *            to
 	 *            derive the base name in case a new {@link Resource} has to be created. In case
-	 *            this parameter is {@code null}, the {@link URI} of the first {@link Resource} in
+	 *            this parameter is {@code null}, the {@link URI} of the first {@link Resource}
+	 *            in
 	 *            the given {@link ResourceSet} is used.
 	 * 
 	 * @return Model of a given type, or {@code null} if it could not be created.
@@ -368,12 +459,14 @@ public final class ResourceUtils {
 	 *            {@link Resource} for which {@link URI}s of directly or indirectly referencing
 	 *            resources should be determined.
 	 * @param includeSelf
-	 *            Flag if the {@link URI} of the given {@code resource} should be contained in the
+	 *            Flag if the {@link URI} of the given {@code resource} should be contained in
+	 *            the
 	 *            result.
 	 * @param progressMonitor
 	 *            {@link IProgressMonitor} (may be {@code null}). Loading all resources in a
 	 *            directory may be a long-running operation.
-	 * @return {@link URI}s of directly or indirectly referencing resources, possibly including the
+	 * @return {@link URI}s of directly or indirectly referencing resources, possibly including
+	 *         the
 	 *         {@link URI} of the given {@code resource} itself (see {@code includeSelf}).
 	 */
 	public static Collection<URI> getReferencingResourceURIs(Resource resource, boolean includeSelf,
-- 
GitLab