// 
// Decompiled by Procyon v0.6.0
// 

package com.google.gson.internal;

import java.io.Serializable;
import java.lang.reflect.Modifier;
import java.lang.reflect.GenericDeclaration;
import java.util.NoSuchElementException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Collection;
import java.util.Arrays;
import java.util.Objects;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.Array;
import java.lang.reflect.WildcardType;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

public final class GsonTypes
{
    static final Type[] EMPTY_TYPE_ARRAY;
    
    private GsonTypes() {
        throw new UnsupportedOperationException();
    }
    
    public static ParameterizedType newParameterizedTypeWithOwner(final Type ownerType, final Class<?> rawType, final Type... typeArguments) {
        return new ParameterizedTypeImpl(ownerType, rawType, typeArguments);
    }
    
    public static GenericArrayType arrayOf(final Type componentType) {
        return new GenericArrayTypeImpl(componentType);
    }
    
    public static WildcardType subtypeOf(final Type bound) {
        Type[] upperBounds;
        if (bound instanceof WildcardType) {
            upperBounds = ((WildcardType)bound).getUpperBounds();
        }
        else {
            upperBounds = new Type[] { bound };
        }
        return new WildcardTypeImpl(upperBounds, GsonTypes.EMPTY_TYPE_ARRAY);
    }
    
    public static WildcardType supertypeOf(final Type bound) {
        Type[] lowerBounds;
        if (bound instanceof WildcardType) {
            lowerBounds = ((WildcardType)bound).getLowerBounds();
        }
        else {
            lowerBounds = new Type[] { bound };
        }
        return new WildcardTypeImpl(new Type[] { Object.class }, lowerBounds);
    }
    
    public static Type canonicalize(final Type type) {
        if (type instanceof Class) {
            final Class<?> c = (Class<?>)type;
            return (Type)(c.isArray() ? new GenericArrayTypeImpl(canonicalize(c.getComponentType())) : c);
        }
        if (type instanceof ParameterizedType) {
            final ParameterizedType p = (ParameterizedType)type;
            return new ParameterizedTypeImpl(p.getOwnerType(), (Class<?>)p.getRawType(), p.getActualTypeArguments());
        }
        if (type instanceof GenericArrayType) {
            final GenericArrayType g = (GenericArrayType)type;
            return new GenericArrayTypeImpl(g.getGenericComponentType());
        }
        if (type instanceof WildcardType) {
            final WildcardType w = (WildcardType)type;
            return new WildcardTypeImpl(w.getUpperBounds(), w.getLowerBounds());
        }
        return type;
    }
    
    public static Class<?> getRawType(final Type type) {
        if (type instanceof Class) {
            return (Class)type;
        }
        if (type instanceof ParameterizedType) {
            final ParameterizedType parameterizedType = (ParameterizedType)type;
            final Type rawType = parameterizedType.getRawType();
            return (Class)rawType;
        }
        if (type instanceof GenericArrayType) {
            final Type componentType = ((GenericArrayType)type).getGenericComponentType();
            return Array.newInstance(getRawType(componentType), 0).getClass();
        }
        if (type instanceof TypeVariable) {
            return Object.class;
        }
        if (!(type instanceof WildcardType)) {
            final String className = (type == null) ? "null" : type.getClass().getName();
            throw new IllegalArgumentException("Expected a Class, ParameterizedType, or GenericArrayType, but <" + type + "> is of type " + className);
        }
        final Type[] bounds = ((WildcardType)type).getUpperBounds();
        assert bounds.length == 1;
        return getRawType(bounds[0]);
    }
    
    private static boolean equal(final Object a, final Object b) {
        return Objects.equals(a, b);
    }
    
    public static boolean equals(final Type a, final Type b) {
        if (a == b) {
            return true;
        }
        if (a instanceof Class) {
            return a.equals(b);
        }
        if (a instanceof ParameterizedType) {
            if (!(b instanceof ParameterizedType)) {
                return false;
            }
            final ParameterizedType pa = (ParameterizedType)a;
            final ParameterizedType pb = (ParameterizedType)b;
            return equal(pa.getOwnerType(), pb.getOwnerType()) && pa.getRawType().equals(pb.getRawType()) && Arrays.equals(pa.getActualTypeArguments(), pb.getActualTypeArguments());
        }
        else if (a instanceof GenericArrayType) {
            if (!(b instanceof GenericArrayType)) {
                return false;
            }
            final GenericArrayType ga = (GenericArrayType)a;
            final GenericArrayType gb = (GenericArrayType)b;
            return equals(ga.getGenericComponentType(), gb.getGenericComponentType());
        }
        else if (a instanceof WildcardType) {
            if (!(b instanceof WildcardType)) {
                return false;
            }
            final WildcardType wa = (WildcardType)a;
            final WildcardType wb = (WildcardType)b;
            return Arrays.equals(wa.getUpperBounds(), wb.getUpperBounds()) && Arrays.equals(wa.getLowerBounds(), wb.getLowerBounds());
        }
        else {
            if (!(a instanceof TypeVariable)) {
                return false;
            }
            if (!(b instanceof TypeVariable)) {
                return false;
            }
            final TypeVariable<?> va = (TypeVariable<?>)a;
            final TypeVariable<?> vb = (TypeVariable<?>)b;
            return Objects.equals(va.getGenericDeclaration(), vb.getGenericDeclaration()) && va.getName().equals(vb.getName());
        }
    }
    
    public static String typeToString(final Type type) {
        return (type instanceof Class) ? ((Class)type).getName() : type.toString();
    }
    
    private static Type getGenericSupertype(final Type context, Class<?> rawType, final Class<?> supertype) {
        if (supertype == rawType) {
            return context;
        }
        if (supertype.isInterface()) {
            final Class<?>[] interfaces = rawType.getInterfaces();
            for (int i = 0, length = interfaces.length; i < length; ++i) {
                if (interfaces[i] == supertype) {
                    return rawType.getGenericInterfaces()[i];
                }
                if (supertype.isAssignableFrom(interfaces[i])) {
                    return getGenericSupertype(rawType.getGenericInterfaces()[i], interfaces[i], supertype);
                }
            }
        }
        if (!rawType.isInterface()) {
            while (rawType != Object.class) {
                final Class<?> rawSupertype = rawType.getSuperclass();
                if (rawSupertype == supertype) {
                    return rawType.getGenericSuperclass();
                }
                if (supertype.isAssignableFrom(rawSupertype)) {
                    return getGenericSupertype(rawType.getGenericSuperclass(), rawSupertype, supertype);
                }
                rawType = rawSupertype;
            }
        }
        return supertype;
    }
    
    private static Type getSupertype(Type context, final Class<?> contextRawType, final Class<?> supertype) {
        if (context instanceof WildcardType) {
            final Type[] bounds = ((WildcardType)context).getUpperBounds();
            assert bounds.length == 1;
            context = bounds[0];
        }
        if (!supertype.isAssignableFrom(contextRawType)) {
            throw new IllegalArgumentException(contextRawType + " is not the same as or a subtype of " + supertype);
        }
        return resolve(context, contextRawType, getGenericSupertype(context, contextRawType, supertype));
    }
    
    public static Type getArrayComponentType(final Type array) {
        return (array instanceof GenericArrayType) ? ((GenericArrayType)array).getGenericComponentType() : ((Class)array).getComponentType();
    }
    
    public static Type getCollectionElementType(final Type context, final Class<?> contextRawType) {
        final Type collectionType = getSupertype(context, contextRawType, Collection.class);
        if (collectionType instanceof ParameterizedType) {
            return ((ParameterizedType)collectionType).getActualTypeArguments()[0];
        }
        return Object.class;
    }
    
    public static Type[] getMapKeyAndValueTypes(final Type context, final Class<?> contextRawType) {
        if (Properties.class.isAssignableFrom(contextRawType)) {
            return new Type[] { String.class, String.class };
        }
        final Type mapType = getSupertype(context, contextRawType, Map.class);
        if (mapType instanceof ParameterizedType) {
            final ParameterizedType mapParameterizedType = (ParameterizedType)mapType;
            return mapParameterizedType.getActualTypeArguments();
        }
        return new Type[] { Object.class, Object.class };
    }
    
    public static Type resolve(final Type context, final Class<?> contextRawType, final Type toResolve) {
        return resolve(context, contextRawType, toResolve, new HashMap<TypeVariable<?>, Type>());
    }
    
    private static Type resolve(final Type context, final Class<?> contextRawType, Type toResolve, final Map<TypeVariable<?>, Type> visitedTypeVariables) {
        TypeVariable<?> resolving = null;
        while (true) {
            while (toResolve instanceof TypeVariable) {
                final TypeVariable<?> typeVariable = (TypeVariable<?>)toResolve;
                final Type previouslyResolved = visitedTypeVariables.get(typeVariable);
                if (previouslyResolved != null) {
                    return (previouslyResolved == Void.TYPE) ? toResolve : previouslyResolved;
                }
                visitedTypeVariables.put(typeVariable, Void.TYPE);
                if (resolving == null) {
                    resolving = typeVariable;
                }
                toResolve = resolveTypeVariable(context, contextRawType, typeVariable);
                if (toResolve == typeVariable) {
                    if (resolving != null) {
                        visitedTypeVariables.put(resolving, toResolve);
                    }
                    return toResolve;
                }
            }
            if (toResolve instanceof Class && ((Class)toResolve).isArray()) {
                final Class<?> original = (Class<?>)toResolve;
                final Type componentType = original.getComponentType();
                final Type newComponentType = resolve(context, contextRawType, componentType, visitedTypeVariables);
                toResolve = (Type)(equal(componentType, newComponentType) ? original : arrayOf(newComponentType));
                continue;
            }
            if (toResolve instanceof GenericArrayType) {
                final GenericArrayType original2 = (GenericArrayType)toResolve;
                final Type componentType = original2.getGenericComponentType();
                final Type newComponentType = resolve(context, contextRawType, componentType, visitedTypeVariables);
                toResolve = (equal(componentType, newComponentType) ? original2 : arrayOf(newComponentType));
                continue;
            }
            if (toResolve instanceof ParameterizedType) {
                final ParameterizedType original3 = (ParameterizedType)toResolve;
                final Type ownerType = original3.getOwnerType();
                final Type newOwnerType = resolve(context, contextRawType, ownerType, visitedTypeVariables);
                final boolean ownerChanged = !equal(newOwnerType, ownerType);
                Type[] args = original3.getActualTypeArguments();
                boolean argsChanged = false;
                for (int t = 0, length = args.length; t < length; ++t) {
                    final Type resolvedTypeArgument = resolve(context, contextRawType, args[t], visitedTypeVariables);
                    if (!equal(resolvedTypeArgument, args[t])) {
                        if (!argsChanged) {
                            args = args.clone();
                            argsChanged = true;
                        }
                        args[t] = resolvedTypeArgument;
                    }
                }
                toResolve = ((ownerChanged || argsChanged) ? newParameterizedTypeWithOwner(newOwnerType, (Class<?>)original3.getRawType(), args) : original3);
                continue;
            }
            if (toResolve instanceof WildcardType) {
                final WildcardType original4 = (WildcardType)toResolve;
                final Type[] originalLowerBound = original4.getLowerBounds();
                final Type[] originalUpperBound = original4.getUpperBounds();
                if (originalLowerBound.length == 1) {
                    final Type lowerBound = resolve(context, contextRawType, originalLowerBound[0], visitedTypeVariables);
                    if (lowerBound != originalLowerBound[0]) {
                        toResolve = supertypeOf(lowerBound);
                        continue;
                    }
                }
                else if (originalUpperBound.length == 1) {
                    final Type upperBound = resolve(context, contextRawType, originalUpperBound[0], visitedTypeVariables);
                    if (upperBound != originalUpperBound[0]) {
                        toResolve = subtypeOf(upperBound);
                        continue;
                    }
                }
                toResolve = original4;
            }
            continue;
        }
    }
    
    private static Type resolveTypeVariable(final Type context, final Class<?> contextRawType, final TypeVariable<?> unknown) {
        final Class<?> declaredByRaw = declaringClassOf(unknown);
        if (declaredByRaw == null) {
            return unknown;
        }
        final Type declaredBy = getGenericSupertype(context, contextRawType, declaredByRaw);
        if (declaredBy instanceof ParameterizedType) {
            final int index = indexOf(declaredByRaw.getTypeParameters(), unknown);
            return ((ParameterizedType)declaredBy).getActualTypeArguments()[index];
        }
        return unknown;
    }
    
    private static int indexOf(final Object[] array, final Object toFind) {
        for (int i = 0, length = array.length; i < length; ++i) {
            if (toFind.equals(array[i])) {
                return i;
            }
        }
        throw new NoSuchElementException();
    }
    
    private static Class<?> declaringClassOf(final TypeVariable<?> typeVariable) {
        final GenericDeclaration genericDeclaration = (GenericDeclaration)typeVariable.getGenericDeclaration();
        return (genericDeclaration instanceof Class) ? ((Class)genericDeclaration) : null;
    }
    
    static void checkNotPrimitive(final Type type) {
        if (type instanceof Class && ((Class)type).isPrimitive()) {
            throw new IllegalArgumentException("Primitive type is not allowed");
        }
    }
    
    public static boolean requiresOwnerType(final Type rawType) {
        if (rawType instanceof Class) {
            final Class<?> rawTypeAsClass = (Class<?>)rawType;
            return !Modifier.isStatic(rawTypeAsClass.getModifiers()) && rawTypeAsClass.getDeclaringClass() != null;
        }
        return false;
    }
    
    static {
        EMPTY_TYPE_ARRAY = new Type[0];
    }
    
    private static final class ParameterizedTypeImpl implements ParameterizedType, Serializable
    {
        private final Type ownerType;
        private final Type rawType;
        private final Type[] typeArguments;
        private static final long serialVersionUID = 0L;
        
        ParameterizedTypeImpl(final Type ownerType, final Class<?> rawType, final Type... typeArguments) {
            Objects.requireNonNull(rawType);
            if (ownerType == null && GsonTypes.requiresOwnerType(rawType)) {
                throw new IllegalArgumentException("Must specify owner type for " + rawType);
            }
            this.ownerType = ((ownerType == null) ? null : GsonTypes.canonicalize(ownerType));
            this.rawType = GsonTypes.canonicalize(rawType);
            this.typeArguments = typeArguments.clone();
            for (int t = 0, length = this.typeArguments.length; t < length; ++t) {
                Objects.requireNonNull(this.typeArguments[t]);
                GsonTypes.checkNotPrimitive(this.typeArguments[t]);
                this.typeArguments[t] = GsonTypes.canonicalize(this.typeArguments[t]);
            }
        }
        
        @Override
        public Type[] getActualTypeArguments() {
            return this.typeArguments.clone();
        }
        
        @Override
        public Type getRawType() {
            return this.rawType;
        }
        
        @Override
        public Type getOwnerType() {
            return this.ownerType;
        }
        
        @Override
        public boolean equals(final Object other) {
            return other instanceof ParameterizedType && GsonTypes.equals(this, (Type)other);
        }
        
        private static int hashCodeOrZero(final Object o) {
            return (o != null) ? o.hashCode() : 0;
        }
        
        @Override
        public int hashCode() {
            return Arrays.hashCode(this.typeArguments) ^ this.rawType.hashCode() ^ hashCodeOrZero(this.ownerType);
        }
        
        @Override
        public String toString() {
            final int length = this.typeArguments.length;
            if (length == 0) {
                return GsonTypes.typeToString(this.rawType);
            }
            final StringBuilder stringBuilder = new StringBuilder(30 * (length + 1));
            stringBuilder.append(GsonTypes.typeToString(this.rawType)).append("<").append(GsonTypes.typeToString(this.typeArguments[0]));
            for (int i = 1; i < length; ++i) {
                stringBuilder.append(", ").append(GsonTypes.typeToString(this.typeArguments[i]));
            }
            return stringBuilder.append(">").toString();
        }
    }
    
    private static final class GenericArrayTypeImpl implements GenericArrayType, Serializable
    {
        private final Type componentType;
        private static final long serialVersionUID = 0L;
        
        GenericArrayTypeImpl(final Type componentType) {
            Objects.requireNonNull(componentType);
            this.componentType = GsonTypes.canonicalize(componentType);
        }
        
        @Override
        public Type getGenericComponentType() {
            return this.componentType;
        }
        
        @Override
        public boolean equals(final Object o) {
            return o instanceof GenericArrayType && GsonTypes.equals(this, (Type)o);
        }
        
        @Override
        public int hashCode() {
            return this.componentType.hashCode();
        }
        
        @Override
        public String toString() {
            return GsonTypes.typeToString(this.componentType) + "[]";
        }
    }
    
    private static final class WildcardTypeImpl implements WildcardType, Serializable
    {
        private final Type upperBound;
        private final Type lowerBound;
        private static final long serialVersionUID = 0L;
        
        WildcardTypeImpl(final Type[] upperBounds, final Type[] lowerBounds) {
            if (lowerBounds.length > 1) {
                throw new IllegalArgumentException("At most one lower bound is supported");
            }
            if (upperBounds.length != 1) {
                throw new IllegalArgumentException("Exactly one upper bound must be specified");
            }
            if (lowerBounds.length == 1) {
                Objects.requireNonNull(lowerBounds[0]);
                GsonTypes.checkNotPrimitive(lowerBounds[0]);
                if (upperBounds[0] != Object.class) {
                    throw new IllegalArgumentException("When lower bound is specified, upper bound must be Object");
                }
                this.lowerBound = GsonTypes.canonicalize(lowerBounds[0]);
                this.upperBound = Object.class;
            }
            else {
                Objects.requireNonNull(upperBounds[0]);
                GsonTypes.checkNotPrimitive(upperBounds[0]);
                this.lowerBound = null;
                this.upperBound = GsonTypes.canonicalize(upperBounds[0]);
            }
        }
        
        @Override
        public Type[] getUpperBounds() {
            return new Type[] { this.upperBound };
        }
        
        @Override
        public Type[] getLowerBounds() {
            return (this.lowerBound != null) ? new Type[] { this.lowerBound } : GsonTypes.EMPTY_TYPE_ARRAY;
        }
        
        @Override
        public boolean equals(final Object other) {
            return other instanceof WildcardType && GsonTypes.equals(this, (Type)other);
        }
        
        @Override
        public int hashCode() {
            return ((this.lowerBound != null) ? (31 + this.lowerBound.hashCode()) : 1) ^ 31 + this.upperBound.hashCode();
        }
        
        @Override
        public String toString() {
            if (this.lowerBound != null) {
                return "? super " + GsonTypes.typeToString(this.lowerBound);
            }
            if (this.upperBound == Object.class) {
                return "?";
            }
            return "? extends " + GsonTypes.typeToString(this.upperBound);
        }
    }
}
