summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar jeff2015-02-05 23:51:40 -0500
committerGravatar jeff2015-02-05 23:51:40 -0500
commit73313b0f1c1660986afc1449960889cac242eee0 (patch)
tree90799533267205999d983821f70e81edc1593a83
parentturn off debug stuff (diff)
downloadenigma-73313b0f1c1660986afc1449960889cac242eee0.tar.gz
enigma-73313b0f1c1660986afc1449960889cac242eee0.tar.xz
enigma-73313b0f1c1660986afc1449960889cac242eee0.zip
add new type/signature system
-rw-r--r--src/cuchaz/enigma/mapping/BehaviorSignature.java96
-rw-r--r--src/cuchaz/enigma/mapping/Type.java179
-rw-r--r--test/cuchaz/enigma/TestBehaviorSignature.java196
-rw-r--r--test/cuchaz/enigma/TestType.java183
4 files changed, 654 insertions, 0 deletions
diff --git a/src/cuchaz/enigma/mapping/BehaviorSignature.java b/src/cuchaz/enigma/mapping/BehaviorSignature.java
new file mode 100644
index 00000000..a6371f85
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/BehaviorSignature.java
@@ -0,0 +1,96 @@
1package cuchaz.enigma.mapping;
2
3import java.util.List;
4
5import com.beust.jcommander.internal.Lists;
6
7public class BehaviorSignature {
8
9 public static interface ClassReplacer {
10 ClassEntry replace(ClassEntry entry);
11 }
12
13 private List<Type> m_argumentTypes;
14 private Type m_returnType;
15
16 public BehaviorSignature(String signature) {
17 m_argumentTypes = Lists.newArrayList();
18 int i=0;
19 while (i<signature.length()) {
20 char c = signature.charAt(i);
21 if (c == '(') {
22 assert(m_argumentTypes.isEmpty());
23 assert(m_returnType == null);
24 i++;
25 } else if (c == ')') {
26 i++;
27 break;
28 } else {
29 String type = Type.parseFirst(signature.substring(i));
30 m_argumentTypes.add(new Type(type));
31 i += type.length();
32 }
33 }
34 m_returnType = new Type(Type.parseFirst(signature.substring(i)));
35 }
36
37 public BehaviorSignature(BehaviorSignature other, ClassReplacer replacer) {
38 m_argumentTypes = Lists.newArrayList(other.m_argumentTypes);
39 for (int i=0; i<m_argumentTypes.size(); i++) {
40 Type type = m_argumentTypes.get(i);
41 if (type.isClass()) {
42 ClassEntry newClassEntry = replacer.replace(type.getClassEntry());
43 if (newClassEntry != null) {
44 m_argumentTypes.set(i, new Type(newClassEntry));
45 }
46 }
47 }
48 m_returnType = other.m_returnType;
49 if (other.m_returnType.isClass()) {
50 ClassEntry newClassEntry = replacer.replace(m_returnType.getClassEntry());
51 if (newClassEntry != null) {
52 m_returnType = new Type(newClassEntry);
53 }
54 }
55 }
56
57 public List<Type> getArgumentTypes() {
58 return m_argumentTypes;
59 }
60
61 public Type getReturnType() {
62 return m_returnType;
63 }
64
65 @Override
66 public String toString() {
67 StringBuilder buf = new StringBuilder();
68 buf.append("(");
69 for (int i=0; i<m_argumentTypes.size(); i++) {
70 if (i > 0) {
71 buf.append(",");
72 }
73 buf.append(m_argumentTypes.get(i).toString());
74 }
75 buf.append(")");
76 buf.append(m_returnType.toString());
77 return buf.toString();
78 }
79
80 public Iterable<Type> types() {
81 List<Type> types = Lists.newArrayList();
82 types.addAll(m_argumentTypes);
83 types.add(m_returnType);
84 return types;
85 }
86
87 public Iterable<ClassEntry> classes() {
88 List<ClassEntry> out = Lists.newArrayList();
89 for (Type type : types()) {
90 if (type.isClass()) {
91 out.add(type.getClassEntry());
92 }
93 }
94 return out;
95 }
96}
diff --git a/src/cuchaz/enigma/mapping/Type.java b/src/cuchaz/enigma/mapping/Type.java
new file mode 100644
index 00000000..2273e21b
--- /dev/null
+++ b/src/cuchaz/enigma/mapping/Type.java
@@ -0,0 +1,179 @@
1package cuchaz.enigma.mapping;
2
3import java.util.Map;
4
5import com.google.common.collect.Maps;
6
7public class Type {
8
9 public enum Primitive {
10 Byte('B'),
11 Character('C'),
12 Short('S'),
13 Integer('I'),
14 Long('J'),
15 Float('F'),
16 Double('D'),
17 Boolean('Z');
18
19 private static final Map<Character,Primitive> m_lookup;
20
21 static {
22 m_lookup = Maps.newTreeMap();
23 for (Primitive val : values()) {
24 m_lookup.put(val.getCode(), val);
25 }
26 }
27
28 public static Primitive get(char code) {
29 return m_lookup.get(code);
30 }
31
32 private char m_code;
33
34 private Primitive(char code) {
35 m_code = code;
36 }
37
38 public char getCode() {
39 return m_code;
40 }
41 }
42
43 public static String parseFirst(String in) {
44
45 // read one type from the input
46
47 char c = in.charAt(0);
48
49 // first check for void
50 if (c == 'V') {
51 return "V";
52 }
53
54 // then check for primitives
55 Primitive primitive = Primitive.get(c);
56 if (primitive != null) {
57 return in.substring(0, 1);
58 }
59
60 // then check for classes
61 if (c == 'L') {
62 return readClass(in);
63 }
64
65 // then check for arrays
66 int dim = countArrayDimension(in);
67 if (dim > 0) {
68 String arrayType = Type.parseFirst(in.substring(dim));
69 return in.substring(0, dim + arrayType.length());
70 }
71
72 throw new IllegalArgumentException("don't know how to parse: " + in);
73 }
74
75 private String m_name;
76
77 public Type(String name) {
78 m_name = name;
79 }
80
81 public Type(ClassEntry classEntry) {
82 m_name = "L" + classEntry.getClassName() + ";";
83 }
84
85 @Override
86 public String toString() {
87 return m_name;
88 }
89
90 public boolean isVoid() {
91 return m_name.length() == 1 && m_name.charAt(0) == 'V';
92 }
93
94 public boolean isPrimitive() {
95 return m_name.length() == 1 && Primitive.get(m_name.charAt(0)) != null;
96 }
97
98 public Primitive getPrimitive() {
99 if (!isPrimitive()) {
100 throw new IllegalStateException("not a primitive");
101 }
102 return Primitive.get(m_name.charAt(0));
103 }
104
105 public boolean isClass() {
106 return m_name.charAt(0) == 'L' && m_name.charAt(m_name.length() - 1) == ';';
107 }
108
109 public ClassEntry getClassEntry() {
110 if (!isClass()) {
111 throw new IllegalStateException("not a class");
112 }
113 String name = m_name.substring(1, m_name.length() - 1);
114
115 int pos = name.indexOf('<');
116 if (pos >= 0) {
117 // remove the parameters from the class name
118 name = name.substring(0, pos);
119 }
120
121 return new ClassEntry(name);
122 }
123
124 public boolean isArray() {
125 return m_name.charAt(0) == '[';
126 }
127
128 public int getArrayDimension() {
129 if (!isArray()) {
130 throw new IllegalStateException("not an array");
131 }
132 return countArrayDimension(m_name);
133 }
134
135 public Type getArrayType() {
136 if (!isArray()) {
137 throw new IllegalStateException("not an array");
138 }
139 return new Type(m_name.substring(getArrayDimension(), m_name.length()));
140 }
141
142 @Override
143 public boolean equals(Object other) {
144 if (other instanceof Type) {
145 return equals((Type)other);
146 }
147 return false;
148 }
149
150 public boolean equals(Type other) {
151 return m_name.equals(other.m_name);
152 }
153
154 private static int countArrayDimension(String in) {
155 int i=0;
156 for(; i < in.length() && in.charAt(i) == '['; i++);
157 return i;
158 }
159
160 private static String readClass(String in) {
161 // read all the characters in the buffer until we hit a ';'
162 // remember to treat parameters correctly
163 StringBuilder buf = new StringBuilder();
164 int depth = 0;
165 for (int i=0; i<in.length(); i++) {
166 char c = in.charAt(i);
167 buf.append(c);
168
169 if (c == '<') {
170 depth++;
171 } else if (c == '>') {
172 depth--;
173 } else if (depth == 0 && c == ';') {
174 return buf.toString();
175 }
176 }
177 return null;
178 }
179}
diff --git a/test/cuchaz/enigma/TestBehaviorSignature.java b/test/cuchaz/enigma/TestBehaviorSignature.java
new file mode 100644
index 00000000..4705cd12
--- /dev/null
+++ b/test/cuchaz/enigma/TestBehaviorSignature.java
@@ -0,0 +1,196 @@
1package cuchaz.enigma;
2
3import static org.hamcrest.MatcherAssert.*;
4import static org.hamcrest.Matchers.*;
5
6import org.junit.Test;
7
8import cuchaz.enigma.mapping.BehaviorSignature;
9import cuchaz.enigma.mapping.ClassEntry;
10import cuchaz.enigma.mapping.Type;
11
12
13public class TestBehaviorSignature {
14
15 @Test
16 public void easiest() {
17 final BehaviorSignature sig = new BehaviorSignature("()V");
18 assertThat(sig.getArgumentTypes(), is(empty()));
19 assertThat(sig.getReturnType(), is(new Type("V")));
20 }
21
22 @Test
23 public void primitives() {
24 {
25 final BehaviorSignature sig = new BehaviorSignature("(I)V");
26 assertThat(sig.getArgumentTypes(), contains(
27 new Type("I")
28 ));
29 assertThat(sig.getReturnType(), is(new Type("V")));
30 }
31 {
32 final BehaviorSignature sig = new BehaviorSignature("(I)I");
33 assertThat(sig.getArgumentTypes(), contains(
34 new Type("I")
35 ));
36 assertThat(sig.getReturnType(), is(new Type("I")));
37 }
38 {
39 final BehaviorSignature sig = new BehaviorSignature("(IBCJ)Z");
40 assertThat(sig.getArgumentTypes(), contains(
41 new Type("I"),
42 new Type("B"),
43 new Type("C"),
44 new Type("J")
45 ));
46 assertThat(sig.getReturnType(), is(new Type("Z")));
47 }
48 }
49
50 @Test
51 public void classes() {
52 {
53 final BehaviorSignature sig = new BehaviorSignature("([LFoo;)V");
54 assertThat(sig.getArgumentTypes().size(), is(1));
55 assertThat(sig.getArgumentTypes().get(0), is(new Type("[LFoo;")));
56 assertThat(sig.getReturnType(), is(new Type("V")));
57 }
58 {
59 final BehaviorSignature sig = new BehaviorSignature("(LFoo;)LBar;");
60 assertThat(sig.getArgumentTypes(), contains(
61 new Type("LFoo;")
62 ));
63 assertThat(sig.getReturnType(), is(new Type("LBar;")));
64 }
65 {
66 final BehaviorSignature sig = new BehaviorSignature("(LFoo;LMoo;LZoo;)LBar;");
67 assertThat(sig.getArgumentTypes(), contains(
68 new Type("LFoo;"),
69 new Type("LMoo;"),
70 new Type("LZoo;")
71 ));
72 assertThat(sig.getReturnType(), is(new Type("LBar;")));
73 }
74 {
75 final BehaviorSignature sig = new BehaviorSignature("(LFoo<LParm;>;LMoo<LParm;>;)LBar<LParm;>;");
76 assertThat(sig.getArgumentTypes(), contains(
77 new Type("LFoo<LParm;>;"),
78 new Type("LMoo<LParm;>;")
79 ));
80 assertThat(sig.getReturnType(), is(new Type("LBar<LParm;>;")));
81 }
82 }
83
84 @Test
85 public void arrays() {
86 {
87 final BehaviorSignature sig = new BehaviorSignature("([I)V");
88 assertThat(sig.getArgumentTypes(), contains(
89 new Type("[I")
90 ));
91 assertThat(sig.getReturnType(), is(new Type("V")));
92 }
93 {
94 final BehaviorSignature sig = new BehaviorSignature("([I)[J");
95 assertThat(sig.getArgumentTypes(), contains(
96 new Type("[I")
97 ));
98 assertThat(sig.getReturnType(), is(new Type("[J")));
99 }
100 {
101 final BehaviorSignature sig = new BehaviorSignature("([I[Z[F)[D");
102 assertThat(sig.getArgumentTypes(), contains(
103 new Type("[I"),
104 new Type("[Z"),
105 new Type("[F")
106 ));
107 assertThat(sig.getReturnType(), is(new Type("[D")));
108 }
109 }
110
111 @Test
112 public void mixed() {
113 {
114 final BehaviorSignature sig = new BehaviorSignature("(I[JLFoo;)Z");
115 assertThat(sig.getArgumentTypes(), contains(
116 new Type("I"),
117 new Type("[J"),
118 new Type("LFoo;")
119 ));
120 assertThat(sig.getReturnType(), is(new Type("Z")));
121 }
122 {
123 final BehaviorSignature sig = new BehaviorSignature("(III)[LFoo;");
124 assertThat(sig.getArgumentTypes(), contains(
125 new Type("I"),
126 new Type("I"),
127 new Type("I")
128 ));
129 assertThat(sig.getReturnType(), is(new Type("[LFoo;")));
130 }
131 }
132
133 @Test
134 public void replaceClasses() {
135 {
136 final BehaviorSignature oldSig = new BehaviorSignature("()V");
137 final BehaviorSignature sig = new BehaviorSignature(oldSig, new BehaviorSignature.ClassReplacer() {
138 @Override
139 public ClassEntry replace(ClassEntry entry) {
140 return null;
141 }
142 });
143 assertThat(sig.getArgumentTypes(), is(empty()));
144 assertThat(sig.getReturnType(), is(new Type("V")));
145 }
146 {
147 final BehaviorSignature oldSig = new BehaviorSignature("(IJLFoo;)V");
148 final BehaviorSignature sig = new BehaviorSignature(oldSig, new BehaviorSignature.ClassReplacer() {
149 @Override
150 public ClassEntry replace(ClassEntry entry) {
151 return null;
152 }
153 });
154 assertThat(sig.getArgumentTypes(), contains(
155 new Type("I"),
156 new Type("J"),
157 new Type("LFoo;")
158 ));
159 assertThat(sig.getReturnType(), is(new Type("V")));
160 }
161 {
162 final BehaviorSignature oldSig = new BehaviorSignature("(LFoo;LBar;)LMoo;");
163 final BehaviorSignature sig = new BehaviorSignature(oldSig, new BehaviorSignature.ClassReplacer() {
164 @Override
165 public ClassEntry replace(ClassEntry entry) {
166 if (entry.getName().equals("Foo")) {
167 return new ClassEntry("Bar");
168 }
169 return null;
170 }
171 });
172 assertThat(sig.getArgumentTypes(), contains(
173 new Type("LBar;"),
174 new Type("LBar;")
175 ));
176 assertThat(sig.getReturnType(), is(new Type("LMoo;")));
177 }
178 {
179 final BehaviorSignature oldSig = new BehaviorSignature("(LFoo;LBar;)LMoo;");
180 final BehaviorSignature sig = new BehaviorSignature(oldSig, new BehaviorSignature.ClassReplacer() {
181 @Override
182 public ClassEntry replace(ClassEntry entry) {
183 if (entry.getName().equals("Moo")) {
184 return new ClassEntry("Cow");
185 }
186 return null;
187 }
188 });
189 assertThat(sig.getArgumentTypes(), contains(
190 new Type("LFoo;"),
191 new Type("LBar;")
192 ));
193 assertThat(sig.getReturnType(), is(new Type("LCow;")));
194 }
195 }
196}
diff --git a/test/cuchaz/enigma/TestType.java b/test/cuchaz/enigma/TestType.java
new file mode 100644
index 00000000..efc6a4c3
--- /dev/null
+++ b/test/cuchaz/enigma/TestType.java
@@ -0,0 +1,183 @@
1package cuchaz.enigma;
2
3import org.junit.Test;
4
5import static org.hamcrest.MatcherAssert.*;
6import static org.hamcrest.Matchers.*;
7
8import cuchaz.enigma.mapping.Type;
9
10import static cuchaz.enigma.EntryFactory.*;
11
12
13public class TestType {
14
15 @Test
16 public void isVoid() {
17 assertThat(new Type("V").isVoid(), is(true));
18 assertThat(new Type("Z").isVoid(), is(false));
19 assertThat(new Type("B").isVoid(), is(false));
20 assertThat(new Type("C").isVoid(), is(false));
21 assertThat(new Type("I").isVoid(), is(false));
22 assertThat(new Type("J").isVoid(), is(false));
23 assertThat(new Type("F").isVoid(), is(false));
24 assertThat(new Type("D").isVoid(), is(false));
25 assertThat(new Type("LFoo;").isVoid(), is(false));
26 assertThat(new Type("[I").isVoid(), is(false));
27 }
28
29 @Test
30 public void isPrimitive() {
31 assertThat(new Type("V").isPrimitive(), is(false));
32 assertThat(new Type("Z").isPrimitive(), is(true));
33 assertThat(new Type("B").isPrimitive(), is(true));
34 assertThat(new Type("C").isPrimitive(), is(true));
35 assertThat(new Type("I").isPrimitive(), is(true));
36 assertThat(new Type("J").isPrimitive(), is(true));
37 assertThat(new Type("F").isPrimitive(), is(true));
38 assertThat(new Type("D").isPrimitive(), is(true));
39 assertThat(new Type("LFoo;").isPrimitive(), is(false));
40 assertThat(new Type("[I").isPrimitive(), is(false));
41 }
42
43 @Test
44 public void getPrimitive() {
45 assertThat(new Type("Z").getPrimitive(), is(Type.Primitive.Boolean));
46 assertThat(new Type("B").getPrimitive(), is(Type.Primitive.Byte));
47 assertThat(new Type("C").getPrimitive(), is(Type.Primitive.Character));
48 assertThat(new Type("I").getPrimitive(), is(Type.Primitive.Integer));
49 assertThat(new Type("J").getPrimitive(), is(Type.Primitive.Long));
50 assertThat(new Type("F").getPrimitive(), is(Type.Primitive.Float));
51 assertThat(new Type("D").getPrimitive(), is(Type.Primitive.Double));
52 }
53
54 @Test
55 public void isClass() {
56 assertThat(new Type("V").isClass(), is(false));
57 assertThat(new Type("Z").isClass(), is(false));
58 assertThat(new Type("B").isClass(), is(false));
59 assertThat(new Type("C").isClass(), is(false));
60 assertThat(new Type("I").isClass(), is(false));
61 assertThat(new Type("J").isClass(), is(false));
62 assertThat(new Type("F").isClass(), is(false));
63 assertThat(new Type("D").isClass(), is(false));
64 assertThat(new Type("LFoo;").isClass(), is(true));
65 assertThat(new Type("[I").isClass(), is(false));
66 }
67
68 @Test
69 public void getClassEntry() {
70 assertThat(new Type("LFoo;").getClassEntry(), is(newClass("Foo")));
71 assertThat(new Type("LFoo<Ljava/lang/String;>;").getClassEntry(), is(newClass("Foo")));
72 }
73
74 @Test
75 public void isArray() {
76 assertThat(new Type("V").isArray(), is(false));
77 assertThat(new Type("Z").isArray(), is(false));
78 assertThat(new Type("B").isArray(), is(false));
79 assertThat(new Type("C").isArray(), is(false));
80 assertThat(new Type("I").isArray(), is(false));
81 assertThat(new Type("J").isArray(), is(false));
82 assertThat(new Type("F").isArray(), is(false));
83 assertThat(new Type("D").isArray(), is(false));
84 assertThat(new Type("LFoo;").isArray(), is(false));
85 assertThat(new Type("[I").isArray(), is(true));
86 }
87
88 @Test
89 public void getArrayDimension() {
90 assertThat(new Type("[I").getArrayDimension(), is(1));
91 assertThat(new Type("[[I").getArrayDimension(), is(2));
92 assertThat(new Type("[[[I").getArrayDimension(), is(3));
93 }
94
95 @Test
96 public void getArrayType() {
97 assertThat(new Type("[I").getArrayType(), is(new Type("I")));
98 assertThat(new Type("[[I").getArrayType(), is(new Type("I")));
99 assertThat(new Type("[[[I").getArrayType(), is(new Type("I")));
100 assertThat(new Type("[Ljava/lang/String;").getArrayType(), is(new Type("Ljava/lang/String;")));
101 }
102
103 @Test
104 public void parseVoid() {
105 final String answer = "V";
106 assertThat(Type.parseFirst("V"), is(answer));
107 assertThat(Type.parseFirst("VVV"), is(answer));
108 assertThat(Type.parseFirst("VIJ"), is(answer));
109 assertThat(Type.parseFirst("V[I"), is(answer));
110 assertThat(Type.parseFirst("VLFoo;"), is(answer));
111 assertThat(Type.parseFirst("V[LFoo;"), is(answer));
112 }
113
114 @Test
115 public void parsePrimitive() {
116 final String answer = "I";
117 assertThat(Type.parseFirst("I"), is(answer));
118 assertThat(Type.parseFirst("III"), is(answer));
119 assertThat(Type.parseFirst("IJZ"), is(answer));
120 assertThat(Type.parseFirst("I[I"), is(answer));
121 assertThat(Type.parseFirst("ILFoo;"), is(answer));
122 assertThat(Type.parseFirst("I[LFoo;"), is(answer));
123 }
124
125 @Test
126 public void parseClass() {
127 {
128 final String answer = "LFoo;";
129 assertThat(Type.parseFirst("LFoo;"), is(answer));
130 assertThat(Type.parseFirst("LFoo;I"), is(answer));
131 assertThat(Type.parseFirst("LFoo;JZ"), is(answer));
132 assertThat(Type.parseFirst("LFoo;[I"), is(answer));
133 assertThat(Type.parseFirst("LFoo;LFoo;"), is(answer));
134 assertThat(Type.parseFirst("LFoo;[LFoo;"), is(answer));
135 }
136 {
137 final String answer = "LFoo<LFoo;>;";
138 assertThat(Type.parseFirst("LFoo<LFoo;>;"), is(answer));
139 assertThat(Type.parseFirst("LFoo<LFoo;>;I"), is(answer));
140 assertThat(Type.parseFirst("LFoo<LFoo;>;JZ"), is(answer));
141 assertThat(Type.parseFirst("LFoo<LFoo;>;[I"), is(answer));
142 assertThat(Type.parseFirst("LFoo<LFoo;>;LFoo;"), is(answer));
143 assertThat(Type.parseFirst("LFoo<LFoo;>;[LFoo;"), is(answer));
144 }
145 {
146 final String answer = "LFoo<LFoo;,LBar;>;";
147 assertThat(Type.parseFirst("LFoo<LFoo;,LBar;>;"), is(answer));
148 assertThat(Type.parseFirst("LFoo<LFoo;,LBar;>;I"), is(answer));
149 assertThat(Type.parseFirst("LFoo<LFoo;,LBar;>;JZ"), is(answer));
150 assertThat(Type.parseFirst("LFoo<LFoo;,LBar;>;[I"), is(answer));
151 assertThat(Type.parseFirst("LFoo<LFoo;,LBar;>;LFoo;"), is(answer));
152 assertThat(Type.parseFirst("LFoo<LFoo;,LBar;>;[LFoo;"), is(answer));
153 }
154 }
155
156 @Test
157 public void parseArray() {
158 {
159 final String answer = "[I";
160 assertThat(Type.parseFirst("[I"), is(answer));
161 assertThat(Type.parseFirst("[III"), is(answer));
162 assertThat(Type.parseFirst("[IJZ"), is(answer));
163 assertThat(Type.parseFirst("[I[I"), is(answer));
164 assertThat(Type.parseFirst("[ILFoo;"), is(answer));
165 }
166 {
167 final String answer = "[[I";
168 assertThat(Type.parseFirst("[[I"), is(answer));
169 assertThat(Type.parseFirst("[[III"), is(answer));
170 assertThat(Type.parseFirst("[[IJZ"), is(answer));
171 assertThat(Type.parseFirst("[[I[I"), is(answer));
172 assertThat(Type.parseFirst("[[ILFoo;"), is(answer));
173 }
174 {
175 final String answer = "[LFoo;";
176 assertThat(Type.parseFirst("[LFoo;"), is(answer));
177 assertThat(Type.parseFirst("[LFoo;II"), is(answer));
178 assertThat(Type.parseFirst("[LFoo;JZ"), is(answer));
179 assertThat(Type.parseFirst("[LFoo;[I"), is(answer));
180 assertThat(Type.parseFirst("[LFoo;LFoo;"), is(answer));
181 }
182 }
183}