1 package com.fasterxml.jackson.databind.type;
2
3 import java.util.*;
4
5 import com.fasterxml.jackson.databind.JavaType;
6 import com.fasterxml.jackson.databind.util.ClassUtil;
7
8 /**
9  * Simple recursive-descent parser for parsing canonical {@link JavaType}
10  * representations and constructing type instances.
11  */

12 public class TypeParser
13     implements java.io.Serializable
14 {
15     private static final long serialVersionUID = 1L;
16
17     protected final TypeFactory _factory;
18         
19     public TypeParser(TypeFactory f) {
20         _factory = f;
21     }
22
23     /**
24      * @since 2.6.2
25      */

26     public TypeParser withFactory(TypeFactory f) {
27         return (f == _factory) ? this : new TypeParser(f);
28     }
29
30     public JavaType parse(String canonical) throws IllegalArgumentException
31     {
32         MyTokenizer tokens = new MyTokenizer(canonical.trim());
33         JavaType type = parseType(tokens);
34         // must be end, now
35         if (tokens.hasMoreTokens()) {
36             throw _problem(tokens, "Unexpected tokens after complete type");
37         }
38         return type;
39     }
40
41     protected JavaType parseType(MyTokenizer tokens)
42         throws IllegalArgumentException
43     {
44         if (!tokens.hasMoreTokens()) {
45             throw _problem(tokens, "Unexpected end-of-string");
46         }
47         Class<?> base = findClass(tokens.nextToken(), tokens);
48
49         // either end (ok, non generic type), or generics
50         if (tokens.hasMoreTokens()) {
51             String token = tokens.nextToken();
52             if ("<".equals(token)) {
53                 List<JavaType> parameterTypes = parseTypes(tokens);
54                 TypeBindings b = TypeBindings.create(base, parameterTypes);
55                 return _factory._fromClass(null, base, b);
56             }
57             // can be comma that separates types, or closing '>'
58             tokens.pushBack(token);
59         }
60         return _factory._fromClass(null, base, TypeBindings.emptyBindings());
61     }
62
63     protected List<JavaType> parseTypes(MyTokenizer tokens)
64         throws IllegalArgumentException
65     {
66         ArrayList<JavaType> types = new ArrayList<JavaType>();
67         while (tokens.hasMoreTokens()) {
68             types.add(parseType(tokens));
69             if (!tokens.hasMoreTokens()) break;
70             String token = tokens.nextToken();
71             if (">".equals(token)) return types;
72             if (!",".equals(token)) {
73                 throw _problem(tokens, "Unexpected token '"+token+"', expected ',' or '>')");
74             }
75         }
76         throw _problem(tokens, "Unexpected end-of-string");
77     }
78
79     protected Class<?> findClass(String className, MyTokenizer tokens)
80     {
81         try {
82             return _factory.findClass(className);
83         } catch (Exception e) {
84             ClassUtil.throwIfRTE(e);
85             throw _problem(tokens, "Cannot locate class '"+className+"', problem: "+e.getMessage());
86         }
87     }
88
89     protected IllegalArgumentException _problem(MyTokenizer tokens, String msg)
90     {
91         return new IllegalArgumentException(String.format("Failed to parse type '%s' (remaining: '%s'): %s",
92                 tokens.getAllInput(), tokens.getRemainingInput(), msg));
93     }
94
95     final static class MyTokenizer extends StringTokenizer
96     {
97         protected final String _input;
98
99         protected int _index;
100
101         protected String _pushbackToken;
102
103         public MyTokenizer(String str) {            
104             super(str, "<,>"true);
105             _input = str;
106         }
107
108         @Override
109         public boolean hasMoreTokens() {
110             return (_pushbackToken != null) || super.hasMoreTokens();
111         }
112         
113         @Override
114         public String nextToken() {
115             String token;
116             if (_pushbackToken != null) {
117                 token = _pushbackToken;
118                 _pushbackToken = null;
119             } else {
120                 token = super.nextToken();
121                 _index += token.length();
122                 token = token.trim();
123             }
124             return token;
125         }
126
127         public void pushBack(String token) {
128             _pushbackToken = token;
129             // let's NOT change index for now, since token may have been trim()ed
130         }
131
132         public String getAllInput() { return _input; }
133 //        public String getUsedInput() { return _input.substring(0, _index); }
134         public String getRemainingInput() { return _input.substring(_index); }
135     }
136 }
137