[540812] Make CacheAdapter pluggable

Change-Id: I17323f529d8a211edc931f4b04e5d2bd7913ada3
Task-Url: https://bugs.eclipse.org/bugs/show_bug.cgi?id=540812
Signed-off-by: Eike Stepper <stepper@esc-net.de>
diff --git a/plugins/org.eclipse.uml2.common/build.properties b/plugins/org.eclipse.uml2.common/build.properties
index 97da4ad..2609065 100644
--- a/plugins/org.eclipse.uml2.common/build.properties
+++ b/plugins/org.eclipse.uml2.common/build.properties
@@ -1,4 +1,4 @@
-# Copyright (c) 2005, 2013 IBM Corporation, Embarcadero Technologies, CEA, and others.
+# Copyright (c) 2005, 2018 IBM Corporation, Embarcadero Technologies, CEA, and others.
 # All rights reserved.   This program and the accompanying materials
 # are made available under the terms of the Eclipse Public License v1.0
 # which accompanies this distribution, and is available at
@@ -8,6 +8,7 @@
 #   IBM - initial API and implementation
 #   Kenn Hussey (Embarcadero Technologies) - 204200
 #   Christian W. Damus (CEA) - 227616
+#   Eike Stepper - 540812
 #
 
 # NLS_MESSAGEFORMAT_VAR
@@ -17,7 +18,9 @@
                modeling32.png,\
                plugin.properties,\
                .,\
-               META-INF/
+               META-INF/,\
+               plugin.xml,\
+               schema/
 src.includes = about.html
 exclude.. = **/doc-files/**
 javacSource = 1.5
diff --git a/plugins/org.eclipse.uml2.common/plugin.properties b/plugins/org.eclipse.uml2.common/plugin.properties
index d995a4e..0f4f270 100644
--- a/plugins/org.eclipse.uml2.common/plugin.properties
+++ b/plugins/org.eclipse.uml2.common/plugin.properties
@@ -1,4 +1,4 @@
-# Copyright (c) 2005, 2009 IBM Corporation, Embarcadero Technologies, and others.
+# Copyright (c) 2005, 2018 IBM Corporation, Embarcadero Technologies, CEA, and others.
 # All rights reserved.   This program and the accompanying materials
 # are made available under the terms of the Eclipse Public License v1.0
 # which accompanies this distribution, and is available at
@@ -7,6 +7,7 @@
 # Contributors: 
 #   IBM - initial API and implementation
 #   Kenn Hussey (Embarcadero Technologies) - 204200
+#   Eike Stepper - 540812
 #
 # $Id: plugin.properties,v 1.5 2009/04/07 18:24:53 jbruck Exp $
 
@@ -14,3 +15,5 @@
 
 pluginName = UML2 Common
 providerName = Eclipse Modeling Project
+
+_UI_CacheAdapter_extensionpoint = Cache Adapter
diff --git a/plugins/org.eclipse.uml2.common/plugin.xml b/plugins/org.eclipse.uml2.common/plugin.xml
new file mode 100644
index 0000000..be11c0a
--- /dev/null
+++ b/plugins/org.eclipse.uml2.common/plugin.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?eclipse version="3.4"?>
+
+<!--
+ Copyright (c) 2018 CEA, and others.
+ All rights reserved.   This program and the accompanying materials
+ are made available under the terms of the Eclipse Public License v1.0
+ which accompanies this distribution, and is available at
+ http://www.eclipse.org/legal/epl-v10.html
+
+ Contributors:
+   Eike Stepper - initial API and implementation
+
+-->
+
+<plugin>
+
+   <extension-point id="cache_adapter_override" name="%_UI_CacheAdapter_extensionpoint" schema="schema/cache_adapter_override.exsd"/>
+
+</plugin>
diff --git a/plugins/org.eclipse.uml2.common/schema/cache_adapter_override.exsd b/plugins/org.eclipse.uml2.common/schema/cache_adapter_override.exsd
new file mode 100644
index 0000000..0db0ea6
--- /dev/null
+++ b/plugins/org.eclipse.uml2.common/schema/cache_adapter_override.exsd
@@ -0,0 +1,109 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!-- Schema file written by PDE -->
+<schema targetNamespace="org.eclipse.uml2.common" xmlns="http://www.w3.org/2001/XMLSchema">
+<annotation>
+      <appinfo>
+         <meta.schema plugin="org.eclipse.uml2.common" id="cache_adapter_override" name="Cache Adapter"/>
+      </appinfo>
+      <documentation>
+         This extension point is used to register a custom cache adapter implementation. Note that at most one contribution to this extension point is able to register a cache adapter implementation. In case of multiple contributions one of them is selected arbitrarily, and a warning message is logged for the other ones.
+      </documentation>
+   </annotation>
+
+   <element name="extension">
+      <annotation>
+         <appinfo>
+            <meta.element />
+         </appinfo>
+      </annotation>
+      <complexType>
+         <sequence>
+            <element ref="cacheAdapter"/>
+         </sequence>
+         <attribute name="point" type="string" use="required">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+            </annotation>
+         </attribute>
+         <attribute name="id" type="string">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+            </annotation>
+         </attribute>
+         <attribute name="name" type="string">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+               <appinfo>
+                  <meta.attribute translatable="true"/>
+               </appinfo>
+            </annotation>
+         </attribute>
+      </complexType>
+   </element>
+
+   <element name="cacheAdapter">
+      <complexType>
+         <attribute name="class" type="string" use="required">
+            <annotation>
+               <documentation>
+                  
+               </documentation>
+               <appinfo>
+                  <meta.attribute kind="java" basedOn="org.eclipse.uml2.common.util.CacheAdapter:"/>
+               </appinfo>
+            </annotation>
+         </attribute>
+      </complexType>
+   </element>
+
+   <annotation>
+      <appinfo>
+         <meta.section type="since"/>
+      </appinfo>
+      <documentation>
+         2.4.0
+      </documentation>
+   </annotation>
+
+   <annotation>
+      <appinfo>
+         <meta.section type="examples"/>
+      </appinfo>
+      <documentation>
+         Following is an example of how a custom cache adapter implementation can be registered: 
+&lt;pre&gt;
+  &lt;extension point=&quot;org.eclipse.uml2.common.cache_adapter_override&quot;&gt;
+     &lt;cacheAdapter class=&quot;org.example.ExampleCacheAdapter&quot;/&gt; 
+  &lt;/extension&gt;
+&lt;/pre&gt;
+      </documentation>
+   </annotation>
+
+   <annotation>
+      <appinfo>
+         <meta.section type="apiinfo"/>
+      </appinfo>
+      <documentation>
+         No API exists to register a custom cache adapter implementation.
+      </documentation>
+   </annotation>
+
+
+   <annotation>
+      <appinfo>
+         <meta.section type="copyright"/>
+      </appinfo>
+      <documentation>
+         Copyright (c) 2018 CEA, and others.&lt;br&gt;
+All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License v1.0 which accompanies this distribution, and is available at &lt;a 
+href=&quot;http://www.eclipse.org/legal/epl-v10.html&quot;&gt;http://www.eclipse.org/legal/epl-v10.html&lt;/a&gt;
+      </documentation>
+   </annotation>
+
+</schema>
diff --git a/plugins/org.eclipse.uml2.common/src/org/eclipse/uml2/common/util/CacheAdapter.java b/plugins/org.eclipse.uml2.common/src/org/eclipse/uml2/common/util/CacheAdapter.java
index 2ff05a4..6df259f 100644
--- a/plugins/org.eclipse.uml2.common/src/org/eclipse/uml2/common/util/CacheAdapter.java
+++ b/plugins/org.eclipse.uml2.common/src/org/eclipse/uml2/common/util/CacheAdapter.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2004, 2015 IBM Corporation, Embarcadero Technologies, CEA, and others.
+ * Copyright (c) 2004, 2018 IBM Corporation, Embarcadero Technologies, CEA, and others.
  * All rights reserved.   This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
@@ -11,6 +11,7 @@
  *   Kenn Hussey - 335125
  *   Christian W. Damus (CEA) - 389632, 332057
  *   Kenn Hussey (CEA) - 418466, 455572
+ *   Eike Stepper - 540812
  *
  */
 package org.eclipse.uml2.common.util;
@@ -24,21 +25,28 @@
 import java.util.Set;
 import java.util.WeakHashMap;
 
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IConfigurationElement;
+import org.eclipse.core.runtime.RegistryFactory;
+import org.eclipse.emf.common.EMFPlugin;
 import org.eclipse.emf.common.notify.Adapter;
 import org.eclipse.emf.common.notify.Notification;
 import org.eclipse.emf.common.notify.Notifier;
 import org.eclipse.emf.common.util.BasicEList;
 import org.eclipse.emf.common.util.EList;
 import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.common.util.WrappedException;
 import org.eclipse.emf.ecore.EObject;
 import org.eclipse.emf.ecore.EReference;
 import org.eclipse.emf.ecore.EStructuralFeature;
 import org.eclipse.emf.ecore.InternalEObject;
+import org.eclipse.emf.ecore.plugin.RegistryReader;
 import org.eclipse.emf.ecore.resource.Resource;
 import org.eclipse.emf.ecore.resource.ResourceSet;
 import org.eclipse.emf.ecore.resource.URIConverter;
 import org.eclipse.emf.ecore.util.ECrossReferenceAdapter;
 import org.eclipse.emf.ecore.util.EcoreUtil;
+import org.eclipse.uml2.common.CommonPlugin;
 
 public class CacheAdapter
 		extends ECrossReferenceAdapter {
@@ -124,7 +132,64 @@
 		}
 	}
 
-	private static final CacheAdapter INSTANCE = createCacheAdapter();
+	/**
+	 * @author Eike Stepper
+	 */
+	private static final class OverrideRegistryReader
+			extends RegistryReader {
+
+		private static final String PPID = "cache_adapter_override";
+
+		private static final String TAG_CACHE_ADAPTER = "cacheAdapter";
+
+		private static final String ATT_CLASS = "class";
+
+		private CacheAdapter cacheAdapter;
+
+		private OverrideRegistryReader() {
+			super(RegistryFactory.getRegistry(),
+				CommonPlugin.INSTANCE.getSymbolicName(), PPID);
+		}
+
+		public CacheAdapter getCacheAdapter() {
+			return cacheAdapter;
+		}
+
+		@Override
+		protected boolean readElement(IConfigurationElement element,
+				boolean add) {
+			if (element.getName().equals(TAG_CACHE_ADAPTER)) {
+				String className = element.getAttribute(ATT_CLASS);
+				if (className == null) {
+					logMissingAttribute(element, ATT_CLASS);
+				} else if (add) {
+					if (cacheAdapter != null) {
+						if (!cacheAdapter.getClass().getName()
+							.equals(className)) {
+							CommonPlugin.INSTANCE.log(
+								"Ignored '" + className + "' in favour of '"
+									+ cacheAdapter.getClass().getName() + "'");
+						}
+
+						return false;
+					}
+
+					try {
+						cacheAdapter = (CacheAdapter) element
+							.createExecutableExtension(ATT_CLASS);
+					} catch (CoreException e) {
+						throw new WrappedException(e);
+					}
+				}
+				
+				return true;
+			}
+
+			return false;
+		}
+	}
+
+	private static CacheAdapter INSTANCE = createCacheAdapter();
 
 	protected static final ThreadLocal<CacheAdapter> THREAD_LOCAL = System
 		.getProperty("org.eclipse.uml2.common.util.CacheAdapter.ThreadLocal") != null //$NON-NLS-1$
@@ -155,6 +220,16 @@
 			return cacheAdapter;
 		}
 
+		if (EMFPlugin.IS_ECLIPSE_RUNNING) {
+			OverrideRegistryReader registryReader = new OverrideRegistryReader();
+			registryReader.readRegistry();
+			cacheAdapter = registryReader.getCacheAdapter();
+
+			if (cacheAdapter != null) {
+				return cacheAdapter;
+			}
+		}
+
 		return new CacheAdapter();
 	}
 
@@ -164,7 +239,7 @@
 
 	protected boolean adapting = false;
 
-	private URIConverter uriConverter = null;
+	protected URIConverter uriConverter = null;
 
 	public static CacheAdapter getCacheAdapter(Notifier notifier) {
 		List<Adapter> eAdapters = notifier.eAdapters();
@@ -289,9 +364,12 @@
 		boolean result = false;
 
 		if (notifier != null) {
-			adapting = true;
-			result = addAdapter(notifier.eAdapters());
-			adapting = false;
+			try {
+				adapting = true;
+				result = addAdapter(notifier.eAdapters());
+			} finally {
+				adapting = false;
+			}
 		}
 
 		return result;
@@ -321,6 +399,10 @@
 		}
 	}
 
+	protected ECrossReferenceAdapter provideCrossReferenceAdapter(EObject eObject) {
+		return getCrossReferenceAdapter(eObject);
+	}
+	
 	@Override
 	public Collection<EStructuralFeature.Setting> getNonNavigableInverseReferences(
 			EObject eObject) {
diff --git a/plugins/org.eclipse.uml2.common/src/org/eclipse/uml2/common/util/UML2Util.java b/plugins/org.eclipse.uml2.common/src/org/eclipse/uml2/common/util/UML2Util.java
index 7f3fe6f..0179b83 100644
--- a/plugins/org.eclipse.uml2.common/src/org/eclipse/uml2/common/util/UML2Util.java
+++ b/plugins/org.eclipse.uml2.common/src/org/eclipse/uml2/common/util/UML2Util.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2005, 2014 IBM Corporation, Embarcadero Technologies, CEA, and others.
+ * Copyright (c) 2005, 2018 IBM Corporation, Embarcadero Technologies, CEA, and others.
  * All rights reserved.   This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
@@ -11,6 +11,7 @@
  *   Keith Campbell (IBM) - 343783
  *   Kenn Hussey (CEA) - 316165, 322715, 212765, 421756, 424568, 443662
  *   Christian W. Damus (CEA) - 405065
+#   Eike Stepper - 540812
  *
  */
 package org.eclipse.uml2.common.util;
@@ -1107,8 +1108,8 @@
 	 */
 	public static Collection<EStructuralFeature.Setting> getNonNavigableInverseReferences(
 			EObject eObject) {
-		ECrossReferenceAdapter crossReferenceAdapter = ECrossReferenceAdapter
-			.getCrossReferenceAdapter(eObject);
+		ECrossReferenceAdapter crossReferenceAdapter = CacheAdapter
+			.getInstance().provideCrossReferenceAdapter(eObject);
 		return crossReferenceAdapter == null
 			? Collections.<EStructuralFeature.Setting> emptyList()
 			: crossReferenceAdapter.getNonNavigableInverseReferences(eObject);
@@ -1123,8 +1124,8 @@
 	 */
 	public static Collection<EStructuralFeature.Setting> getInverseReferences(
 			EObject eObject) {
-		ECrossReferenceAdapter crossReferenceAdapter = ECrossReferenceAdapter
-			.getCrossReferenceAdapter(eObject);
+		ECrossReferenceAdapter crossReferenceAdapter = CacheAdapter
+			.getInstance().provideCrossReferenceAdapter(eObject);
 		return crossReferenceAdapter == null
 			? Collections.<EStructuralFeature.Setting> emptyList()
 			: crossReferenceAdapter.getInverseReferences(eObject);