Home | History | Annotate | Download | only in reflection
      1 /*
      2  * Copyright (c) 2007 Mockito contributors
      3  * This program is made available under the terms of the MIT License.
      4  */
      5 package org.mockito.internal.util.reflection;
      6 
      7 
      8 import org.mockito.Incubating;
      9 import org.mockito.exceptions.base.MockitoException;
     10 import org.mockito.internal.util.Checks;
     11 
     12 import java.lang.reflect.*;
     13 import java.util.*;
     14 
     15 
     16 /**
     17  * This class can retrieve generic meta-data that the compiler stores on classes
     18  * and accessible members.
     19  *
     20  * <p>
     21  *     The main idea of this code is to create a Map that will help to resolve return types.
     22  *     In order to actually work with nested generics, this map will have to be passed along new instances
     23  *     as a type context.
     24  * </p>
     25  *
     26  * <p>
     27  *     Hence :
     28  *     <ul>
     29  *         <li>A new instance representing the metadata is created using the {@link #inferFrom(Type)} method from a real
     30  *         <code>Class</code> or from a <code>ParameterizedType</code>, other types are not yet supported.</li>
     31  *
     32  *         <li>Then from this metadata, we can extract meta-data for a generic return type of a method, using
     33  *         {@link #resolveGenericReturnType(Method)}.</li>
     34  *     </ul>
     35  * </p>
     36  *
     37  * <p>
     38  * For now this code support the following kind of generic declarations :
     39  * <pre class="code"><code class="java">
     40  * interface GenericsNest&lt;K extends Comparable&lt;K&gt; & Cloneable&gt; extends Map&lt;K, Set&lt;Number&gt;&gt; {
     41  *     Set&lt;Number&gt; remove(Object key); // override with fixed ParameterizedType
     42  *     List&lt;? super Integer&gt; returning_wildcard_with_class_lower_bound();
     43  *     List&lt;? super K&gt; returning_wildcard_with_typeVar_lower_bound();
     44  *     List&lt;? extends K&gt; returning_wildcard_with_typeVar_upper_bound();
     45  *     K returningK();
     46  *     &lt;O extends K&gt; List&lt;O&gt; paramType_with_type_params();
     47  *     &lt;S, T extends S&gt; T two_type_params();
     48  *     &lt;O extends K&gt; O typeVar_with_type_params();
     49  *     Number returningNonGeneric();
     50  * }
     51  * </code></pre>
     52  *
     53  * @see #inferFrom(Type)
     54  * @see #resolveGenericReturnType(Method)
     55  * @see org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs
     56  */
     57 @Incubating
     58 public abstract class GenericMetadataSupport {
     59 
     60     // public static MockitoLogger logger = new ConsoleMockitoLogger();
     61 
     62     /**
     63      * Represents actual type variables resolved for current class.
     64      */
     65     protected Map<TypeVariable, Type> contextualActualTypeParameters = new HashMap<TypeVariable, Type>();
     66 
     67 
     68     protected void registerTypeVariablesOn(Type classType) {
     69         if (!(classType instanceof ParameterizedType)) {
     70             return;
     71         }
     72         ParameterizedType parameterizedType = (ParameterizedType) classType;
     73         TypeVariable[] typeParameters = ((Class<?>) parameterizedType.getRawType()).getTypeParameters();
     74         Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
     75         for (int i = 0; i < actualTypeArguments.length; i++) {
     76             TypeVariable typeParameter = typeParameters[i];
     77             Type actualTypeArgument = actualTypeArguments[i];
     78 
     79             if (actualTypeArgument instanceof WildcardType) {
     80                 contextualActualTypeParameters.put(typeParameter, boundsOf((WildcardType) actualTypeArgument));
     81             } else {
     82                 contextualActualTypeParameters.put(typeParameter, actualTypeArgument);
     83             }
     84             // logger.log("For '" + parameterizedType + "' found type variable : { '" + typeParameter + "(" + System.identityHashCode(typeParameter) + ")" + "' : '" + actualTypeArgument + "(" + System.identityHashCode(typeParameter) + ")" + "' }");
     85         }
     86     }
     87 
     88     protected void registerTypeParametersOn(TypeVariable[] typeParameters) {
     89         for (TypeVariable typeParameter : typeParameters) {
     90             contextualActualTypeParameters.put(typeParameter, boundsOf(typeParameter));
     91             // logger.log("For '" + typeParameter.getGenericDeclaration() + "' found type variable : { '" + typeParameter + "(" + System.identityHashCode(typeParameter) + ")" + "' : '" + boundsOf(typeParameter) + "' }");
     92         }
     93     }
     94 
     95     /**
     96      * @param typeParameter The TypeVariable parameter
     97      * @return A {@link BoundedType} for easy bound information, if first bound is a TypeVariable
     98      *         then retrieve BoundedType of this TypeVariable
     99      */
    100     private BoundedType boundsOf(TypeVariable typeParameter) {
    101         if (typeParameter.getBounds()[0] instanceof TypeVariable) {
    102             return boundsOf((TypeVariable) typeParameter.getBounds()[0]);
    103         }
    104         return new TypeVarBoundedType(typeParameter);
    105     }
    106 
    107     /**
    108      * @param wildCard The WildCard type
    109      * @return A {@link BoundedType} for easy bound information, if first bound is a TypeVariable
    110      *         then retrieve BoundedType of this TypeVariable
    111      */
    112     private BoundedType boundsOf(WildcardType wildCard) {
    113         /*
    114          *  According to JLS(http://docs.oracle.com/javase/specs/jls/se5.0/html/typesValues.html#4.5.1):
    115          *  - Lower and upper can't coexist: (for instance, this is not allowed: <? extends List<String> & super MyInterface>)
    116          *  - Multiple bounds are not supported (for instance, this is not allowed: <? extends List<String> & MyInterface>)
    117          */
    118 
    119         WildCardBoundedType wildCardBoundedType = new WildCardBoundedType(wildCard);
    120         if (wildCardBoundedType.firstBound() instanceof TypeVariable) {
    121             return boundsOf((TypeVariable) wildCardBoundedType.firstBound());
    122         }
    123 
    124         return wildCardBoundedType;
    125     }
    126 
    127 
    128 
    129     /**
    130      * @return Raw type of the current instance.
    131      */
    132     public abstract Class<?> rawType();
    133 
    134 
    135 
    136     /**
    137      * @return Returns extra interfaces <strong>if relevant</strong>, otherwise empty List.
    138      */
    139     public List<Type> extraInterfaces() {
    140         return Collections.emptyList();
    141     }
    142 
    143     /**
    144      * @return Returns an array with the raw types of {@link #extraInterfaces()} <strong>if relevant</strong>.
    145      */
    146     public Class<?>[] rawExtraInterfaces() {
    147         return new Class[0];
    148     }
    149 
    150 
    151 
    152     /**
    153      * @return Actual type arguments matching the type variables of the raw type represented by this {@link GenericMetadataSupport} instance.
    154      */
    155     public Map<TypeVariable, Type> actualTypeArguments() {
    156         TypeVariable[] typeParameters = rawType().getTypeParameters();
    157         LinkedHashMap<TypeVariable, Type> actualTypeArguments = new LinkedHashMap<TypeVariable, Type>();
    158 
    159         for (TypeVariable typeParameter : typeParameters) {
    160 
    161             Type actualType = getActualTypeArgumentFor(typeParameter);
    162 
    163             actualTypeArguments.put(typeParameter, actualType);
    164             // logger.log("For '" + rawType().getCanonicalName() + "' returning explicit TypeVariable : { '" + typeParameter + "(" + System.identityHashCode(typeParameter) + ")" + "' : '" + actualType +"' }");
    165         }
    166 
    167         return actualTypeArguments;
    168     }
    169 
    170     protected Type getActualTypeArgumentFor(TypeVariable typeParameter) {
    171         Type type = this.contextualActualTypeParameters.get(typeParameter);
    172         if (type instanceof TypeVariable) {
    173             TypeVariable typeVariable = (TypeVariable) type;
    174             return getActualTypeArgumentFor(typeVariable);
    175         }
    176 
    177         return type;
    178     }
    179 
    180 
    181 
    182     /**
    183      * Resolve current method generic return type to a {@link GenericMetadataSupport}.
    184      *
    185      * @param method Method to resolve the return type.
    186      * @return {@link GenericMetadataSupport} representing this generic return type.
    187      */
    188     public GenericMetadataSupport resolveGenericReturnType(Method method) {
    189         Type genericReturnType = method.getGenericReturnType();
    190         // logger.log("Method '" + method.toGenericString() + "' has return type : " + genericReturnType.getClass().getInterfaces()[0].getSimpleName() + " : " + genericReturnType);
    191 
    192         if (genericReturnType instanceof Class) {
    193             return new NotGenericReturnTypeSupport(genericReturnType);
    194         }
    195         if (genericReturnType instanceof ParameterizedType) {
    196             return new ParameterizedReturnType(this, method.getTypeParameters(), (ParameterizedType) method.getGenericReturnType());
    197         }
    198         if (genericReturnType instanceof TypeVariable) {
    199             return new TypeVariableReturnType(this, method.getTypeParameters(), (TypeVariable) genericReturnType);
    200         }
    201 
    202         throw new MockitoException("Ouch, it shouldn't happen, type '" + genericReturnType.getClass().getCanonicalName() + "' on method : '" + method.toGenericString() + "' is not supported : " + genericReturnType);
    203     }
    204 
    205     /**
    206      * Create an new instance of {@link GenericMetadataSupport} inferred from a {@link Type}.
    207      *
    208      * <p>
    209      *     At the moment <code>type</code> can only be a {@link Class} or a {@link ParameterizedType}, otherwise
    210      *     it'll throw a {@link MockitoException}.
    211      * </p>
    212      *
    213      * @param type The class from which the {@link GenericMetadataSupport} should be built.
    214      * @return The new {@link GenericMetadataSupport}.
    215      * @throws MockitoException Raised if type is not a {@link Class} or a {@link ParameterizedType}.
    216      */
    217     public static GenericMetadataSupport inferFrom(Type type) {
    218         Checks.checkNotNull(type, "type");
    219         if (type instanceof Class) {
    220             return new FromClassGenericMetadataSupport((Class<?>) type);
    221         }
    222         if (type instanceof ParameterizedType) {
    223             return new FromParameterizedTypeGenericMetadataSupport((ParameterizedType) type);
    224         }
    225 
    226         throw new MockitoException("Type meta-data for this Type (" + type.getClass().getCanonicalName() + ") is not supported : " + type);
    227     }
    228 
    229 
    230     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    231     //// Below are specializations of GenericMetadataSupport that could handle retrieval of possible Types
    232     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    233 
    234     /**
    235      * Generic metadata implementation for {@link Class}.
    236      *
    237      * Offer support to retrieve generic metadata on a {@link Class} by reading type parameters and type variables on
    238      * the class and its ancestors and interfaces.
    239      */
    240     private static class FromClassGenericMetadataSupport extends GenericMetadataSupport {
    241         private Class<?> clazz;
    242 
    243         public FromClassGenericMetadataSupport(Class<?> clazz) {
    244             this.clazz = clazz;
    245             readActualTypeParametersOnDeclaringClass();
    246         }
    247 
    248         private void readActualTypeParametersOnDeclaringClass() {
    249             registerTypeParametersOn(clazz.getTypeParameters());
    250             registerTypeVariablesOn(clazz.getGenericSuperclass());
    251             for (Type genericInterface : clazz.getGenericInterfaces()) {
    252                 registerTypeVariablesOn(genericInterface);
    253             }
    254         }
    255 
    256         @Override
    257         public Class<?> rawType() {
    258             return clazz;
    259         }
    260     }
    261 
    262 
    263     /**
    264      * Generic metadata implementation for "standalone" {@link ParameterizedType}.
    265      *
    266      * Offer support to retrieve generic metadata on a {@link ParameterizedType} by reading type variables of
    267      * the related raw type and declared type variable of this parameterized type.
    268      *
    269      * This class is not designed to work on ParameterizedType returned by {@link Method#getGenericReturnType()}, as
    270      * the ParameterizedType instance return in these cases could have Type Variables that refer to type declaration(s).
    271      * That's what meant the "standalone" word at the beginning of the Javadoc.
    272      * Instead use {@link ParameterizedReturnType}.
    273      */
    274     private static class FromParameterizedTypeGenericMetadataSupport extends GenericMetadataSupport {
    275         private ParameterizedType parameterizedType;
    276 
    277         public FromParameterizedTypeGenericMetadataSupport(ParameterizedType parameterizedType) {
    278             this.parameterizedType = parameterizedType;
    279             readActualTypeParameters();
    280         }
    281 
    282         private void readActualTypeParameters() {
    283             registerTypeVariablesOn(parameterizedType.getRawType());
    284             registerTypeVariablesOn(parameterizedType);
    285         }
    286 
    287         @Override
    288         public Class<?> rawType() {
    289             return (Class<?>) parameterizedType.getRawType();
    290         }
    291     }
    292 
    293 
    294     /**
    295      * Generic metadata specific to {@link ParameterizedType} returned via {@link Method#getGenericReturnType()}.
    296      */
    297     private static class ParameterizedReturnType extends GenericMetadataSupport {
    298         private final ParameterizedType parameterizedType;
    299         private final TypeVariable[] typeParameters;
    300 
    301         public ParameterizedReturnType(GenericMetadataSupport source, TypeVariable[] typeParameters, ParameterizedType parameterizedType) {
    302             this.parameterizedType = parameterizedType;
    303             this.typeParameters = typeParameters;
    304             this.contextualActualTypeParameters = source.contextualActualTypeParameters;
    305 
    306             readTypeParameters();
    307             readTypeVariables();
    308         }
    309 
    310         private void readTypeParameters() {
    311             registerTypeParametersOn(typeParameters);
    312         }
    313 
    314         private void readTypeVariables() {
    315             registerTypeVariablesOn(parameterizedType);
    316         }
    317 
    318         @Override
    319         public Class<?> rawType() {
    320             return (Class<?>) parameterizedType.getRawType();
    321         }
    322 
    323     }
    324 
    325 
    326     /**
    327      * Generic metadata for {@link TypeVariable} returned via {@link Method#getGenericReturnType()}.
    328      */
    329     private static class TypeVariableReturnType extends GenericMetadataSupport {
    330         private final TypeVariable typeVariable;
    331         private final TypeVariable[] typeParameters;
    332         private Class<?> rawType;
    333 
    334 
    335 
    336         public TypeVariableReturnType(GenericMetadataSupport source, TypeVariable[] typeParameters, TypeVariable typeVariable) {
    337             this.typeParameters = typeParameters;
    338             this.typeVariable = typeVariable;
    339             this.contextualActualTypeParameters = source.contextualActualTypeParameters;
    340 
    341             readTypeParameters();
    342             readTypeVariables();
    343         }
    344 
    345         private void readTypeParameters() {
    346             registerTypeParametersOn(typeParameters);
    347         }
    348 
    349         private void readTypeVariables() {
    350             for (Type type : typeVariable.getBounds()) {
    351                 registerTypeVariablesOn(type);
    352             }
    353             registerTypeVariablesOn(getActualTypeArgumentFor(typeVariable));
    354         }
    355 
    356         @Override
    357         public Class<?> rawType() {
    358             if (rawType == null) {
    359                 rawType = extractRawTypeOf(typeVariable);
    360             }
    361             return rawType;
    362         }
    363 
    364         private Class<?> extractRawTypeOf(Type type) {
    365             if (type instanceof Class) {
    366                 return (Class<?>) type;
    367             }
    368             if (type instanceof ParameterizedType) {
    369                 return (Class<?>) ((ParameterizedType) type).getRawType();
    370             }
    371             if (type instanceof BoundedType) {
    372                 return extractRawTypeOf(((BoundedType) type).firstBound());
    373             }
    374             if (type instanceof TypeVariable) {
    375                 /*
    376                  * If type is a TypeVariable, then it is needed to gather data elsewhere. Usually TypeVariables are declared
    377                  * on the class definition, such as such as List<E>.
    378                  */
    379                 return extractRawTypeOf(contextualActualTypeParameters.get(type));
    380             }
    381             throw new MockitoException("Raw extraction not supported for : '" + type + "'");
    382         }
    383 
    384         @Override
    385         public List<Type> extraInterfaces() {
    386             Type type = extractActualBoundedTypeOf(typeVariable);
    387             if (type instanceof BoundedType) {
    388                 return Arrays.asList(((BoundedType) type).interfaceBounds());
    389             }
    390             if (type instanceof ParameterizedType) {
    391                 return Collections.singletonList(type);
    392             }
    393             if (type instanceof Class) {
    394                 return Collections.emptyList();
    395             }
    396             throw new MockitoException("Cannot extract extra-interfaces from '" + typeVariable + "' : '" + type + "'");
    397         }
    398 
    399         /**
    400          * @return Returns an array with the extracted raw types of {@link #extraInterfaces()}.
    401          * @see #extractRawTypeOf(java.lang.reflect.Type)
    402          */
    403         public Class<?>[] rawExtraInterfaces() {
    404             List<Type> extraInterfaces = extraInterfaces();
    405             List<Class<?>> rawExtraInterfaces = new ArrayList<Class<?>>();
    406             for (Type extraInterface : extraInterfaces) {
    407                 Class<?> rawInterface = extractRawTypeOf(extraInterface);
    408                 // avoid interface collision with actual raw type (with typevariables, resolution ca be quite aggressive)
    409                 if(!rawType().equals(rawInterface)) {
    410                     rawExtraInterfaces.add(rawInterface);
    411                 }
    412             }
    413             return rawExtraInterfaces.toArray(new Class[rawExtraInterfaces.size()]);
    414         }
    415 
    416         private Type extractActualBoundedTypeOf(Type type) {
    417             if (type instanceof TypeVariable) {
    418                 /*
    419                 If type is a TypeVariable, then it is needed to gather data elsewhere. Usually TypeVariables are declared
    420                 on the class definition, such as such as List<E>.
    421                 */
    422                 return extractActualBoundedTypeOf(contextualActualTypeParameters.get(type));
    423             }
    424             if (type instanceof BoundedType) {
    425                 Type actualFirstBound = extractActualBoundedTypeOf(((BoundedType) type).firstBound());
    426                 if (!(actualFirstBound instanceof BoundedType)) {
    427                     return type; // avoid going one step further, ie avoid : O(TypeVar) -> K(TypeVar) -> Some ParamType
    428                 }
    429                 return actualFirstBound;
    430             }
    431             return type; // irrelevant, we don't manage other types as they are not bounded.
    432         }
    433     }
    434 
    435 
    436 
    437     /**
    438      * Non-Generic metadata for {@link Class} returned via {@link Method#getGenericReturnType()}.
    439      */
    440     private static class NotGenericReturnTypeSupport extends GenericMetadataSupport {
    441         private final Class<?> returnType;
    442 
    443         public NotGenericReturnTypeSupport(Type genericReturnType) {
    444             returnType = (Class<?>) genericReturnType;
    445         }
    446 
    447         @Override
    448         public Class<?> rawType() {
    449             return returnType;
    450         }
    451     }
    452 
    453 
    454 
    455     /**
    456      * Type representing bounds of a type
    457      *
    458      * @see TypeVarBoundedType
    459      * @see <a href="http://docs.oracle.com/javase/specs/jls/se5.0/html/typesValues.html#4.4">http://docs.oracle.com/javase/specs/jls/se5.0/html/typesValues.html#4.4</a>
    460      * @see WildCardBoundedType
    461      * @see <a href="http://docs.oracle.com/javase/specs/jls/se5.0/html/typesValues.html#4.5.1">http://docs.oracle.com/javase/specs/jls/se5.0/html/typesValues.html#4.5.1</a>
    462      */
    463     public static interface BoundedType extends Type {
    464         Type firstBound();
    465 
    466         Type[] interfaceBounds();
    467     }
    468 
    469     /**
    470      * Type representing bounds of a type variable, allows to keep all bounds information.
    471      *
    472      * <p>It uses the first bound in the array, as this array is never null and always contains at least
    473      * one element (Object is always here if no bounds are declared).</p>
    474      *
    475      * <p>If upper bounds are declared with SomeClass and additional interfaces, then firstBound will be SomeClass and
    476      * interfacesBound will be an array of the additional interfaces.
    477      *
    478      * i.e. <code>SomeClass</code>.
    479      * <pre class="code"><code class="java">
    480      *     interface UpperBoundedTypeWithClass<E extends Comparable<E> & Cloneable> {
    481      *         E get();
    482      *     }
    483      *     // will return Comparable type
    484      * </code></pre>
    485      * </p>
    486      *
    487      * @see <a href="http://docs.oracle.com/javase/specs/jls/se5.0/html/typesValues.html#4.4">http://docs.oracle.com/javase/specs/jls/se5.0/html/typesValues.html#4.4</a>
    488      */
    489     public static class TypeVarBoundedType implements BoundedType {
    490         private TypeVariable typeVariable;
    491 
    492 
    493         public TypeVarBoundedType(TypeVariable typeVariable) {
    494             this.typeVariable = typeVariable;
    495         }
    496 
    497         /**
    498          * @return either a class or an interface (parameterized or not), if no bounds declared Object is returned.
    499          */
    500         public Type firstBound() {
    501             return typeVariable.getBounds()[0]; //
    502         }
    503 
    504         /**
    505          * On a Type Variable (typeVar extends C_0 & I_1 & I_2 & etc), will return an array
    506          * containing I_1 and I_2.
    507          *
    508          * @return other bounds for this type, these bounds can only be only interfaces as the JLS says,
    509          * empty array if no other bound declared.
    510          */
    511         public Type[] interfaceBounds() {
    512             Type[] interfaceBounds = new Type[typeVariable.getBounds().length - 1];
    513             System.arraycopy(typeVariable.getBounds(), 1, interfaceBounds, 0, typeVariable.getBounds().length - 1);
    514             return interfaceBounds;
    515         }
    516 
    517         @Override
    518         public boolean equals(Object o) {
    519             if (this == o) return true;
    520             if (o == null || getClass() != o.getClass()) return false;
    521 
    522             return typeVariable.equals(((TypeVarBoundedType) o).typeVariable);
    523 
    524         }
    525 
    526         @Override
    527         public int hashCode() {
    528             return typeVariable.hashCode();
    529         }
    530 
    531         @Override
    532         public String toString() {
    533             final StringBuilder sb = new StringBuilder();
    534             sb.append("{firstBound=").append(firstBound());
    535             sb.append(", interfaceBounds=").append(Arrays.deepToString(interfaceBounds()));
    536             sb.append('}');
    537             return sb.toString();
    538         }
    539 
    540         public TypeVariable typeVariable() {
    541             return typeVariable;
    542         }
    543     }
    544 
    545     /**
    546      * Type representing bounds of a wildcard, allows to keep all bounds information.
    547      *
    548      * <p>The JLS says that lower bound and upper bound are mutually exclusive, and that multiple bounds
    549      * are not allowed.
    550      *
    551      * @see <a href="http://docs.oracle.com/javase/specs/jls/se5.0/html/typesValues.html#4.4">http://docs.oracle.com/javase/specs/jls/se5.0/html/typesValues.html#4.4</a>
    552      */
    553     public static class WildCardBoundedType implements BoundedType {
    554         private WildcardType wildcard;
    555 
    556 
    557         public WildCardBoundedType(WildcardType wildcard) {
    558             this.wildcard = wildcard;
    559         }
    560 
    561         /**
    562          * @return The first bound, either a type or a reference to a TypeVariable
    563          */
    564         public Type firstBound() {
    565             Type[] lowerBounds = wildcard.getLowerBounds();
    566             Type[] upperBounds = wildcard.getUpperBounds();
    567 
    568             return lowerBounds.length != 0 ? lowerBounds[0] : upperBounds[0];
    569         }
    570 
    571         /**
    572          * @return An empty array as, wildcard don't support multiple bounds.
    573          */
    574         public Type[] interfaceBounds() {
    575             return new Type[0];
    576         }
    577 
    578         @Override
    579         public boolean equals(Object o) {
    580             if (this == o) return true;
    581             if (o == null || getClass() != o.getClass()) return false;
    582 
    583             return wildcard.equals(((TypeVarBoundedType) o).typeVariable);
    584 
    585         }
    586 
    587         @Override
    588         public int hashCode() {
    589             return wildcard.hashCode();
    590         }
    591 
    592         @Override
    593         public String toString() {
    594             final StringBuilder sb = new StringBuilder();
    595             sb.append("{firstBound=").append(firstBound());
    596             sb.append(", interfaceBounds=[]}");
    597             return sb.toString();
    598         }
    599 
    600         public WildcardType wildCard() {
    601             return wildcard;
    602         }
    603     }
    604 
    605 }
    606 
    607 
    608