View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.juneau;
18  
19  import static org.apache.juneau.TestUtils.*;
20  import static org.junit.jupiter.api.Assertions.*;
21  
22  import java.lang.reflect.*;
23  import java.util.*;
24  import java.util.function.*;
25  
26  import org.apache.juneau.html.*;
27  import org.apache.juneau.json.*;
28  import org.apache.juneau.msgpack.*;
29  import org.apache.juneau.parser.*;
30  import org.apache.juneau.serializer.*;
31  import org.apache.juneau.uon.*;
32  import org.apache.juneau.urlencoding.*;
33  import org.apache.juneau.utils.*;
34  import org.apache.juneau.xml.*;
35  
36  /**
37   * Represents the input to a ComboTest.
38   * @param <T>
39   */
40  public class ComboRoundTrip_Tester<T> {
41  
42  	public static <T> Builder<T> create(int index, String label, Type type, Supplier<T> in) {
43  		return new Builder<>(index, label, type, in);
44  	}
45  
46  	public static class Builder<T> {
47  		private int index;
48  		private String label;
49  		private Supplier<T> in;
50  		private String exceptionMsg;
51  		private Predicate<String> skipTest = x -> false;
52  		private Function<T,T> postConvert = x -> x;
53  		private List<Function<T,String>> verify = list();
54  		private List<Class<?>> swaps = list();
55  		private Type type;
56  		private Map<String,String> expected = map();
57  		private List<Tuple2<Class<?>,Consumer<?>>> applies = list();
58  		private Consumer<Serializer.Builder> serializerApply = x -> {};
59  		private Consumer<Parser.Builder> parserApply = x -> {};
60  
61  		public Builder(int index, String label, Type type, T in) {
62  			this.index = index;
63  			this.label = label;
64  			this.type = type;
65  			this.in = () -> in;
66  		}
67  
68  		public Builder(int index, String label, Type type, Supplier<T> in) {
69  			this.index = index;
70  			this.label = label;
71  			this.type = type;
72  			this.in = in;
73  		}
74  
75  		public Builder<T> beanContext(Consumer<BeanContext.Builder> c) { apply(BeanContext.Builder.class, c); return this; }
76  
77  		public <T2> Builder<T> apply(Class<T2> t, Consumer<T2> c) { applies.add(Tuple2.of(t, c)); return this; }
78  
79  		public Builder<T> exceptionMsg(String v) { exceptionMsg = v; return this; }
80  
81  		public Builder<T> skipTest(Predicate<String> v) { skipTest = v; return this; }
82  
83  		public Builder<T> postConvert(Function<T,T> v) { postConvert = v; return this; }
84  
85  		public Builder<T> verify(Function<T,String> v) { verify.add(v); return this; }
86  
87  		public Builder<T> verify(Predicate<T> p, String msg, Object...args) { verify.add(x -> p.test(x) ? null : f(msg, args)); return this; }
88  
89  		public Builder<T> swaps(Class<?>...c) { swaps.addAll(list(c)); return this; }
90  
91  		public Builder<T> serializerApply(Consumer<Serializer.Builder> v) { serializerApply = v; return this; }
92  
93  		public Builder<T> parserApply(Consumer<Parser.Builder> v) { parserApply = v; return this; }
94  
95  		public Builder<T> json(String value) { expected.put("json", value); return this; }
96  		public Builder<T> jsonT(String value) { expected.put("jsonT", value); return this; }
97  		public Builder<T> jsonR(String value) { expected.put("jsonR", value); return this; }
98  		public Builder<T> xml(String value) { expected.put("xml", value); return this; }
99  		public Builder<T> xmlT(String value) { expected.put("xmlT", value); return this; }
100 		public Builder<T> xmlR(String value) { expected.put("xmlR", value); return this; }
101 		public Builder<T> xmlNs(String value) { expected.put("xmlNs", value); return this; }
102 		public Builder<T> html(String value) { expected.put("html", value); return this; }
103 		public Builder<T> htmlT(String value) { expected.put("htmlT", value); return this; }
104 		public Builder<T> htmlR(String value) { expected.put("htmlR", value); return this; }
105 		public Builder<T> uon(String value) { expected.put("uon", value); return this; }
106 		public Builder<T> uonT(String value) { expected.put("uonT", value); return this; }
107 		public Builder<T> uonR(String value) { expected.put("uonR", value); return this; }
108 		public Builder<T> urlEnc(String value) { expected.put("urlEnc", value); return this; }
109 		public Builder<T> urlEncT(String value) { expected.put("urlEncT", value); return this; }
110 		public Builder<T> urlEncR(String value) { expected.put("urlEncR", value); return this; }
111 		public Builder<T> msgPack(String value) { expected.put("msgPack", value); return this; }
112 		public Builder<T> msgPackT(String value) { expected.put("msgPackT", value); return this; }
113 		public Builder<T> rdfXml(String value) { expected.put("rdfXml", value); return this; }
114 		public Builder<T> rdfXmlT(String value) { expected.put("rdfXmlT", value); return this; }
115 		public Builder<T> rdfXmlR(String value) { expected.put("rdfXmlR", value); return this; }
116 
117 		public ComboRoundTrip_Tester<T> build() {
118 			return new ComboRoundTrip_Tester<>(this);
119 		}
120 	}
121 
122 	private final String label;
123 	private final Supplier<T> in;
124 	private final String exceptionMsg;
125 	private final Predicate<String> skipTest;
126 	private final List<Function<T,String>> verify;
127 	private final Type type;
128 	private final Map<String,String> expected;
129 	private final Map<String,Serializer> serializers = map();
130 	private final Map<String,Parser> parsers = map();
131 	private final Function<T,T> postConvert;
132 
133 	private ComboRoundTrip_Tester(Builder<T> b) {
134 		label = "[" + b.index + "] " + b.label;
135 		type = b.type;
136 		in = b.in;
137 		expected = b.expected;
138 		postConvert = b.postConvert;
139 		verify = b.verify;
140 		skipTest = b.skipTest;
141 		exceptionMsg = b.exceptionMsg;
142 
143 		serializers.put("json", create(b, Json5Serializer.DEFAULT.copy().addBeanTypes().addRootType()));
144 		serializers.put("jsonT", create(b, JsonSerializer.create().json5().typePropertyName("t").addBeanTypes().addRootType()));
145 		serializers.put("jsonR", create(b, Json5Serializer.DEFAULT_READABLE.copy().addBeanTypes().addRootType()));
146 		serializers.put("xml", create(b, XmlSerializer.DEFAULT_SQ.copy().addBeanTypes().addRootType()));
147 		serializers.put("xmlT", create(b, XmlSerializer.create().sq().typePropertyName("t").addBeanTypes().addRootType()));
148 		serializers.put("xmlR", create(b, XmlSerializer.DEFAULT_SQ_READABLE.copy().addBeanTypes().addRootType()));
149 		serializers.put("xmlNs", create(b, XmlSerializer.DEFAULT_NS_SQ.copy().addBeanTypes().addRootType()));
150 		serializers.put("html", create(b, HtmlSerializer.DEFAULT_SQ.copy().addBeanTypes().addRootType()));
151 		serializers.put("htmlT", create(b, HtmlSerializer.create().sq().typePropertyName("t").addBeanTypes().addRootType()));
152 		serializers.put("htmlR", create(b, HtmlSerializer.DEFAULT_SQ_READABLE.copy().addBeanTypes().addRootType()));
153 		serializers.put("uon", create(b, UonSerializer.DEFAULT.copy().addBeanTypes().addRootType()));
154 		serializers.put("uonT", create(b, UonSerializer.create().typePropertyName("t").addBeanTypes().addRootType()));
155 		serializers.put("uonR", create(b, UonSerializer.DEFAULT_READABLE.copy().addBeanTypes().addRootType()));
156 		serializers.put("urlEnc", create(b, UrlEncodingSerializer.DEFAULT.copy().addBeanTypes().addRootType()));
157 		serializers.put("urlEncT", create(b, UrlEncodingSerializer.create().typePropertyName("t").addBeanTypes().addRootType()));
158 		serializers.put("urlEncR", create(b, UrlEncodingSerializer.DEFAULT_READABLE.copy().addBeanTypes().addRootType()));
159 		serializers.put("msgPack", create(b, MsgPackSerializer.create().addBeanTypes().addRootType()));
160 		serializers.put("msgPackT", create(b, MsgPackSerializer.create().typePropertyName("t").addBeanTypes().addRootType()));
161 
162 		parsers.put("json", create(b, JsonParser.DEFAULT.copy()));
163 		parsers.put("jsonT", create(b, JsonParser.create().typePropertyName("t")));
164 		parsers.put("jsonR", create(b, JsonParser.DEFAULT.copy()));
165 		parsers.put("xml", create(b, XmlParser.DEFAULT.copy()));
166 		parsers.put("xmlT", create(b, XmlParser.create().typePropertyName("t")));
167 		parsers.put("xmlR", create(b, XmlParser.DEFAULT.copy()));
168 		parsers.put("xmlNs", create(b, XmlParser.DEFAULT.copy()));
169 		parsers.put("html", create(b, HtmlParser.DEFAULT.copy()));
170 		parsers.put("htmlT", create(b, HtmlParser.create().typePropertyName("t")));
171 		parsers.put("htmlR", create(b, HtmlParser.DEFAULT.copy()));
172 		parsers.put("uon", create(b, UonParser.DEFAULT.copy()));
173 		parsers.put("uonT", create(b, UonParser.create().typePropertyName("t")));
174 		parsers.put("uonR", create(b, UonParser.DEFAULT.copy()));
175 		parsers.put("urlEnc", create(b, UrlEncodingParser.DEFAULT.copy()));
176 		parsers.put("urlEncT", create(b, UrlEncodingParser.create().typePropertyName("t")));
177 		parsers.put("urlEncR", create(b, UrlEncodingParser.DEFAULT.copy()));
178 		parsers.put("msgPack", create(b, MsgPackParser.DEFAULT.copy()));
179 		parsers.put("msgPackT", create(b, MsgPackParser.create().typePropertyName("t")));
180 	}
181 
182 	@SuppressWarnings("unchecked")
183 	private Serializer create(Builder<?> tb, Serializer.Builder sb) {
184 		tb.serializerApply.accept(sb);
185 		sb.swaps(tb.swaps);
186 		tb.applies.forEach(x -> {
187 			if (x.getA().equals(BeanContext.Builder.class))
188 				sb.beanContext((Consumer<BeanContext.Builder>) x.getB());
189 			else if (x.getA().isInstance(sb))
190 				sb.apply(Serializer.Builder.class, (Consumer<Serializer.Builder>) x.getB());
191 		});
192 		return sb.build();
193 	}
194 
195 	@SuppressWarnings("unchecked")
196 	private Parser create(Builder<?> tb, Parser.Builder pb) {
197 		tb.parserApply.accept(pb);
198 		pb.swaps(tb.swaps);
199 		tb.applies.forEach(x -> {
200 			if (x.getA().equals(BeanContext.Builder.class))
201 				pb.beanContext((Consumer<BeanContext.Builder>) x.getB());
202 			else if (x.getA().isInstance(pb))
203 				pb.apply(Parser.Builder.class, (Consumer<Parser.Builder>) x.getB());
204 		});
205 		return pb.build();
206 	}
207 
208 	private void verify(T o, String testName) {
209 		for (var v : verify) {
210 			var s = v.apply(o);
211 			if (isNotEmpty(s)) {
212 				throw new BasicAssertionError("Verification failed on test {0}/{1}: {2}", label, testName, s);
213 			}
214 		}
215 	}
216 
217 	private boolean isSkipped(String testName, String expected) {
218 		return "SKIP".equals(expected) || skipTest.test(testName);
219 	}
220 
221 	public void testSerialize(String testName) throws Exception {
222 		var s = serializers.get(testName);
223 		var exp = expected.get(testName);
224 		try {
225 			if (isSkipped(testName + "-serialize", exp)) return;
226 
227 			var r = s.serializeToString(in.get());
228 
229 			// Specifying "xxx" in the expected results will spit out what we should populate the field with.
230 			if (eq(exp, "xxx")) {
231 				System.out.println(getClass().getName() + ": " + label + "/" + testName + "=\n" + r.replaceAll("\n", "\\\\n").replaceAll("\t", "\\\\t")); // NOT DEBUG
232 				System.out.println(r);
233 			}
234 
235 			assertEquals(exp, r, fms("{0}/{1} serialize-normal failed.", label, testName));
236 		} catch (AssertionError e) {
237 			if (exceptionMsg == null)
238 				throw e;
239 			assertContains(exceptionMsg, e.getMessage());
240 		} catch (Exception e) {
241 			if (exceptionMsg == null)
242 				throw new BasicAssertionError(e, "{0}/{1} failed.  exception={2}", label, testName, e.getLocalizedMessage());
243 			assertContains(exceptionMsg, e.getMessage());
244 		}
245 	}
246 
247 	@SuppressWarnings("unchecked")
248 	public void testParse(String testName) throws Exception {
249 		var s = serializers.get(testName);
250 		var exp = expected.get(testName);
251 		var p = parsers.get(testName);
252 		try {
253 			if (isSkipped(testName + "-parse", exp)) return;
254 
255 			var r = s.serializeToString(in.get());
256 			var o = p.parse(r, type);
257 			o = postConvert.apply((T)o);
258 			r = s.serializeToString(o);
259 
260 			assertEquals(exp, r, fms("{0}/{1} parse-normal failed", label, testName));
261 		} catch (AssertionError e) {
262 			if (exceptionMsg == null)
263 				throw e;
264 			assertContains(exceptionMsg, e.getMessage());
265 		} catch (Throwable e) {
266 			if (exceptionMsg == null)
267 				throw new BasicAssertionError(e, "{0}/{1} failed.  exception={2}", label, testName, e.getLocalizedMessage());
268 			assertContains(exceptionMsg, e.getMessage());
269 		}
270 	}
271 
272 	@SuppressWarnings("unchecked")
273 	public void testParseVerify(String testName) throws Exception {
274 		var s = serializers.get(testName);
275 		var p = parsers.get(testName);
276 		try {
277 			if (isSkipped(testName + "verify", "")) return;
278 
279 			var r = s.serializeToString(in.get());
280 			var o = p.parse(r, type);
281 
282 			verify((T)o, testName);
283 		} catch (AssertionError e) {
284 			if (exceptionMsg == null)
285 				throw e;
286 			assertContains(exceptionMsg, e.getMessage());
287 		} catch (Exception e) {
288 			if (exceptionMsg == null)
289 				throw new BasicAssertionError(e, "{0}/{1} failed.  exception={2}", label, testName, e.getLocalizedMessage());
290 			assertContains(exceptionMsg, e.getMessage());
291 		}
292 	}
293 
294 	public void testParseJsonEquivalency(String testName) throws Exception {
295 		var s = serializers.get(testName);
296 		var js = (WriterSerializer)serializers.get("json");
297 		var exp = expected.get("json");
298 		var p = parsers.get(testName);
299 		try {
300 			var r = s.serializeToString(in.get());
301 			var o = p.parse(r, type);
302 			r = js.serialize(o);
303 			assertEquals(exp, r, fms("{0}/{1} parse-normal failed on JSON equivalency", label, testName));
304 		} catch (AssertionError e) {
305 			if (exceptionMsg == null)
306 				throw e;
307 			assertContains(exceptionMsg, e.getMessage());
308 		} catch (Exception e) {
309 			if (exceptionMsg == null)
310 				throw new BasicAssertionError(e, "{0}/{1} failed.  exception={2}", label, testName, e.getLocalizedMessage());
311 			assertContains(exceptionMsg, e.getMessage());
312 		}
313 	}
314 
315 	@Override
316 	public String toString() {
317 		return "ComboRoundTripTester: " + label;
318 	}
319 }