[ARIES-2020] basic Discovery optimizations
diff --git a/cdi-extender/src/main/java/org/apache/aries/cdi/container/internal/annotated/AnnotatedImpl.java b/cdi-extender/src/main/java/org/apache/aries/cdi/container/internal/annotated/AnnotatedImpl.java
index 463fc05..f7c498b 100644
--- a/cdi-extender/src/main/java/org/apache/aries/cdi/container/internal/annotated/AnnotatedImpl.java
+++ b/cdi-extender/src/main/java/org/apache/aries/cdi/container/internal/annotated/AnnotatedImpl.java
@@ -20,18 +20,22 @@
 import java.lang.reflect.AnnotatedElement;
 import java.lang.reflect.Type;
 import java.util.Arrays;
+import java.util.List;
 import java.util.Set;
 
 import javax.enterprise.inject.spi.Annotated;
 
 import org.apache.aries.cdi.container.internal.util.Reflection;
 
-public class AnnotatedImpl<X> implements Annotated {
+public class AnnotatedImpl<X> implements CachingAnnotated {
 
 	private final Type _baseType;
 	private final AnnotatedElement _annotatedElement;
 	private final Set<Type> _typeClosure;
 
+	private Class<? extends Annotation> beanScope;
+	private List<Annotation> collectedAnnotations;
+
 	public AnnotatedImpl(final Type baseType, final AnnotatedElement annotatedElement) {
 		_baseType = baseType;
 		_annotatedElement = annotatedElement;
@@ -39,6 +43,26 @@
 	}
 
 	@Override
+	public List<Annotation> getCollectedAnnotations() {
+		return collectedAnnotations;
+	}
+
+	@Override
+	public void setCollectedAnnotations(final List<Annotation> collectedAnnotations) {
+		this.collectedAnnotations = collectedAnnotations;
+	}
+
+	@Override
+	public Class<? extends Annotation> getBeanScope() {
+		return beanScope;
+	}
+
+	@Override
+	public void setBeanScope(Class<? extends Annotation> beanScope) {
+		this.beanScope = beanScope;
+	}
+
+	@Override
 	public Type getBaseType() {
 		return _baseType;
 	}
diff --git a/cdi-extender/src/main/java/org/apache/aries/cdi/container/internal/annotated/CachingAnnotated.java b/cdi-extender/src/main/java/org/apache/aries/cdi/container/internal/annotated/CachingAnnotated.java
new file mode 100644
index 0000000..9de0236
--- /dev/null
+++ b/cdi-extender/src/main/java/org/apache/aries/cdi/container/internal/annotated/CachingAnnotated.java
@@ -0,0 +1,29 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.aries.cdi.container.internal.annotated;
+
+import javax.enterprise.inject.spi.Annotated;
+import java.lang.annotation.Annotation;
+import java.util.List;
+
+// because Discovery run should be free and not cost as much as a CDI starts we try to reduce its cost by caching meta
+public interface CachingAnnotated extends Annotated {
+    // tested early and generally later too so cache it
+    Class<? extends Annotation> getBeanScope();
+    void setBeanScope(Class<? extends Annotation> value);
+
+    // used in all "tests" so worth a cache
+    List<Annotation> getCollectedAnnotations();
+    void setCollectedAnnotations(List<Annotation> annotations);
+}
diff --git a/cdi-extender/src/main/java/org/apache/aries/cdi/container/internal/container/Discovery.java b/cdi-extender/src/main/java/org/apache/aries/cdi/container/internal/container/Discovery.java
index 07f56b6..71f8d18 100644
--- a/cdi-extender/src/main/java/org/apache/aries/cdi/container/internal/container/Discovery.java
+++ b/cdi-extender/src/main/java/org/apache/aries/cdi/container/internal/container/Discovery.java
@@ -20,6 +20,7 @@
 import java.net.URL;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -136,23 +137,24 @@
 			}
 
 			try {
-				String beanName = Annotates.beanName(annotatedType);
-				Class<? extends Annotation> beanScope = Annotates.beanScope(annotatedType);
-				Map<String, Object> componentProperties = Annotates.componentProperties(annotatedType);
-				ServiceScope serviceScope = Annotates.serviceScope(annotatedType);
-				List<String> serviceTypes = Annotates.serviceClassNames(annotatedType);
-
 				if (annotatedType.isAnnotationPresent(SingleComponent.class)) {
-					doFactoryOrSingleComponent(osgiBean, osgiBean.getBeanClass(), annotatedType, beanName, serviceTypes, serviceScope, componentProperties, ComponentType.SINGLE);
+					doFactoryOrSingleComponent(
+							osgiBean, osgiBean.getBeanClass(), annotatedType, Annotates.beanName(annotatedType),
+							Annotates.serviceClassNames(annotatedType), Annotates.serviceScope(annotatedType),
+							Annotates.componentProperties(annotatedType), ComponentType.SINGLE);
 				}
 				else if (annotatedType.isAnnotationPresent(FactoryComponent.class)) {
-					doFactoryOrSingleComponent(osgiBean, osgiBean.getBeanClass(), annotatedType, beanName, serviceTypes, serviceScope, componentProperties, ComponentType.FACTORY);
+					doFactoryOrSingleComponent(
+							osgiBean, osgiBean.getBeanClass(), annotatedType, Annotates.beanName(annotatedType),
+							Annotates.serviceClassNames(annotatedType), Annotates.serviceScope(annotatedType),
+							Annotates.componentProperties(annotatedType), ComponentType.FACTORY);
 				}
 				else if (annotatedType.isAnnotationPresent(ComponentScoped.class)) {
 					_componentScoped.add(osgiBean);
 				}
 				else {
-					discoverActivations(osgiBean, osgiBean.getBeanClass(), annotatedType, null, beanScope, serviceTypes, serviceScope, componentProperties);
+					discoverActivations(osgiBean, osgiBean.getBeanClass(), annotatedType, null,
+							Annotates.beanScope(annotatedType), Annotates.serviceClassNames(annotatedType));
 				}
 			}
 			catch (Exception e) {
@@ -174,31 +176,25 @@
 			annotatedType.getFields().stream().filter(this::isProduces).forEach(
 				annotatedField -> {
 					Class<? extends Annotation> beanScope = Annotates.beanScope(annotatedField);
-					Map<String, Object> componentProperties = Annotates.componentProperties(annotatedField);
-					ServiceScope serviceScope = Annotates.serviceScope(annotatedField);
 					List<String> serviceTypes = Annotates.serviceClassNames(annotatedField);
-
-					discoverActivations(osgiBean, osgiBean.getBeanClass(), annotatedField, annotatedField, beanScope, serviceTypes, serviceScope, componentProperties);
+					discoverActivations(osgiBean, osgiBean.getBeanClass(), annotatedField, annotatedField, beanScope, serviceTypes);
 				}
 			);
 
-			annotatedType.getMethods().stream().forEach(annotatedMethod -> {
+			annotatedType.getMethods().forEach(annotatedMethod -> {
 				if (isInjectOrProduces(annotatedMethod)) {
-					annotatedMethod.getParameters().stream().forEach(
+					annotatedMethod.getParameters().forEach(
 						annotatedParameter -> processAnnotated(annotatedParameter, annotatedParameter.getBaseType(), Annotates.qualifiers(annotatedParameter), osgiBean)
 					);
 
 					if (isProduces(annotatedMethod)) {
 						Class<? extends Annotation> beanScope = Annotates.beanScope(annotatedMethod);
-						Map<String, Object> componentProperties = Annotates.componentProperties(annotatedMethod);
-						ServiceScope serviceScope = Annotates.serviceScope(annotatedMethod);
 						List<String> serviceTypes = Annotates.serviceClassNames(annotatedMethod);
-
-						discoverActivations(osgiBean, osgiBean.getBeanClass(), annotatedMethod, annotatedMethod, beanScope, serviceTypes, serviceScope, componentProperties);
+						discoverActivations(osgiBean, osgiBean.getBeanClass(), annotatedMethod, annotatedMethod, beanScope, serviceTypes);
 					}
 				}
 				else if (isDisposeOrObserves(annotatedMethod)) {
-					annotatedMethod.getParameters().subList(1, annotatedMethod.getParameters().size()).stream().forEach(
+					annotatedMethod.getParameters().stream().skip(1).forEach(
 						annotatedParameter -> processAnnotated(annotatedParameter, annotatedParameter.getBaseType(), Annotates.qualifiers(annotatedParameter), osgiBean)
 					);
 				}
@@ -311,7 +307,9 @@
 		}
 	}
 
-	void discoverActivations(OSGiBean osgiBean, Class<?> declaringClass, Annotated annotated, AnnotatedMember<?> producer, Class<? extends Annotation> scope, List<String> serviceTypeNames, ServiceScope serviceScope, Map<String, Object> componentProperties) {
+	void discoverActivations(OSGiBean osgiBean, Class<?> declaringClass, final Annotated component,
+							 AnnotatedMember<?> producer, Class<? extends Annotation> scope,
+							 List<String> serviceTypeNames) {
 		String className = declaringClass.getName();
 
 		if (!_containerTemplate.beans.contains(className)) {
@@ -335,8 +333,8 @@
 			activationTemplate.cdiScope = scope;
 			activationTemplate.declaringClass = declaringClass;
 			activationTemplate.producer = producer;
-			activationTemplate.properties = componentProperties;
-			activationTemplate.scope = serviceScope;
+			activationTemplate.properties = Annotates.componentProperties(component);
+			activationTemplate.scope = Annotates.serviceScope(component);
 			activationTemplate.serviceClasses = serviceTypeNames;
 
 			_containerTemplate.activations.add(activationTemplate);
diff --git a/cdi-extender/src/main/java/org/apache/aries/cdi/container/internal/util/Annotates.java b/cdi-extender/src/main/java/org/apache/aries/cdi/container/internal/util/Annotates.java
index 584076b..3911dd7 100644
--- a/cdi-extender/src/main/java/org/apache/aries/cdi/container/internal/util/Annotates.java
+++ b/cdi-extender/src/main/java/org/apache/aries/cdi/container/internal/util/Annotates.java
@@ -16,6 +16,7 @@
 
 import static java.util.Arrays.asList;
 import static java.util.Optional.ofNullable;
+import static java.util.stream.Collectors.toList;
 import static org.apache.aries.cdi.container.internal.util.Reflection.getRawType;
 
 import java.lang.annotation.Annotation;
@@ -25,7 +26,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -58,6 +58,7 @@
 import javax.inject.Scope;
 import javax.interceptor.Interceptor;
 
+import org.apache.aries.cdi.container.internal.annotated.CachingAnnotated;
 import org.apache.aries.cdi.extension.spi.annotation.AdaptedService;
 import org.osgi.service.cdi.ServiceScope;
 import org.osgi.service.cdi.annotations.Service;
@@ -70,22 +71,32 @@
 	}
 
 	private static final Predicate<Annotation> isBeanDefining = annotation ->
-		ApplicationScoped.class.isAssignableFrom(annotation.annotationType()) ||
-		ConversationScoped.class.isAssignableFrom(annotation.annotationType()) ||
-		Decorator.class.isAssignableFrom(annotation.annotationType()) ||
-		Dependent.class.isAssignableFrom(annotation.annotationType()) ||
-		Interceptor.class.isAssignableFrom(annotation.annotationType()) ||
-		RequestScoped.class.isAssignableFrom(annotation.annotationType()) ||
-		SessionScoped.class.isAssignableFrom(annotation.annotationType()) ||
-		Stereotype.class.isAssignableFrom(annotation.annotationType());
+	{
+		// sun.reflect.annotation.AnnotationParser.annotationForMap is a proxy so locally cache the type
+		final Class<? extends Annotation> annotationType = annotation.annotationType();
+		return ApplicationScoped.class.isAssignableFrom(annotationType) ||
+		ConversationScoped.class.isAssignableFrom(annotationType) ||
+		Decorator.class.isAssignableFrom(annotationType) ||
+		Dependent.class.isAssignableFrom(annotationType) ||
+		Interceptor.class.isAssignableFrom(annotationType) ||
+		RequestScoped.class.isAssignableFrom(annotationType) ||
+		SessionScoped.class.isAssignableFrom(annotationType) ||
+		Stereotype.class.isAssignableFrom(annotationType);
+	};
 
 	private static final Predicate<Annotation> isQualifier = annotation ->
-		!annotation.annotationType().equals(Qualifier.class) &&
-		annotation.annotationType().isAnnotationPresent(Qualifier.class);
+	{
+		final Class<? extends Annotation> annotationType = annotation.annotationType();
+		return !annotationType.equals(Qualifier.class) &&
+		annotationType.isAnnotationPresent(Qualifier.class);
+	};
 
 	private static final Predicate<Annotation> isScope = annotation ->
-		annotation.annotationType().isAnnotationPresent(Scope.class) ||
-		annotation.annotationType().isAnnotationPresent(NormalScope.class);
+	{
+		final Class<? extends Annotation> type = annotation.annotationType();
+		return type.isAnnotationPresent(Scope.class) ||
+		type.isAnnotationPresent(NormalScope.class);
+	};
 
 	public static Map<String, Object> componentProperties(Annotated annotated) {
 		return Maps.merge(annotated.getAnnotations());
@@ -157,17 +168,27 @@
 	}
 
 	public static Set<Annotation> collect(Annotated annotated, Predicate<Annotation> predicate) {
-		return collect(annotated.getAnnotations()).stream().filter(predicate).collect(Collectors.toSet());
+		CachingAnnotated cachingAnnotated = CachingAnnotated.class.isInstance(annotated) ?
+				CachingAnnotated.class.cast(annotated) : null;
+		List<Annotation> cached = cachingAnnotated == null ? null : cachingAnnotated.getCollectedAnnotations();
+		if (cached != null) {
+			return cached.stream().filter(predicate).collect(Collectors.toSet());
+		}
+		List<Annotation> collected = collect(annotated.getAnnotations());
+		if (cachingAnnotated != null) {
+			cachingAnnotated.setCollectedAnnotations(collected);
+		}
+		return collected.stream().filter(predicate).collect(Collectors.toSet());
 	}
 
 	private static List<Annotation> collect(Collection<Annotation> annotations) {
-		List<Annotation> list = new ArrayList<>();
-		for (Annotation a1 : annotations) {
-			if (a1.annotationType().getName().startsWith("java.lang.annotation.")) continue;
-			list.add(a1);
-		}
-		list.addAll(inherit(list));
-		return list;
+		return annotations.stream()
+				.flatMap(it -> Stream.concat(
+						Stream.of(it), Stream.of(it.annotationType().getAnnotations()))
+						.filter(a -> !a.annotationType().getName().startsWith("java.lang.annotation."))
+						.distinct())
+				.distinct()
+				.collect(toList());
 	}
 
 	private static List<Annotation> inherit(Collection<Annotation> annotations) {
@@ -336,19 +357,21 @@
 	}
 
 	public static Class<? extends Annotation> beanScope(Annotated annotated, Class<? extends Annotation> defaultValue) {
-		Class<? extends Annotation> scope = collect(annotated.getAnnotations()).stream().filter(isScope).map(Annotation::annotationType).findFirst().orElse(null);
-
-		return (scope == null) ? defaultValue : scope;
+		CachingAnnotated cachingAnnotated = CachingAnnotated.class.isInstance(annotated) ?
+				CachingAnnotated.class.cast(annotated) : null;
+		Class<? extends Annotation> cached = cachingAnnotated == null ? null : cachingAnnotated.getBeanScope();
+		if (cached == null) {
+			cached = collect(annotated.getAnnotations()).stream().filter(isScope).map(Annotation::annotationType).findFirst().orElse(null);
+			if (cachingAnnotated != null) {
+				cachingAnnotated.setBeanScope(cached);
+			}
+		}
+		return cached == null ? defaultValue : cached;
 	}
 
 	public static boolean hasBeanDefiningAnnotations(AnnotatedType<?> annotatedType) {
-		Set<Annotation> beanDefiningAnnotations = new HashSet<>();
-
-		beanDefiningAnnotations.addAll(collect(annotatedType, isBeanDefining));
-		beanDefiningAnnotations.addAll(annotatedType.getFields().stream().flatMap(field -> collect(field, isBeanDefining).stream()).collect(Collectors.toSet()));
-		beanDefiningAnnotations.addAll(annotatedType.getMethods().stream().flatMap(method -> collect(method, isBeanDefining).stream()).collect(Collectors.toSet()));
-
-		return !beanDefiningAnnotations.isEmpty();
+		// only on classes, not on producers
+		return !collect(annotatedType, isBeanDefining).isEmpty();
 	}
 
 }
\ No newline at end of file
diff --git a/cdi-extender/src/main/java/org/apache/aries/cdi/container/internal/util/Maps.java b/cdi-extender/src/main/java/org/apache/aries/cdi/container/internal/util/Maps.java
index 5b457a0..41a5443 100644
--- a/cdi-extender/src/main/java/org/apache/aries/cdi/container/internal/util/Maps.java
+++ b/cdi-extender/src/main/java/org/apache/aries/cdi/container/internal/util/Maps.java
@@ -20,7 +20,6 @@
 import java.lang.annotation.Annotation;
 import java.util.AbstractMap;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Dictionary;
@@ -108,15 +107,6 @@
 		).map(Map::entrySet).flatMap(Collection::stream));
 	}
 
-	public static Map<String, Object> merge(List<Dictionary<String, Object>> dictionaries) {
-		return merge(dictionaries.stream().flatMap(Maps::streamOf));
-	}
-
-	@SafeVarargs
-	public static Map<String, Object> merge(Map<String, Object>... maps) {
-		return merge(Arrays.stream(maps).map(Map::entrySet).flatMap(Collection::stream));
-	}
-
 	public static Map<String, Object> merge(Stream<Map.Entry<String, Object>> mapEntries) {
 		return mapEntries.collect(
 			Collectors.toMap(
@@ -127,15 +117,6 @@
 		);
 	}
 
-	static Map<String, Object> addPrefix(Map<String, Object> map, String prefix) {
-		return map.entrySet().stream().collect(
-			Collectors.toMap(
-				e -> prefix + e.getKey(),
-				Map.Entry::getValue
-			)
-		);
-	}
-
 	@SuppressWarnings("unchecked")
 	public static List<?> mergeValues(Object a, Object b) {
 		List<?> aList = Conversions.convert(a).to(new TypeReference<List<?>>() {});
diff --git a/cdi-extender/src/test/java/org/apache/aries/cdi/container/internal/util/AnnotatesTest.java b/cdi-extender/src/test/java/org/apache/aries/cdi/container/internal/util/AnnotatesTest.java
new file mode 100644
index 0000000..2e7494b
--- /dev/null
+++ b/cdi-extender/src/test/java/org/apache/aries/cdi/container/internal/util/AnnotatesTest.java
@@ -0,0 +1,39 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.aries.cdi.container.internal.util;
+
+import org.apache.aries.cdi.container.internal.annotated.AnnotatedTypeImpl;
+import org.junit.Test;
+import org.osgi.service.cdi.annotations.Service;
+
+import javax.enterprise.context.ApplicationScoped;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class AnnotatesTest {
+    @Test
+    public void isBeanDefining() {
+        assertFalse(Annotates.hasBeanDefiningAnnotations(new AnnotatedTypeImpl<>(A.class)));
+        assertTrue(Annotates.hasBeanDefiningAnnotations(new AnnotatedTypeImpl<>(Single.class)));
+    }
+
+    @ApplicationScoped
+    public static class Single {
+    }
+
+    @Service
+    public static class A {
+    }
+}
diff --git a/cdi-itests/src/main/java/org/apache/aries/cdi/test/cases/TrimTests.java b/cdi-itests/src/main/java/org/apache/aries/cdi/test/cases/TrimTests.java
index d8b914d..9bf394d 100644
--- a/cdi-itests/src/main/java/org/apache/aries/cdi/test/cases/TrimTests.java
+++ b/cdi-itests/src/main/java/org/apache/aries/cdi/test/cases/TrimTests.java
@@ -25,13 +25,15 @@
 public class TrimTests extends SlimBaseTestCase {
 
 	@Test
-	public void testTrimmed() throws Exception {
+	public void testTrimmed() {
 		Bundle tb2Bundle = installBundle.installBundle("tb17.jar");
 
 		ContainerDTO containerDTO = getContainerDTO(tb2Bundle);
 		assertNotNull(containerDTO);
 
-		assertEquals(5, containerDTO.template.components.get(0).beans.size());
+		assertEquals( // expected: B, E, F, G, H
+				String.join(", ", containerDTO.template.components.get(0).beans),
+				5, containerDTO.template.components.get(0).beans.size());
 
 		assertEquals(2, containerDTO.template.components.size());
 
diff --git a/cdi-itests/src/main/java/org/apache/aries/cdi/test/tb17/A.java b/cdi-itests/src/main/java/org/apache/aries/cdi/test/tb17/A.java
index acff571..0fbbf47 100644
--- a/cdi-itests/src/main/java/org/apache/aries/cdi/test/tb17/A.java
+++ b/cdi-itests/src/main/java/org/apache/aries/cdi/test/tb17/A.java
@@ -29,5 +29,4 @@
 	public int getCount() {
 		return 1;
 	}
-
 }