Author: rmannibucau
Date: Sat Apr 21 20:59:25 2018
New Revision: 1829748
URL: http://svn.apache.org/viewvc?rev=1829748&view=rev
Log:
OWB-1241 ensure to not rely on reflection to compare qualifiers of beans
Added:
openwebbeans/trunk/webbeans-impl/src/test/java/org/apache/webbeans/test/qualifier/
openwebbeans/trunk/webbeans-impl/src/test/java/org/apache/webbeans/test/qualifier/CacheUsesQualifierOverridesTest.java
Modified:
openwebbeans/trunk/webbeans-impl/src/main/java/org/apache/webbeans/container/BeanCacheKey.java
openwebbeans/trunk/webbeans-impl/src/main/java/org/apache/webbeans/container/InjectionResolver.java
openwebbeans/trunk/webbeans-impl/src/test/java/org/apache/webbeans/test/annotation/binding/BeanCacheKeyUnitTest.java
Modified:
openwebbeans/trunk/webbeans-impl/src/main/java/org/apache/webbeans/container/BeanCacheKey.java
URL:
http://svn.apache.org/viewvc/openwebbeans/trunk/webbeans-impl/src/main/java/org/apache/webbeans/container/BeanCacheKey.java?rev=1829748&r1=1829747&r2=1829748&view=diff
==============================================================================
---
openwebbeans/trunk/webbeans-impl/src/main/java/org/apache/webbeans/container/BeanCacheKey.java
(original)
+++
openwebbeans/trunk/webbeans-impl/src/main/java/org/apache/webbeans/container/BeanCacheKey.java
Sat Apr 21 20:59:25 2018
@@ -21,6 +21,7 @@ package org.apache.webbeans.container;
import org.apache.webbeans.annotation.EmptyAnnotationLiteral;
import org.apache.webbeans.util.AnnotationUtil;
+import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.util.Nonbinding;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
@@ -28,22 +29,30 @@ import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Comparator;
+import java.util.function.Function;
+import java.util.stream.Stream;
public final class BeanCacheKey
{
+ private static final Comparator<Annotation> ANNOTATION_COMPARATOR = new
AnnotationComparator();
+
private final boolean isDelegate;
private final Type type;
private final String path;
private final Annotation qualifier;
private final Annotation qualifiers[];
private final int hashCode;
- private static final Comparator<Annotation> ANNOTATION_COMPARATOR = new
AnnotationComparator();
+ private volatile LazyAnnotatedTypes lazyAnnotatedTypes; // only needed for
the "main" key
+ private final Function<Class<?>, AnnotatedType<?>> lazyAtLoader;
- public BeanCacheKey(boolean isDelegate, Type type, String path,
Annotation... qualifiers)
+ public BeanCacheKey(boolean isDelegate, Type type, String path,
+ Function<Class<?>, AnnotatedType<?>> lazyAtLoader,
+ Annotation... qualifiers)
{
this.isDelegate = isDelegate;
this.type = type;
this.path = path;
+ this.lazyAtLoader = lazyAtLoader;
int length = qualifiers != null ? qualifiers.length : 0;
if (length == 0)
{
@@ -90,20 +99,43 @@ public final class BeanCacheKey
{
return false;
}
- if (qualifier != null && cacheKey.qualifier != null ?
!qualifierEquals(qualifier, cacheKey.qualifier) : false)
+ if (qualifier != null && cacheKey.qualifier != null)
{
- return false;
+ ensureQualifierAtAreLoaded();
+ if (!qualifierEquals(lazyAnnotatedTypes.qualifierAt, qualifier,
cacheKey.qualifier))
+ {
+ return false;
+ }
}
if (!qualifierArrayEquals(qualifiers, cacheKey.qualifiers))
{
return false;
}
- if (path != null ? !path.equals(cacheKey.path) : cacheKey.path != null)
+ return path != null ? path.equals(cacheKey.path) : cacheKey.path ==
null;
+ }
+
+ private void ensureQualifierAtAreLoaded()
+ {
+ if (lazyAnnotatedTypes == null)
{
- return false;
+ synchronized (this)
+ {
+ if (lazyAnnotatedTypes == null)
+ {
+ if (qualifier != null)
+ {
+ lazyAnnotatedTypes = new
LazyAnnotatedTypes(lazyAtLoader.apply(qualifier.annotationType()), null);
+ }
+ else
+ {
+ lazyAnnotatedTypes = new LazyAnnotatedTypes(null,
Stream.of(qualifiers)
+ .map(Annotation::annotationType)
+ .map(lazyAtLoader)
+ .toArray(AnnotatedType[]::new));
+ }
+ }
+ }
}
-
- return true;
}
private boolean qualifierArrayEquals(Annotation[] qualifiers1,
Annotation[] qualifiers2)
@@ -120,11 +152,12 @@ public final class BeanCacheKey
{
return false;
}
+ ensureQualifierAtAreLoaded();
for (int i = 0; i < qualifiers1.length; i++)
{
Annotation a1 = qualifiers1[i];
Annotation a2 = qualifiers2[i];
- if (a1 == null ? a2 != null : !qualifierEquals(a1, a2))
+ if (a1 == null ? a2 != null :
!qualifierEquals(lazyAnnotatedTypes.qualifierAts[i], a1, a2))
{
return false;
}
@@ -193,9 +226,13 @@ public final class BeanCacheKey
/**
* Implements the equals() method for qualifiers, which ignores {@link
Nonbinding} members.
*/
- private boolean qualifierEquals(Annotation qualifier1, Annotation
qualifier2)
+ private boolean qualifierEquals(AnnotatedType<?> at, Annotation
qualifier1, Annotation qualifier2)
{
- return ANNOTATION_COMPARATOR.compare(qualifier1, qualifier2) == 0;
+ if (at == null)
+ {
+ return AnnotationUtil.isCdiAnnotationEqual(qualifier1, qualifier2);
+ }
+ return AnnotationUtil.isCdiAnnotationEqual(at, qualifier1, qualifier2);
}
@@ -350,4 +387,17 @@ public final class BeanCacheKey
}
}
}
+
+ private static final class LazyAnnotatedTypes
+ {
+ private final AnnotatedType<?> qualifierAt;
+ private final AnnotatedType<?>[] qualifierAts;
+
+ private LazyAnnotatedTypes(final AnnotatedType<?> qualifierAt,
+ final AnnotatedType<?>[] qualifierAts)
+ {
+ this.qualifierAt = qualifierAt;
+ this.qualifierAts = qualifierAts;
+ }
+ }
}
Modified:
openwebbeans/trunk/webbeans-impl/src/main/java/org/apache/webbeans/container/InjectionResolver.java
URL:
http://svn.apache.org/viewvc/openwebbeans/trunk/webbeans-impl/src/main/java/org/apache/webbeans/container/InjectionResolver.java?rev=1829748&r1=1829747&r2=1829748&view=diff
==============================================================================
---
openwebbeans/trunk/webbeans-impl/src/main/java/org/apache/webbeans/container/InjectionResolver.java
(original)
+++
openwebbeans/trunk/webbeans-impl/src/main/java/org/apache/webbeans/container/InjectionResolver.java
Sat Apr 21 20:59:25 2018
@@ -89,7 +89,8 @@ public class InjectionResolver
/**
* This Map contains all resolved beans via it's type and qualifiers.
* If a bean have resolved as not existing, the entry will contain
<code>null</code> as value.
- * The Long key is a hashCode, see {@link
BeanCacheKey#BeanCacheKey(boolean, Type, String, Annotation...)}
+ * The Long key is a hashCode, see
+ * {@link BeanCacheKey#BeanCacheKey(boolean, Type, String,
java.util.function.Function, Annotation...)}
*/
private Map<BeanCacheKey, Set<Bean<?>>> resolvedBeansByType = new
ConcurrentHashMap<>();
@@ -458,7 +459,7 @@ public class InjectionResolver
currentQualifier = true;
}
- Set<Bean<?>> resolvedComponents = null;
+ Set<Bean<?>> resolvedComponents;
BeanCacheKey cacheKey = null;
if (!startup)
@@ -466,7 +467,7 @@ public class InjectionResolver
// we only cache and validate once the set of Beans is final,
otherwise we would cache crap
validateInjectionPointType(injectionPointType);
- cacheKey = new BeanCacheKey(isDelegate, injectionPointType,
bdaBeansXMLFilePath, qualifiers);
+ cacheKey = new BeanCacheKey(isDelegate, injectionPointType,
bdaBeansXMLFilePath, this::findQualifierModel, qualifiers);
resolvedComponents = resolvedBeansByType.get(cacheKey);
if (resolvedComponents != null)
@@ -475,10 +476,7 @@ public class InjectionResolver
}
}
- if (resolvedComponents == null)
- {
- resolvedComponents = new HashSet<>();
- }
+ resolvedComponents = new HashSet<>();
boolean returnAll = injectionPointType.equals(Object.class) &&
currentQualifier;
@@ -809,7 +807,7 @@ public class InjectionResolver
Annotation qualifier = itQualifiers.next();
if
(annot.annotationType().equals(qualifier.annotationType()))
{
- AnnotatedType<?> at =
webBeansContext.getBeanManagerImpl().getAdditionalAnnotatedTypeQualifiers().get(qualifier.annotationType());
+ AnnotatedType<?> at =
findQualifierModel(qualifier.annotationType());
if (at == null)
{
if (AnnotationUtil.isCdiAnnotationEqual(qualifier,
annot))
@@ -837,4 +835,9 @@ public class InjectionResolver
return result;
}
+
+ private AnnotatedType<? extends Annotation> findQualifierModel(final
Class<?> qualifier)
+ {
+ return
webBeansContext.getBeanManagerImpl().getAdditionalAnnotatedTypeQualifiers().get(qualifier);
+ }
}
Modified:
openwebbeans/trunk/webbeans-impl/src/test/java/org/apache/webbeans/test/annotation/binding/BeanCacheKeyUnitTest.java
URL:
http://svn.apache.org/viewvc/openwebbeans/trunk/webbeans-impl/src/test/java/org/apache/webbeans/test/annotation/binding/BeanCacheKeyUnitTest.java?rev=1829748&r1=1829747&r2=1829748&view=diff
==============================================================================
---
openwebbeans/trunk/webbeans-impl/src/test/java/org/apache/webbeans/test/annotation/binding/BeanCacheKeyUnitTest.java
(original)
+++
openwebbeans/trunk/webbeans-impl/src/test/java/org/apache/webbeans/test/annotation/binding/BeanCacheKeyUnitTest.java
Sat Apr 21 20:59:25 2018
@@ -127,8 +127,8 @@ public class BeanCacheKeyUnitTest
@Test
public void testEmptyNull()
{
- BeanCacheKey a = new BeanCacheKey(true, String.class, null);
- BeanCacheKey b = new BeanCacheKey(true, String.class, null);
+ BeanCacheKey a = new BeanCacheKey(true, String.class, null, it ->
null);
+ BeanCacheKey b = new BeanCacheKey(true, String.class, null, it ->
null);
Assert.assertEquals(a, b);
Assert.assertEquals(a.hashCode(), b.hashCode());
}
@@ -145,8 +145,8 @@ public class BeanCacheKeyUnitTest
@Test
public void testTypeUnequal()
{
- BeanCacheKey a = new BeanCacheKey(true, String.class, null);
- BeanCacheKey b = new BeanCacheKey(true, Integer.class, null);
+ BeanCacheKey a = new BeanCacheKey(true, String.class, null, it ->
null);
+ BeanCacheKey b = new BeanCacheKey(true, Integer.class, null, it ->
null);
Assert.assertFalse(a.equals(b));
Assert.assertFalse(a.hashCode() == b.hashCode());
}
@@ -154,8 +154,8 @@ public class BeanCacheKeyUnitTest
@Test
public void testPath()
{
- BeanCacheKey a = new BeanCacheKey(true, String.class, "A");
- BeanCacheKey b = new BeanCacheKey(true, String.class, "A");
+ BeanCacheKey a = new BeanCacheKey(true, String.class, "A", it -> null);
+ BeanCacheKey b = new BeanCacheKey(true, String.class, "A", it -> null);
Assert.assertEquals(a, b);
Assert.assertEquals(a.hashCode(), b.hashCode());
}
@@ -163,8 +163,8 @@ public class BeanCacheKeyUnitTest
@Test
public void testPathUnequal()
{
- BeanCacheKey a = new BeanCacheKey(true, String.class, "A");
- BeanCacheKey b = new BeanCacheKey(true, String.class, "B");
+ BeanCacheKey a = new BeanCacheKey(true, String.class, "A", it -> null);
+ BeanCacheKey b = new BeanCacheKey(true, String.class, "B", it -> null);
Assert.assertFalse(a.equals(b));
Assert.assertFalse(a.hashCode() == b.hashCode());
}
@@ -172,16 +172,16 @@ public class BeanCacheKeyUnitTest
@Test
public void testNonEqualsWithBindingMemberParameter()
{
- BeanCacheKey a = new BeanCacheKey(true, String.class, null, a1);
- BeanCacheKey b = new BeanCacheKey(true, String.class, null, a2);
+ BeanCacheKey a = new BeanCacheKey(true, String.class, null, it ->
null, a1);
+ BeanCacheKey b = new BeanCacheKey(true, String.class, null, it ->
null, a2);
Assert.assertFalse(a.equals(b));
}
@Test
public void testEqualsWithBindingMember()
{
- BeanCacheKey a = new BeanCacheKey(true, String.class, null, a1);
- BeanCacheKey b = new BeanCacheKey(true, String.class, null, a1);
+ BeanCacheKey a = new BeanCacheKey(true, String.class, null, it ->
null, a1);
+ BeanCacheKey b = new BeanCacheKey(true, String.class, null, it ->
null, a1);
Assert.assertEquals(a, b);
Assert.assertEquals(a.hashCode(), b.hashCode());
}
@@ -190,8 +190,8 @@ public class BeanCacheKeyUnitTest
@Test
public void testEqualsWithNonBindingMember()
{
- BeanCacheKey a = new BeanCacheKey(true, String.class, null, a3);
- BeanCacheKey b = new BeanCacheKey(true, String.class, null, a3);
+ BeanCacheKey a = new BeanCacheKey(true, String.class, null, it ->
null, a3);
+ BeanCacheKey b = new BeanCacheKey(true, String.class, null, it ->
null, a3);
Assert.assertEquals(a, b);
Assert.assertEquals(a.hashCode(), b.hashCode());
}
@@ -199,8 +199,8 @@ public class BeanCacheKeyUnitTest
@Test
public void testEquals2Annotations()
{
- BeanCacheKey a = new BeanCacheKey(true, String.class, null, a12);
- BeanCacheKey b = new BeanCacheKey(true, String.class, null, a12);
+ BeanCacheKey a = new BeanCacheKey(true, String.class, null, it ->
null, a12);
+ BeanCacheKey b = new BeanCacheKey(true, String.class, null, it ->
null, a12);
Assert.assertEquals(a, b);
Assert.assertEquals(a.hashCode(), b.hashCode());
}
@@ -208,8 +208,8 @@ public class BeanCacheKeyUnitTest
@Test
public void testEquals2AnnotationsUnorderedName()
{
- BeanCacheKey a = new BeanCacheKey(true, String.class, null, a13);
- BeanCacheKey b = new BeanCacheKey(true, String.class, null, a31);
+ BeanCacheKey a = new BeanCacheKey(true, String.class, null, it ->
null, a13);
+ BeanCacheKey b = new BeanCacheKey(true, String.class, null, it ->
null, a31);
Assert.assertEquals(a, b);
Assert.assertEquals(a.hashCode(), b.hashCode());
}
@@ -217,8 +217,8 @@ public class BeanCacheKeyUnitTest
@Test
public void testEquals2AnnotationsUnorderedParam()
{
- BeanCacheKey a = new BeanCacheKey(true, String.class, null, a12);
- BeanCacheKey b = new BeanCacheKey(true, String.class, null, a21);
+ BeanCacheKey a = new BeanCacheKey(true, String.class, null, it ->
null, a12);
+ BeanCacheKey b = new BeanCacheKey(true, String.class, null, it ->
null, a21);
Assert.assertEquals(a, b);
Assert.assertEquals(a.hashCode(), b.hashCode());
}
@@ -226,16 +226,16 @@ public class BeanCacheKeyUnitTest
@Test
public void testDiffMembers()
{
- BeanCacheKey a = new BeanCacheKey(true, String.class, null, a4);
- BeanCacheKey b = new BeanCacheKey(true, String.class, null, a5);
+ BeanCacheKey a = new BeanCacheKey(true, String.class, null, it ->
null, a4);
+ BeanCacheKey b = new BeanCacheKey(true, String.class, null, it ->
null, a5);
Assert.assertFalse(a.equals(b));
}
@Test
public void testOnyDiffMembersInNonBinding()
{
- BeanCacheKey a = new BeanCacheKey(true, String.class, null, a5);
- BeanCacheKey b = new BeanCacheKey(true, String.class, null, a6);
+ BeanCacheKey a = new BeanCacheKey(true, String.class, null, it ->
null, a5);
+ BeanCacheKey b = new BeanCacheKey(true, String.class, null, it ->
null, a6);
Assert.assertEquals(a, b);
Assert.assertEquals(a.hashCode(), b.hashCode());
}
@@ -243,8 +243,8 @@ public class BeanCacheKeyUnitTest
@Test
public void testAnnotationOrdering()
{
- BeanCacheKey a = new BeanCacheKey(true, String.class, null, a56);
- BeanCacheKey b = new BeanCacheKey(true, String.class, null, a65);
+ BeanCacheKey a = new BeanCacheKey(true, String.class, null, it ->
null, a56);
+ BeanCacheKey b = new BeanCacheKey(true, String.class, null, it ->
null, a65);
Assert.assertEquals(a, b);
Assert.assertEquals(a.hashCode(), b.hashCode());
}
@@ -252,32 +252,32 @@ public class BeanCacheKeyUnitTest
@Test
public void testMemberArraysInt()
{
- BeanCacheKey a = new BeanCacheKey(true, String.class, null, a7);
- BeanCacheKey b = new BeanCacheKey(true, String.class, null, a8);
+ BeanCacheKey a = new BeanCacheKey(true, String.class, null, it ->
null, a7);
+ BeanCacheKey b = new BeanCacheKey(true, String.class, null, it ->
null, a8);
Assert.assertFalse(a.equals(b));
}
@Test
public void testMemberArraysString()
{
- BeanCacheKey a = new BeanCacheKey(true, String.class, null, a9);
- BeanCacheKey b = new BeanCacheKey(true, String.class, null, aa);
+ BeanCacheKey a = new BeanCacheKey(true, String.class, null, it ->
null, a9);
+ BeanCacheKey b = new BeanCacheKey(true, String.class, null, it ->
null, aa);
Assert.assertFalse(a.equals(b));
}
@Test
public void testMemberArraysBoolean()
{
- BeanCacheKey a = new BeanCacheKey(true, String.class, null, ab);
- BeanCacheKey b = new BeanCacheKey(true, String.class, null, ac);
+ BeanCacheKey a = new BeanCacheKey(true, String.class, null, it ->
null, ab);
+ BeanCacheKey b = new BeanCacheKey(true, String.class, null, it ->
null, ac);
Assert.assertFalse(a.equals(b));
}
@Test
public void testDiffArrays()
{
- BeanCacheKey a = new BeanCacheKey(true, String.class, null, a9a);
- BeanCacheKey b = new BeanCacheKey(true, String.class, null, abc);
+ BeanCacheKey a = new BeanCacheKey(true, String.class, null, it ->
null, a9a);
+ BeanCacheKey b = new BeanCacheKey(true, String.class, null, it ->
null, abc);
Assert.assertFalse(a.equals(b));
Assert.assertFalse(a.hashCode() == b.hashCode());
}
@@ -286,8 +286,8 @@ public class BeanCacheKeyUnitTest
@Test
public void testDelegateUnequal()
{
- BeanCacheKey a = new BeanCacheKey(true, String.class, "A");
- BeanCacheKey b = new BeanCacheKey(false, String.class, "A");
+ BeanCacheKey a = new BeanCacheKey(true, String.class, "A", it -> null);
+ BeanCacheKey b = new BeanCacheKey(false, String.class, "A", it ->
null);
Assert.assertFalse(a.equals(b));
Assert.assertFalse(a.hashCode() == b.hashCode());
}
Added:
openwebbeans/trunk/webbeans-impl/src/test/java/org/apache/webbeans/test/qualifier/CacheUsesQualifierOverridesTest.java
URL:
http://svn.apache.org/viewvc/openwebbeans/trunk/webbeans-impl/src/test/java/org/apache/webbeans/test/qualifier/CacheUsesQualifierOverridesTest.java?rev=1829748&view=auto
==============================================================================
---
openwebbeans/trunk/webbeans-impl/src/test/java/org/apache/webbeans/test/qualifier/CacheUsesQualifierOverridesTest.java
(added)
+++
openwebbeans/trunk/webbeans-impl/src/test/java/org/apache/webbeans/test/qualifier/CacheUsesQualifierOverridesTest.java
Sat Apr 21 20:59:25 2018
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.webbeans.test.qualifier;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.util.function.Supplier;
+
+import javax.enterprise.context.Dependent;
+import javax.enterprise.event.Observes;
+import javax.enterprise.inject.spi.BeforeBeanDiscovery;
+import javax.enterprise.inject.spi.Extension;
+import javax.enterprise.util.AnnotationLiteral;
+import javax.enterprise.util.Nonbinding;
+import javax.inject.Qualifier;
+
+import org.apache.webbeans.config.OwbParametrizedTypeImpl;
+import org.apache.webbeans.test.AbstractUnitTest;
+import org.junit.Test;
+
+public class CacheUsesQualifierOverridesTest extends AbstractUnitTest
+{
+ @Test
+ public void run()
+ {
+ addExtension(new Extension() {
+ void changeQualifier(@Observes final BeforeBeanDiscovery
beforeBeanDiscovery) {
+ beforeBeanDiscovery.configureQualifier(TheQualifier.class)
+ .methods().forEach(m -> m.remove(it ->
it.annotationType() == Nonbinding.class));
+ }
+ });
+ startContainer(Impl1.class, Impl2.class);
+ final OwbParametrizedTypeImpl type = new OwbParametrizedTypeImpl(null,
Supplier.class, String.class);
+ final Supplier<String> uno = getInstance(type, new
TheQualifier.Literal("uno"));
+ final Supplier<String> due = getInstance(type, new
TheQualifier.Literal("due"));
+ assertEquals("1", uno.get());
+ assertEquals("2", due.get());
+ // redundant but this is the real test here, previous are just nicer
to read in the output
+ assertNotEquals(uno.getClass(), due.getClass());
+ }
+
+ @Dependent
+ @TheQualifier("uno")
+ public static class Impl1 implements Supplier<String>
+ {
+ @Override
+ public String get()
+ {
+ return "1";
+ }
+ }
+
+ @Dependent
+ @TheQualifier("due")
+ public static class Impl2 implements Supplier<String>
+ {
+ @Override
+ public String get()
+ {
+ return "2";
+ }
+ }
+
+ @Qualifier
+ @Target({FIELD, TYPE})
+ @Retention(RUNTIME)
+ public @interface TheQualifier
+ {
+ @Nonbinding
+ String value();
+
+ class Literal extends AnnotationLiteral<TheQualifier> implements
TheQualifier
+ {
+ private final String value;
+
+ public Literal(final String value)
+ {
+ this.value = value;
+ }
+
+ @Override
+ public String value()
+ {
+ return value;
+ }
+ }
+ }
+}