Bug 552175 - Register dynamic property handler for EMF classes

Adds an EMF implementation of JXPath's DynamicPropertyHandler and
registers one for each workbench model class. This avoids costly bean
inspection and many class lookup failures for non-existing *BeanInfo and
*Customizer classes during Eclipse startup.

Change-Id: Ic8d5fbca15fecfb2fef94df48ed82cb962fe2798
Signed-off-by: Karsten Thoms <karsten.thoms@itemis.de>
diff --git a/bundles/org.eclipse.e4.emf.xpath/META-INF/MANIFEST.MF b/bundles/org.eclipse.e4.emf.xpath/META-INF/MANIFEST.MF
index aea3887..43ef125 100644
--- a/bundles/org.eclipse.e4.emf.xpath/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.e4.emf.xpath/META-INF/MANIFEST.MF
@@ -5,9 +5,10 @@
 Bundle-Version: 0.2.500.qualifier
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
 Require-Bundle: org.apache.commons.jxpath;bundle-version="1.2.0",
- org.eclipse.emf.ecore;bundle-version="2.6.0"
+ org.eclipse.emf.ecore;bundle-version="2.6.0",
+ org.eclipse.core.runtime;bundle-version="3.16.100"
 Export-Package: org.eclipse.e4.emf.internal.xpath;x-internal:=true,
- org.eclipse.e4.emf.internal.xpath.helper;x-internal:=true,
+ org.eclipse.e4.emf.internal.xpath.helper;x-friends:="org.eclipse.e4.emf.xpath.test,org.eclipse.e4.ui.model.workbench,org.eclipse.e4.ui.workbench",
  org.eclipse.e4.emf.xpath
 Bundle-Vendor: %Bundle-Vendor
 Automatic-Module-Name: org.eclipse.e4.emf.xpath
diff --git a/bundles/org.eclipse.e4.emf.xpath/src/org/eclipse/e4/emf/internal/xpath/helper/EDynamicPropertyHandler.java b/bundles/org.eclipse.e4.emf.xpath/src/org/eclipse/e4/emf/internal/xpath/helper/EDynamicPropertyHandler.java
new file mode 100644
index 0000000..17a5677
--- /dev/null
+++ b/bundles/org.eclipse.e4.emf.xpath/src/org/eclipse/e4/emf/internal/xpath/helper/EDynamicPropertyHandler.java
@@ -0,0 +1,53 @@
+/*******************************************************************************
+ * Copyright (c) 2019 itemis AG.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Karsten Thoms <karsten.thoms@itemis.de> - Initial implementation and API
+ ******************************************************************************/
+package org.eclipse.e4.emf.internal.xpath.helper;
+
+import org.apache.commons.jxpath.DynamicPropertyHandler;
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.emf.ecore.EClass;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EStructuralFeature;
+
+/**
+ * A dynamic property handler for EMF classes.
+ *
+ * @author Karsten Thoms <karsten.thoms@itemis.de>
+ */
+public class EDynamicPropertyHandler implements DynamicPropertyHandler {
+	@Override
+	public String[] getPropertyNames(Object object) {
+		Assert.isLegal(object instanceof EObject);
+		EClass eClass = ((EObject) object).eClass();
+		String[] propNames = eClass.getEAllStructuralFeatures().stream().map(f -> f.getName()).toArray(String[]::new);
+		return propNames;
+	}
+
+	@Override
+	public Object getProperty(Object object, String propertyName) {
+		Assert.isLegal(object instanceof EObject);
+		EObject eObject = (EObject) object;
+		EStructuralFeature feature = eObject.eClass().getEStructuralFeature(propertyName);
+		return feature != null ? eObject.eGet(feature) : null;
+	}
+
+	@Override
+	public void setProperty(Object object, String propertyName, Object value) {
+		Assert.isLegal(object instanceof EObject);
+		EObject eObject = (EObject) object;
+		EStructuralFeature feature = eObject.eClass().getEStructuralFeature(propertyName);
+		if (feature != null) {
+			eObject.eSet(feature, value);
+		}
+	}
+}
diff --git a/bundles/org.eclipse.e4.ui.workbench/META-INF/MANIFEST.MF b/bundles/org.eclipse.e4.ui.workbench/META-INF/MANIFEST.MF
index 5ef09e3..3989077 100644
--- a/bundles/org.eclipse.e4.ui.workbench/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.e4.ui.workbench/META-INF/MANIFEST.MF
@@ -21,7 +21,8 @@
  org.eclipse.emf.ecore.xmi;bundle-version="2.7.0",
  org.eclipse.e4.core.di.extensions,
  org.eclipse.core.runtime;bundle-version="3.3.0",
- org.eclipse.jdt.annotation;bundle-version="2.2.200";resolution:=optional
+ org.eclipse.jdt.annotation;bundle-version="2.2.200";resolution:=optional,
+ org.eclipse.e4.emf.xpath;bundle-version="0.2.500"
 Bundle-ActivationPolicy: lazy
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
 Export-Package: org.eclipse.e4.ui.internal.workbench;
@@ -43,6 +44,7 @@
 Import-Package: com.ibm.icu.text;version="3.8.1",
  javax.annotation,
  javax.inject;version="1.0.0",
+ org.apache.commons.jxpath;version="1.3.0",
  org.osgi.service.component.annotations;version="1.2.0";resolution:=optional,
  org.osgi.service.event;version="1.3.0"
 Require-Capability: osgi.extender;
diff --git a/bundles/org.eclipse.e4.ui.workbench/src/org/eclipse/e4/ui/internal/workbench/Activator.java b/bundles/org.eclipse.e4.ui.workbench/src/org/eclipse/e4/ui/internal/workbench/Activator.java
index cf13991..0f74b5d 100644
--- a/bundles/org.eclipse.e4.ui.workbench/src/org/eclipse/e4/ui/internal/workbench/Activator.java
+++ b/bundles/org.eclipse.e4.ui.workbench/src/org/eclipse/e4/ui/internal/workbench/Activator.java
@@ -35,6 +35,21 @@
 
 import java.util.Hashtable;
 import java.util.List;
+import org.apache.commons.jxpath.JXPathIntrospector;
+import org.eclipse.e4.emf.internal.xpath.helper.EDynamicPropertyHandler;
+import org.eclipse.e4.ui.model.application.commands.impl.CommandsPackageImpl;
+import org.eclipse.e4.ui.model.application.descriptor.basic.impl.BasicPackageImpl;
+import org.eclipse.e4.ui.model.application.impl.ApplicationPackageImpl;
+import org.eclipse.e4.ui.model.application.ui.advanced.impl.AdvancedPackageImpl;
+import org.eclipse.e4.ui.model.application.ui.impl.UiPackageImpl;
+import org.eclipse.e4.ui.model.application.ui.menu.impl.MenuPackageImpl;
+import org.eclipse.e4.ui.model.fragment.impl.FragmentPackageImpl;
+import org.eclipse.emf.ecore.EClass;
+import org.eclipse.emf.ecore.EClassifier;
+import org.eclipse.emf.ecore.EFactory;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.EPackage;
+import org.eclipse.emf.ecore.EcorePackage;
 import org.eclipse.osgi.service.datalocation.Location;
 import org.eclipse.osgi.service.debug.DebugOptions;
 import org.eclipse.osgi.service.debug.DebugOptionsListener;
@@ -108,6 +123,8 @@
 		resolvedBundles = new BundleTracker<>(context, Bundle.RESOLVED
 				| Bundle.STARTING | Bundle.ACTIVE | Bundle.STOPPING, bundleFinder);
 		resolvedBundles.open();
+
+		registerJXPathPropertyHandlers();
 	}
 
 	@Override
@@ -205,4 +222,28 @@
 		}
 	}
 
+	private static void registerJXPathPropertyHandlers() {
+		EPackage[] packages = new EPackage[] { EcorePackage.eINSTANCE, ApplicationPackageImpl.eINSTANCE,
+				CommandsPackageImpl.eINSTANCE, BasicPackageImpl.eINSTANCE, UiPackageImpl.eINSTANCE,
+				AdvancedPackageImpl.eINSTANCE,
+				org.eclipse.e4.ui.model.application.ui.basic.impl.BasicPackageImpl.eINSTANCE, MenuPackageImpl.eINSTANCE,
+				FragmentPackageImpl.eINSTANCE };
+
+		// register dynamic property handler for all application model classes
+		// to avoid costly bean introspection
+		for (EPackage ePackage : packages) {
+			for (EClassifier eClassifier : ePackage.getEClassifiers()) {
+				if (!(eClassifier instanceof EClass)) {
+					continue;
+				}
+				EClass eClass = (EClass) eClassifier;
+				if (eClass.isAbstract()) {
+					continue;
+				}
+				EFactory eFactory = EPackage.Registry.INSTANCE.getEFactory(ePackage.getNsURI());
+				Class<? extends EObject> implClass = eFactory.create(eClass).getClass();
+				JXPathIntrospector.registerDynamicClass(implClass, EDynamicPropertyHandler.class);
+			}
+		}
+	}
 }
diff --git a/tests/org.eclipse.e4.emf.xpath.test/src/org/eclipse/e4/emf/xpath/test/EDynamicPropertyHandlerTest.java b/tests/org.eclipse.e4.emf.xpath.test/src/org/eclipse/e4/emf/xpath/test/EDynamicPropertyHandlerTest.java
new file mode 100644
index 0000000..2586fce
--- /dev/null
+++ b/tests/org.eclipse.e4.emf.xpath.test/src/org/eclipse/e4/emf/xpath/test/EDynamicPropertyHandlerTest.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (c) 2019 itemis AG.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ *     Karsten Thoms <karsten.thoms@itemis.de> - Initial implementation and API
+ ******************************************************************************/
+package org.eclipse.e4.emf.xpath.test;
+
+import static java.util.Arrays.stream;
+import static java.util.stream.Collectors.joining;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import org.eclipse.e4.emf.internal.xpath.helper.EDynamicPropertyHandler;
+import org.eclipse.e4.emf.xpath.test.model.xpathtest.Menu;
+import org.eclipse.e4.emf.xpath.test.model.xpathtest.XpathtestFactory;
+import org.junit.Test;
+
+public class EDynamicPropertyHandlerTest {
+
+	@Test
+	public void test_getPropertyNames() {
+		EDynamicPropertyHandler propertyHandler = new EDynamicPropertyHandler();
+		Menu menu = XpathtestFactory.eINSTANCE.createMenu();
+		String propertyNames = stream(propertyHandler.getPropertyNames(menu)).sorted().collect(joining(","));
+		assertEquals("children,id,label", propertyNames);
+	}
+
+	@Test
+	public void test_getProperty() {
+		EDynamicPropertyHandler propertyHandler = new EDynamicPropertyHandler();
+		Menu menu = XpathtestFactory.eINSTANCE.createMenu();
+		menu.setLabel("foo");
+		assertEquals("foo", propertyHandler.getProperty(menu, "label"));
+	}
+
+	@Test
+	public void test_getProperty_When_UnknownProperty_Expect_NullResult() {
+		EDynamicPropertyHandler propertyHandler = new EDynamicPropertyHandler();
+		Menu menu = XpathtestFactory.eINSTANCE.createMenu();
+		assertNull(propertyHandler.getProperty(menu, "invalid"));
+	}
+
+	@Test
+	public void test_setProperty() {
+		EDynamicPropertyHandler propertyHandler = new EDynamicPropertyHandler();
+		Menu menu = XpathtestFactory.eINSTANCE.createMenu();
+		propertyHandler.setProperty(menu, "label", "foo");
+		assertEquals("foo", menu.getLabel());
+	}
+}