
/*
 * Copyright (C) 2014 Archie L. Cobbs. All rights reserved.
 *
 * $Id$
 */

package org.jsimpledb.util;

import com.google.common.reflect.TypeResolver;
import com.google.common.reflect.TypeToken;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.regex.Matcher;

/**
 * Recreates {@link TypeToken}s from the output of {@link TypeToken#toString}.
 * Requires that no type variables or wildcards appear.
 */
public class TypeTokenParser {

    private final ClassLoader loader;

    private Method toGenericTypeMethod;

    /**
     * Default constructor.
     */
    public TypeTokenParser() {
        this(null);
    }

    /**
     * Primary constructor.
     *
     * @param loader class loader to use for loading classes,
     *  or null for the current thread's {@linkplain Thread#getContextClassLoader context loader}
     */
    public TypeTokenParser(ClassLoader loader) {
        if (loader == null)
            loader = Thread.currentThread().getContextClassLoader();
        this.loader = loader;
    }

    /**
     * Parse a {@link TypeToken} in string form.
     *
     * @param string string to parse
     * @throws IllegalArgumentException if the input is invalid
     * @throws ClassNotFoundException if a named class could not be found
     */
    public TypeToken<?> parse(String string) throws ClassNotFoundException {
        final ParseContext ctx = new ParseContext(string);
        final TypeToken<?> token = this.parse(new ParseContext(string));
        ctx.skipWhitespace();
        if (!ctx.isEOF())
            throw new IllegalArgumentException("string contains trailing garbage at index " + ctx.getIndex());
        return token;
    }

    /**
     * Parse a {@link TypeToken} in string form.
     * Since the {@link TypeToken} string format is self-delimiting, not all of the input may be consumed.
     *
     * @param ctx parse context
     * @throws IllegalArgumentException if the input is invalid
     * @throws ClassNotFoundException if a named class could not be found
     */
    public TypeToken<?> parse(ParseContext ctx) throws ClassNotFoundException {
        ctx.skipWhitespace();
        final Matcher matcher = ctx.matchPrefix("[^,<>]+");
        final String className = matcher.group();
        final Class<?> cl = this.loader.loadClass(className);
        final TypeVariable<?>[] typeParameters = cl.getTypeParameters();
        final TypeToken<?> typeToken = this.toGenericType(cl);
        ctx.skipWhitespace();
        if (ctx.isEOF())
            return typeToken;
        ctx.skipWhitespace();
        final ArrayList<Type> parameterTypes = new ArrayList<>(typeParameters.length);
        if (ctx.peek() == '<') {
            ctx.read();
            while (true) {
                ctx.skipWhitespace();
                parameterTypes.add(this.parse(ctx).getType());
                ctx.skipWhitespace();
                final int ch = ctx.read();
                if (ch == '>')
                    break;
                if (ch != ',')
                    throw new IllegalArgumentException("unexpected character `" + ch + "' at index " + ctx.getIndex());
            }
        }
        if (parameterTypes.size() != typeParameters.length) {
            throw new IllegalArgumentException(cl + " has " + typeParameters.length + " generic type parameters but "
              + parameterTypes.size() + " parameters were supplied");
        }
        TypeResolver typeResolver = new TypeResolver();
        for (int i = 0; i < typeParameters.length; i++)
            typeResolver = typeResolver.where(typeParameters[i], parameterTypes.get(i));
        return TypeToken.of(typeResolver.resolveType(typeToken.getType()));
    }

    /**
     * Convert a raw class back into its generic type.
     *
     * @param target raw class
     * @return generic {@link TypeToken} for {@code target}
     * @see <a href="https://code.google.com/p/guava-libraries/issues/detail?id=1645">Guava Issue #1645</a>
     */
    @SuppressWarnings("unchecked")
    private <T> TypeToken<? extends T> toGenericType(Class<T> target) {
        Type type;
        try {
            if (this.toGenericTypeMethod == null) {
                this.toGenericTypeMethod = Class.forName("com.google.common.reflect.Types", false, this.loader)
                  .getDeclaredMethod("newParameterizedType", Class.class);
                this.toGenericTypeMethod.setAccessible(true);
            }
            type = (Type)this.toGenericTypeMethod.invoke(null, target);
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException("unexpected exception", e);
        }
        return (TypeToken<T>)TypeToken.of(type);
    }

// Test method

    public static void main(String[] args) throws Exception {
        final TypeTokenParser parser = new TypeTokenParser();
        for (String arg : args)
            System.out.println(parser.parse(new ParseContext(arg)));
    }
}


@@ -106,55 +106,8 @@
 l;%0A%0A
-import com.google.common.reflect.TypeResolver;%0A
 impo
@@ -278,16 +278,39 @@
 ayList;%0A
+import java.util.List;%0A
 import j
@@ -420,17 +420,27 @@
 ng%7D.%0A * 
-R
+Currently r
 equires 
@@ -577,25 +577,32 @@
  Method 
-toGeneric
+newParameterized
 TypeMeth
@@ -2462,71 +2462,8 @@
 ();%0A
-        final TypeToken%3C?%3E typeToken = this.toGenericType(cl);%0A
     
@@ -2532,25 +2532,25 @@
  return 
-t
+T
 ypeToken
 ;%0A      
@@ -2537,24 +2537,31 @@
 rn TypeToken
+.of(cl)
 ;%0A        ct
@@ -3438,274 +3438,105 @@
     
-TypeResolver typeResolver = new TypeResolver();%0A        for (int i = 0; i %3C typeParameters.length; i++)%0A            typeResolver = typeResolver.where(typeParameters%5Bi%5D, parameterTypes.get(i));%0A        return TypeToken.of(typeResolver.resolveType(typeToken.getType())
+return parameterTypes.isEmpty() ? TypeToken.of(cl) : this.newParameterizedType(cl, parameterTypes
 );%0A 
@@ -3642,16 +3642,53 @@
 w class%0A
+     * @param params type parameters%0A
      * @
@@ -3929,25 +3929,32 @@
 ends T%3E 
-toGeneric
+newParameterized
 Type(Cla
@@ -3965,16 +3965,35 @@
 %3E target
+, List%3CType%3E params
 ) %7B%0A    
@@ -4038,33 +4038,40 @@
    if (this.
-toGeneric
+newParameterized
 TypeMethod =
@@ -4097,33 +4097,40 @@
        this.
-toGeneric
+newParameterized
 TypeMethod =
@@ -4271,16 +4271,30 @@
 ss.class
+, Type%5B%5D.class
 );%0A     
@@ -4305,33 +4305,40 @@
        this.
-toGeneric
+newParameterized
 TypeMethod.s
@@ -4405,17 +4405,24 @@
 his.
-toGeneric
+newParameterized
 Type
@@ -4447,16 +4447,57 @@
 , target
+, params.toArray(new Type%5Bparams.size()%5D)
 );%0A     

Update for newer Guava.