You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by da...@apache.org on 2016/08/22 21:12:22 UTC

svn commit: r1757276 - in /felix/trunk/converter/src: main/java/org/apache/felix/converter/impl/ main/java/org/osgi/service/converter/ test/java/org/apache/felix/converter/impl/

Author: davidb
Date: Mon Aug 22 21:12:22 2016
New Revision: 1757276

URL: http://svn.apache.org/viewvc?rev=1757276&view=rev
Log:
Felix converter: more flexible adapters.

Added:
    felix/trunk/converter/src/main/java/org/osgi/service/converter/ConvertFunction.java
    felix/trunk/converter/src/main/java/org/osgi/service/converter/SimpleConvertFunction.java
Removed:
    felix/trunk/converter/src/main/java/org/osgi/service/converter/FunctionThrowsException.java
Modified:
    felix/trunk/converter/src/main/java/org/apache/felix/converter/impl/AdapterImpl.java
    felix/trunk/converter/src/main/java/org/apache/felix/converter/impl/ConverterService.java
    felix/trunk/converter/src/main/java/org/osgi/service/converter/Adapter.java
    felix/trunk/converter/src/main/java/org/osgi/service/converter/Rule.java
    felix/trunk/converter/src/test/java/org/apache/felix/converter/impl/AdapterTest.java

Modified: felix/trunk/converter/src/main/java/org/apache/felix/converter/impl/AdapterImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/converter/src/main/java/org/apache/felix/converter/impl/AdapterImpl.java?rev=1757276&r1=1757275&r2=1757276&view=diff
==============================================================================
--- felix/trunk/converter/src/main/java/org/apache/felix/converter/impl/AdapterImpl.java (original)
+++ felix/trunk/converter/src/main/java/org/apache/felix/converter/impl/AdapterImpl.java Mon Aug 22 21:12:22 2016
@@ -17,21 +17,29 @@
 package org.apache.felix.converter.impl;
 
 import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
 import org.osgi.service.converter.Adapter;
 import org.osgi.service.converter.ConversionException;
+import org.osgi.service.converter.ConvertFunction;
 import org.osgi.service.converter.Converter;
 import org.osgi.service.converter.Converting;
-import org.osgi.service.converter.FunctionThrowsException;
 import org.osgi.service.converter.Rule;
+import org.osgi.service.converter.SimpleConvertFunction;
 import org.osgi.service.converter.TypeReference;
 
 public class AdapterImpl implements Adapter, InternalConverter {
     private final InternalConverter delegate;
-    private final Map<TypePair, FunctionThrowsException<Object, Object>> classRules =
+    private final Map<TypePair, ConvertFunction<Object, Object>> classRules =
             new ConcurrentHashMap<>();
 
     AdapterImpl(InternalConverter converter) {
@@ -50,42 +58,46 @@ public class AdapterImpl implements Adap
         return new AdapterImpl(this);
     }
 
-    @SuppressWarnings("unchecked")
     @Override
+    @SuppressWarnings("unchecked")
     public <F, T> Adapter rule(Class<F> fromCls, Class<T> toCls,
-            FunctionThrowsException<F, T> toFun, FunctionThrowsException<T, F> fromFun) {
+            SimpleConvertFunction<F, T> toFun, SimpleConvertFunction<T, F> fromFun) {
         if (fromCls.equals(toCls))
             throw new IllegalArgumentException();
 
-        classRules.put(new TypePair(fromCls, toCls), (FunctionThrowsException<Object, Object>) toFun);
-        classRules.put(new TypePair(toCls, fromCls), (FunctionThrowsException<Object, Object>) fromFun);
+        classRules.put(new TypePair(fromCls, toCls), (ConvertFunction<Object, Object>) toFun);
+        classRules.put(new TypePair(toCls, fromCls), (ConvertFunction<Object, Object>) fromFun);
         return this;
     }
 
     @Override
     public <F, T> Adapter rule(TypeReference<F> fromRef, TypeReference<T> toRef,
-            FunctionThrowsException<F, T> toFun, FunctionThrowsException<T, F> fromFun) {
+            SimpleConvertFunction<F, T> toFun, SimpleConvertFunction<T, F> fromFun) {
         // TODO Auto-generated method stub
         return null;
     }
 
     @Override
     public <F, T> Adapter rule(Type fromType, Type toType,
-            FunctionThrowsException<F, T> toFun, FunctionThrowsException<T, F> fromFun) {
-        // TODO Auto-generated method stub
-        return null;
-    }
-
-    @Override
-    public <F, T> Adapter rule(FunctionThrowsException<F, T> toFun, FunctionThrowsException<T, F> fromFun) {
+            SimpleConvertFunction<F, T> toFun, SimpleConvertFunction<T, F> fromFun) {
         // TODO Auto-generated method stub
         return null;
     }
 
     @Override
+    @SuppressWarnings("unchecked")
     public <F, T> Adapter rule(Rule<F, T> rule) {
-        // TODO Auto-generated method stub
-        return null;
+        ConvertFunction<F, T> toFun = rule.getToFunction();
+        if (toFun != null)
+            classRules.put(new TypePair(rule.getFromClass(), rule.getToClass()),
+                (ConvertFunction<Object, Object>) toFun);
+
+
+        ConvertFunction<T, F> fromFun = rule.getFromFunction();
+        if (fromFun != null)
+            classRules.put(new TypePair(rule.getToClass(), rule.getFromClass()),
+                (ConvertFunction<Object, Object>) fromFun);
+        return this;
     }
 
     private class ConvertingWrapper implements InternalConverting {
@@ -128,11 +140,33 @@ public class AdapterImpl implements Adap
         @Override
         public Object to(Type type) {
             if (object != null) {
-                FunctionThrowsException<Object, Object> f = classRules.get(
-                    new TypePair(object.getClass(), Util.primitiveToBoxed(type)));
-                if (f != null) {
+                Set<Type> fromTypes = assignableTypes(object.getClass());
+                Set<Type> toTypes = assignableTypes(type);
+
+                List<ConvertFunction<Object, Object>> converters = new ArrayList<>();
+                for (Type fromType : fromTypes) {
+                    for (Type toType : toTypes) {
+                        // TODO what exactly do we use as order here?
+                        converters.add(classRules.get(new TypePair(fromType, Util.primitiveToBoxed(toType))));
+                    }
+                }
+                for (Type fromType : fromTypes) {
+                    converters.add(classRules.get(new TypePair(fromType, Object.class)));
+                }
+                for (Type toType : toTypes) {
+                    converters.add(classRules.get(new TypePair(Object.class, Util.primitiveToBoxed(toType))));
+                }
+
+                for (Iterator<ConvertFunction<Object, Object>> it = converters.iterator(); it.hasNext(); ) {
+                    ConvertFunction<Object, Object> func = it.next();
+                    it.remove();
+                    if (func == null)
+                        continue;
+
                     try {
-                        return f.apply(object);
+                        Object res = func.convert(object, type);
+                        if (res != ConvertFunction.CANNOT_CONVERT)
+                            return res;
                     } catch (Exception ex) {
                         if (hasDefault)
                             return defaultValue;
@@ -151,6 +185,21 @@ public class AdapterImpl implements Adap
         }
     }
 
+    private static Set<Type> assignableTypes(Type mostSpecialized) {
+        if (!(mostSpecialized instanceof Class))
+            return Collections.singleton(mostSpecialized);
+
+        Class<?> curClass = (Class<?>) mostSpecialized;
+        Set<Type> lookupTypes = new LinkedHashSet<>(); // Iteration order matters!
+        while((curClass != null) && (!(Object.class.equals(curClass)))) {
+            lookupTypes.add(curClass);
+            lookupTypes.addAll(Arrays.asList(curClass.getInterfaces()));
+            curClass = curClass.getSuperclass();
+        }
+        lookupTypes.add(Object.class); // Object is the superclass of any type
+        return lookupTypes;
+    }
+
     static class TypePair {
         private final Type from;
         private final Type to;

Modified: felix/trunk/converter/src/main/java/org/apache/felix/converter/impl/ConverterService.java
URL: http://svn.apache.org/viewvc/felix/trunk/converter/src/main/java/org/apache/felix/converter/impl/ConverterService.java?rev=1757276&r1=1757275&r2=1757276&view=diff
==============================================================================
--- felix/trunk/converter/src/main/java/org/apache/felix/converter/impl/ConverterService.java (original)
+++ felix/trunk/converter/src/main/java/org/apache/felix/converter/impl/ConverterService.java Mon Aug 22 21:12:22 2016
@@ -34,19 +34,24 @@ public class ConverterService implements
 
     public ConverterService() {
         Adapter a = new ConverterImpl().getAdapter();
+        a.rule(Byte.class, String.class, v -> v.toString(), Byte::parseByte); // TODO test
         a.rule(Character.class, Boolean.class, v -> v.charValue() != 0,
                 v -> v.booleanValue() ? (char) 1 : (char) 0);
         a.rule(Character.class, String.class, v -> v.toString(),
                 v -> v.length() > 0 ? v.charAt(0) : 0);
         a.rule(Class.class, String.class, Class::toString,
                 v -> getClass().getClassLoader().loadClass(v));
+        a.rule(Double.class, String.class, v -> v.toString(), Double::parseDouble); // TODO test
+        a.rule(Float.class, String.class, v -> v.toString(), Float::parseFloat); // TODO test
         a.rule(Integer.class, String.class, v -> v.toString(), Integer::parseInt);
         a.rule(LocalDateTime.class, String.class, LocalDateTime::toString, LocalDateTime::parse);
         a.rule(LocalDate.class, String.class, LocalDate::toString, LocalDate::parse);
         a.rule(LocalTime.class, String.class, LocalTime::toString, LocalTime::parse);
+        a.rule(Long.class, String.class, v -> v.toString(), Long::parseLong); // TODO test
         a.rule(OffsetDateTime.class, String.class, OffsetDateTime::toString, OffsetDateTime::parse);
         a.rule(OffsetTime.class, String.class, OffsetTime::toString, OffsetTime::parse);
         a.rule(Pattern.class, String.class, Pattern::toString, Pattern::compile);
+        a.rule(Short.class, String.class, v -> v.toString(), Short::parseShort); // TODO test
         a.rule(UUID.class, String.class, UUID::toString, UUID::fromString);
         a.rule(ZonedDateTime.class, String.class, ZonedDateTime::toString, ZonedDateTime::parse);
         adapter = a;

Modified: felix/trunk/converter/src/main/java/org/osgi/service/converter/Adapter.java
URL: http://svn.apache.org/viewvc/felix/trunk/converter/src/main/java/org/osgi/service/converter/Adapter.java?rev=1757276&r1=1757275&r2=1757276&view=diff
==============================================================================
--- felix/trunk/converter/src/main/java/org/osgi/service/converter/Adapter.java (original)
+++ felix/trunk/converter/src/main/java/org/osgi/service/converter/Adapter.java Mon Aug 22 21:12:22 2016
@@ -58,24 +58,11 @@ public interface Adapter extends Convert
 	 * @param fromFun the function to perform the reverse conversion.
 	 * @return The current adapter, can be used to chain invocations.
 	 */
-	<F, T> Adapter rule(Class<F> fromCls, Class<T> toCls, FunctionThrowsException<F,T> toFun,
-	        FunctionThrowsException<T,F> fromFun);
+	<F, T> Adapter rule(Class<F> fromCls, Class<T> toCls, SimpleConvertFunction<F, T> toFun,
+	        SimpleConvertFunction<T, F> fromFun);
 
-	/**
-	 * Specify a rule for the conversion to and from two classes. The rule
-	 * specifies the conversion in both directions. This overload makes it easy
-	 * to provide the conversions as method references.
-	 *
-	 * @param <F> the type to convert from.
-	 * @param <T> the type to convert to.
-	 * @param toFun the function to perform the conversion.
-	 * @param fromFun the function to perform the reverse conversion.
-	 * @return The current adapter, can be used to chain invocations.
-	 */
-	<F, T> Adapter rule(FunctionThrowsException<F,T> toFun, FunctionThrowsException<T,F> fromFun);
-
-    <F, T> Adapter rule(TypeReference<F> fromRef, TypeReference<T> toRef, FunctionThrowsException<F, T> toFun,
-            FunctionThrowsException<T, F> fromFun);
+    <F, T> Adapter rule(TypeReference<F> fromRef, TypeReference<T> toRef, SimpleConvertFunction<F, T> toFun,
+            SimpleConvertFunction<T, F> fromFun);
 
-    <F, T> Adapter rule(Type fromType, Type toType, FunctionThrowsException<F, T> toFun, FunctionThrowsException<T, F> fromFun);
+    <F, T> Adapter rule(Type fromType, Type toType, SimpleConvertFunction<F, T> toFun, SimpleConvertFunction<T, F> fromFun);
 }

Added: felix/trunk/converter/src/main/java/org/osgi/service/converter/ConvertFunction.java
URL: http://svn.apache.org/viewvc/felix/trunk/converter/src/main/java/org/osgi/service/converter/ConvertFunction.java?rev=1757276&view=auto
==============================================================================
--- felix/trunk/converter/src/main/java/org/osgi/service/converter/ConvertFunction.java (added)
+++ felix/trunk/converter/src/main/java/org/osgi/service/converter/ConvertFunction.java Mon Aug 22 21:12:22 2016
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) OSGi Alliance (2016). All Rights Reserved.
+ *
+ * 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.osgi.service.converter;
+
+import java.lang.reflect.Type;
+
+/**
+ * An functional interface with a single convert method that is passed
+ * the original object and the target type.
+ * @param <F> Type parameter for the source object.
+ * @param <T> Type parameter for the converted object.
+ */
+@FunctionalInterface
+public interface ConvertFunction<F, T> {
+    /**
+     * Used to indicate that the default converter
+     * should be used for this value.
+     */
+    public static final Object CANNOT_CONVERT = new Object();
+
+    /**
+     * Convert the object into the target type.
+     * @param obj The object to be converted.
+     * @param targetType The target type.
+     * @return The converted object or {@link #CANNOT_CONVERT} to indicate
+     * that this converter cannot handle the conversion.
+     */
+    T convert(F obj, Type targetType) throws Exception;
+}

Modified: felix/trunk/converter/src/main/java/org/osgi/service/converter/Rule.java
URL: http://svn.apache.org/viewvc/felix/trunk/converter/src/main/java/org/osgi/service/converter/Rule.java?rev=1757276&r1=1757275&r2=1757276&view=diff
==============================================================================
--- felix/trunk/converter/src/main/java/org/osgi/service/converter/Rule.java (original)
+++ felix/trunk/converter/src/main/java/org/osgi/service/converter/Rule.java Mon Aug 22 21:12:22 2016
@@ -25,26 +25,71 @@ package org.osgi.service.converter;
  * @Immutable
  */
 public class Rule<F, T> {
-	private final FunctionThrowsException<F,T>	toFun;
-	private final FunctionThrowsException<T,F>	fromFun;
+    private final Class<F> fromClass;
+    private final Class<T> toClass;
+	private final ConvertFunction<T,F>	fromFun;
+    private final ConvertFunction<F,T>  toFun;
 
-	/**
-	 * Specify the functions to do the conversions in both directions.
-	 *
-	 * @param to The function that performs the conversion.
-	 * @param from The function that performs the reverse conversion.
-	 */
-	public Rule(FunctionThrowsException<F,T> to, FunctionThrowsException<T,F> from) {
+    /**
+     * Create a bidirectional rule.
+     * @param fromCls The class from which to convert. If {@link Object} is specified then this
+     * functions as a wildcard for generic conversions.
+     * @param toCls The class to which to convert. If {@link Object} is specified then this
+     * functions as a wildcard for generic conversions.
+     * @param to The conversion function for this rule.
+     * @param from The reverse conversion for this rule.
+     */
+	public Rule(Class<F> fromCls, Class<T> toCls, ConvertFunction<F,T> to, ConvertFunction<T,F> from) {
+	    if (fromCls.equals(toCls)) {
+	        if (fromCls.equals(Object.class)) {
+	            if (from != null) {
+	                throw new IllegalStateException("Can only register one catchall converter");
+	            }
+	        } else {
+	            throw new IllegalStateException("Cannot register a convert to itself");
+	        }
+	    }
+
+	    fromClass = fromCls;
+	    toClass = toCls;
 		toFun = to;
 		fromFun = from;
 	}
 
+    /**
+     * Create a single-direction rule.
+     * @param fromCls The class from which to convert. If {@link Object} is specified then this
+     * functions as a wildcard for generic conversions.
+     * @param toCls The class to which to convert. If {@link Object} is specified then this
+     * functions as a wildcard for generic conversions.
+     * @param to The conversion function for this rule.
+     */
+    public Rule(Class<F> fromCls, Class<T> toCls, ConvertFunction<F,T> to) {
+        this(fromCls, toCls, to, null);
+    }
+
+    /**
+	 * Accessor for the class to convert from.
+	 * @return The class to convert from.
+	 */
+	public Class<F> getFromClass() {
+        return fromClass;
+    }
+
+	/**
+	 * Accessor for the class to convert to.
+	 * @return The class to convert to.
+	 */
+    public Class<T> getToClass() {
+        return toClass;
+    }
+
 	/**
 	 * Obtain the conversion function.
 	 *
 	 * @return The conversion function.
 	 */
-	public FunctionThrowsException<F,T> getToFunction() {
+	public ConvertFunction<F,T> getToFunction() {
 		return toFun;
 	}
 
@@ -53,7 +98,7 @@ public class Rule<F, T> {
 	 *
 	 * @return The reverse conversion function.
 	 */
-	public FunctionThrowsException<T,F> getFromFunction() {
+	public ConvertFunction<T,F> getFromFunction() {
 		return fromFun;
 	}
 }

Added: felix/trunk/converter/src/main/java/org/osgi/service/converter/SimpleConvertFunction.java
URL: http://svn.apache.org/viewvc/felix/trunk/converter/src/main/java/org/osgi/service/converter/SimpleConvertFunction.java?rev=1757276&view=auto
==============================================================================
--- felix/trunk/converter/src/main/java/org/osgi/service/converter/SimpleConvertFunction.java (added)
+++ felix/trunk/converter/src/main/java/org/osgi/service/converter/SimpleConvertFunction.java Mon Aug 22 21:12:22 2016
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) OSGi Alliance (2016). All Rights Reserved.
+ *
+ * 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.osgi.service.converter;
+
+import java.lang.reflect.Type;
+
+/**
+ * An functional interface with a single convert method that is passed
+ * the object to convert. This interface implements the
+ * {@link ConvertFunction} interface via a default method and can be used
+ * in cases where the target type does not need to be passed to the
+ * {@link #convert} method, for example in Lambda expressions.
+ * @param <F> Type parameter for the source object.
+ * @param <T> Type parameter for the converted object.
+ */
+@FunctionalInterface
+public interface SimpleConvertFunction<F, T> extends ConvertFunction<F, T> {
+    default T convert(F t, Type type) throws Exception {
+        return convert(t);
+    }
+
+    /**
+     * Convert the object into the target type.
+     * @param obj The object to be converted.
+     * @return The converted object or {@link #CANNOT_CONVERT} to indicate
+     * that this converter cannot handle the conversion.
+     */
+    T convert(F t) throws Exception;
+}

Modified: felix/trunk/converter/src/test/java/org/apache/felix/converter/impl/AdapterTest.java
URL: http://svn.apache.org/viewvc/felix/trunk/converter/src/test/java/org/apache/felix/converter/impl/AdapterTest.java?rev=1757276&r1=1757275&r2=1757276&view=diff
==============================================================================
--- felix/trunk/converter/src/test/java/org/apache/felix/converter/impl/AdapterTest.java (original)
+++ felix/trunk/converter/src/test/java/org/apache/felix/converter/impl/AdapterTest.java Mon Aug 22 21:12:22 2016
@@ -16,6 +16,14 @@
  */
 package org.apache.felix.converter.impl;
 
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
@@ -23,7 +31,9 @@ import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.osgi.service.converter.Adapter;
+import org.osgi.service.converter.ConvertFunction;
 import org.osgi.service.converter.Converter;
+import org.osgi.service.converter.Rule;
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
@@ -56,4 +66,59 @@ public class AdapterTest {
         assertArrayEquals(new String [] {"A","B"},
                 ca.convert("A,B").to(String[].class));
     }
+
+    @Test @SuppressWarnings("rawtypes")
+    public void testWildcardAdapter() {
+        ConvertFunction<List, Object> foo = new ConvertFunction<List, Object>() {
+            @Override
+            public Object convert(List t, Type type) throws Exception {
+                if (type instanceof Class) {
+                    if (Number.class.isAssignableFrom((Class<?>) type))
+                        return converter.convert(t.size()).to(type);
+                }
+                return ConvertFunction.CANNOT_CONVERT;
+            }
+        };
+
+        Rule<List, Object> r = new Rule<>(List.class, Object.class, foo);
+        Rule<Object, Object> allCatch = new Rule<>(Object.class, Object.class,
+                (v,t) -> v.toString());
+
+        Adapter ca = converter.getAdapter();
+        ca.rule(r);
+        ca.rule(allCatch);
+
+        assertEquals(3L, (long) ca.convert(Arrays.asList("a", "b", "c")).to(Long.class));
+        assertEquals(3, (long) ca.convert(Arrays.asList("a", "b", "c")).to(Integer.class));
+        assertEquals("[a, b, c]", ca.convert(Arrays.asList("a", "b", "c")).to(String.class));
+    }
+
+    @Test @SuppressWarnings("rawtypes")
+    public void testWildcardAdapter2() {
+        Map<Object, Object> snooped = new HashMap<>();
+        Rule<Object, ArrayList> r = new Rule<>(Object.class, ArrayList.class,
+                (v,t) -> null,
+                (v,t) -> "arraylist");
+        Rule<Object, List> r2 = new Rule<>(Object.class, List.class,
+                (v,t) -> null,
+                (v,t) -> "list");
+        Rule<Object, Object> allCatch = new Rule<>(Object.class, Object.class,
+                (v,t) -> {snooped.put(v,t); return ConvertFunction.CANNOT_CONVERT;}, null);
+
+        Adapter ca = converter.getAdapter();
+        ca.rule(r);
+        ca.rule(r2);
+        ca.rule(allCatch);
+
+        assertEquals("Precondition", 0, snooped.size());
+        assertEquals("arraylist", ca.convert(
+                new ArrayList<String>(Arrays.asList("a", "b", "c"))).to(String.class));
+        assertEquals("Precondition", 0, snooped.size());
+        assertEquals("list",ca.convert(
+                new LinkedList<String>(Arrays.asList("a", "b", "c"))).to(String.class));
+        assertEquals("Precondition", 0, snooped.size());
+        assertEquals("a", ca.convert(
+                new HashSet<String>(Arrays.asList("a", "b", "c"))).to(String.class));
+        assertEquals(String.class, snooped.get(new HashSet<String>(Arrays.asList("a", "b", "c"))));
+    }
 }