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.commons.utils.CollectionUtils.*;
20  import static org.apache.juneau.commons.utils.Utils.*;
21  import static org.apache.juneau.junit.bct.BctAssertions.*;
22  import static org.junit.jupiter.api.Assertions.*;
23  
24  import java.lang.reflect.*;
25  import java.util.*;
26  import java.util.function.*;
27  
28  import org.apache.juneau.commons.function.*;
29  import org.apache.juneau.html.*;
30  import org.apache.juneau.json.*;
31  import org.apache.juneau.msgpack.*;
32  import org.apache.juneau.parser.*;
33  import org.apache.juneau.serializer.*;
34  import org.apache.juneau.uon.*;
35  import org.apache.juneau.urlencoding.*;
36  import org.apache.juneau.xml.*;
37  
38  /**
39   * Represents the input to a ComboTest.
40   * @param <T>
41   */
42  public class ComboRoundTrip_Tester<T> {
43  
44  	public static <T> Builder<T> create(int index, String label, Type type, Supplier<T> in) {
45  		return new Builder<>(index, label, type, in);
46  	}
47  
48  	public static class Builder<T> {
49  		private int index;
50  		private String label;
51  		private Supplier<T> in;
52  		private String exceptionMsg;
53  		private Predicate<String> skipTest = x -> false;
54  		private Function<T,T> postConvert = x -> x;
55  		private List<Function<T,String>> verify = list();
56  		private List<Class<?>> swaps = list();
57  		private Type type;
58  		private Map<String,String> expected = map();
59  		private List<Tuple2<Class<?>,Consumer<?>>> applies = list();
60  		private Consumer<Serializer.Builder> serializerApply = x -> {};
61  		private Consumer<Parser.Builder> parserApply = x -> {};
62  
63  		public Builder(int index, String label, Type type, T in) {
64  			this.index = index;
65  			this.label = label;
66  			this.type = type;
67  			this.in = () -> in;
68  		}
69  
70  		public Builder(int index, String label, Type type, Supplier<T> in) {
71  			this.index = index;
72  			this.label = label;
73  			this.type = type;
74  			this.in = in;
75  		}
76  
77  		public Builder<T> beanContext(Consumer<BeanContext.Builder> c) { apply(BeanContext.Builder.class, c); return this; }
78  
79  		public <T2> Builder<T> apply(Class<T2> t, Consumer<T2> c) { applies.add(Tuple2.of(t, c)); return this; }
80  
81  		public Builder<T> exceptionMsg(String v) { exceptionMsg = v; return this; }
82  
83  		public Builder<T> skipTest(Predicate<String> v) { skipTest = v; return this; }
84  
85  		public Builder<T> postConvert(Function<T,T> v) { postConvert = v; return this; }
86  
87  		public Builder<T> verify(Function<T,String> v) { verify.add(v); return this; }
88  
89  		public Builder<T> verify(Predicate<T> p, String msg, Object...args) { verify.add(x -> p.test(x) ? null : f(msg, args)); return this; }
90  
91  		public Builder<T> swaps(Class<?>...c) { swaps.addAll(l(c)); return this; }
92  
93  		public Builder<T> serializerApply(Consumer<Serializer.Builder> v) { serializerApply = v; return this; }
94  
95  		public Builder<T> parserApply(Consumer<Parser.Builder> v) { parserApply = v; return this; }
96  
97  		public Builder<T> json(String value) { expected.put("json", value); return this; }
98  		public Builder<T> jsonT(String value) { expected.put("jsonT", value); return this; }
99  		public Builder<T> jsonR(String value) { expected.put("jsonR", value); return this; }
100 		public Builder<T> xml(String value) { expected.put("xml", value); return this; }
101 		public Builder<T> xmlT(String value) { expected.put("xmlT", value); return this; }
102 		public Builder<T> xmlR(String value) { expected.put("xmlR", value); return this; }
103 		public Builder<T> xmlNs(String value) { expected.put("xmlNs", value); return this; }
104 		public Builder<T> html(String value) { expected.put("html", value); return this; }
105 		public Builder<T> htmlT(String value) { expected.put("htmlT", value); return this; }
106 		public Builder<T> htmlR(String value) { expected.put("htmlR", value); return this; }
107 		public Builder<T> uon(String value) { expected.put("uon", value); return this; }
108 		public Builder<T> uonT(String value) { expected.put("uonT", value); return this; }
109 		public Builder<T> uonR(String value) { expected.put("uonR", value); return this; }
110 		public Builder<T> urlEnc(String value) { expected.put("urlEnc", value); return this; }
111 		public Builder<T> urlEncT(String value) { expected.put("urlEncT", value); return this; }
112 		public Builder<T> urlEncR(String value) { expected.put("urlEncR", value); return this; }
113 		public Builder<T> msgPack(String value) { expected.put("msgPack", value); return this; }
114 		public Builder<T> msgPackT(String value) { expected.put("msgPackT", value); return this; }
115 		public Builder<T> rdfXml(String value) { expected.put("rdfXml", value); return this; }
116 		public Builder<T> rdfXmlT(String value) { expected.put("rdfXmlT", value); return this; }
117 		public Builder<T> rdfXmlR(String value) { expected.put("rdfXmlR", value); return this; }
118 
119 		public ComboRoundTrip_Tester<T> build() {
120 			return new ComboRoundTrip_Tester<>(this);
121 		}
122 	}
123 
124 	private final String label;
125 	private final Supplier<T> in;
126 	private final String exceptionMsg;
127 	private final Predicate<String> skipTest;
128 	private final List<Function<T,String>> verify;
129 	private final Type type;
130 	private final Map<String,String> expected;
131 	private final Map<String,Serializer> serializers = map();
132 	private final Map<String,Parser> parsers = map();
133 	private final Function<T,T> postConvert;
134 
135 	private ComboRoundTrip_Tester(Builder<T> b) {
136 		label = "[" + b.index + "] " + b.label;
137 		type = b.type;
138 		in = b.in;
139 		expected = b.expected;
140 		postConvert = b.postConvert;
141 		verify = b.verify;
142 		skipTest = b.skipTest;
143 		exceptionMsg = b.exceptionMsg;
144 
145 		serializers.put("json", create(b, Json5Serializer.DEFAULT.copy().addBeanTypes().addRootType()));
146 		serializers.put("jsonT", create(b, JsonSerializer.create().json5().typePropertyName("t").addBeanTypes().addRootType()));
147 		serializers.put("jsonR", create(b, Json5Serializer.DEFAULT_READABLE.copy().addBeanTypes().addRootType()));
148 		serializers.put("xml", create(b, XmlSerializer.DEFAULT_SQ.copy().addBeanTypes().addRootType()));
149 		serializers.put("xmlT", create(b, XmlSerializer.create().sq().typePropertyName("t").addBeanTypes().addRootType()));
150 		serializers.put("xmlR", create(b, XmlSerializer.DEFAULT_SQ_READABLE.copy().addBeanTypes().addRootType()));
151 		serializers.put("xmlNs", create(b, XmlSerializer.DEFAULT_NS_SQ.copy().addBeanTypes().addRootType()));
152 		serializers.put("html", create(b, HtmlSerializer.DEFAULT_SQ.copy().addBeanTypes().addRootType()));
153 		serializers.put("htmlT", create(b, HtmlSerializer.create().sq().typePropertyName("t").addBeanTypes().addRootType()));
154 		serializers.put("htmlR", create(b, HtmlSerializer.DEFAULT_SQ_READABLE.copy().addBeanTypes().addRootType()));
155 		serializers.put("uon", create(b, UonSerializer.DEFAULT.copy().addBeanTypes().addRootType()));
156 		serializers.put("uonT", create(b, UonSerializer.create().typePropertyName("t").addBeanTypes().addRootType()));
157 		serializers.put("uonR", create(b, UonSerializer.DEFAULT_READABLE.copy().addBeanTypes().addRootType()));
158 		serializers.put("urlEnc", create(b, UrlEncodingSerializer.DEFAULT.copy().addBeanTypes().addRootType()));
159 		serializers.put("urlEncT", create(b, UrlEncodingSerializer.create().typePropertyName("t").addBeanTypes().addRootType()));
160 		serializers.put("urlEncR", create(b, UrlEncodingSerializer.DEFAULT_READABLE.copy().addBeanTypes().addRootType()));
161 		serializers.put("msgPack", create(b, MsgPackSerializer.create().addBeanTypes().addRootType()));
162 		serializers.put("msgPackT", create(b, MsgPackSerializer.create().typePropertyName("t").addBeanTypes().addRootType()));
163 
164 		parsers.put("json", create(b, JsonParser.DEFAULT.copy()));
165 		parsers.put("jsonT", create(b, JsonParser.create().typePropertyName("t")));
166 		parsers.put("jsonR", create(b, JsonParser.DEFAULT.copy()));
167 		parsers.put("xml", create(b, XmlParser.DEFAULT.copy()));
168 		parsers.put("xmlT", create(b, XmlParser.create().typePropertyName("t")));
169 		parsers.put("xmlR", create(b, XmlParser.DEFAULT.copy()));
170 		parsers.put("xmlNs", create(b, XmlParser.DEFAULT.copy()));
171 		parsers.put("html", create(b, HtmlParser.DEFAULT.copy()));
172 		parsers.put("htmlT", create(b, HtmlParser.create().typePropertyName("t")));
173 		parsers.put("htmlR", create(b, HtmlParser.DEFAULT.copy()));
174 		parsers.put("uon", create(b, UonParser.DEFAULT.copy()));
175 		parsers.put("uonT", create(b, UonParser.create().typePropertyName("t")));
176 		parsers.put("uonR", create(b, UonParser.DEFAULT.copy()));
177 		parsers.put("urlEnc", create(b, UrlEncodingParser.DEFAULT.copy()));
178 		parsers.put("urlEncT", create(b, UrlEncodingParser.create().typePropertyName("t")));
179 		parsers.put("urlEncR", create(b, UrlEncodingParser.DEFAULT.copy()));
180 		parsers.put("msgPack", create(b, MsgPackParser.DEFAULT.copy()));
181 		parsers.put("msgPackT", create(b, MsgPackParser.create().typePropertyName("t")));
182 	}
183 
184 	private Serializer create(Builder<?> tb, Serializer.Builder sb) {
185 		tb.serializerApply.accept(sb);
186 		sb.swaps(tb.swaps);
187 		tb.applies.forEach(x -> {
188 			if (x.getA().equals(BeanContext.Builder.class))
189 				sb.beanContext((Consumer<BeanContext.Builder>) x.getB());
190 			else if (x.getA().isInstance(sb))
191 				sb.asSubtype(Serializer.Builder.class).ifPresent((Consumer<Serializer.Builder>) x.getB());
192 		});
193 		return sb.build();
194 	}
195 
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.asSubtype(Parser.Builder.class).ifPresent((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 (ne(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, fs("{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 	public void testParse(String testName) throws Exception {
248 		var s = serializers.get(testName);
249 		var exp = expected.get(testName);
250 		var p = parsers.get(testName);
251 		try {
252 			if (isSkipped(testName + "-parse", exp)) return;
253 
254 			var r = s.serializeToString(in.get());
255 			var o = p.parse(r, type);
256 			o = postConvert.apply((T)o);
257 			r = s.serializeToString(o);
258 
259 			assertEquals(exp, r, fs("{0}/{1} parse-normal failed", label, testName));
260 		} catch (AssertionError e) {
261 			if (exceptionMsg == null)
262 				throw e;
263 			assertContains(exceptionMsg, e.getMessage());
264 		} catch (Throwable e) {
265 			if (exceptionMsg == null)
266 				throw new BasicAssertionError(e, "{0}/{1} failed.  exception={2}", label, testName, e.getLocalizedMessage());
267 			assertContains(exceptionMsg, e.getMessage());
268 		}
269 	}
270 
271 	public void testParseVerify(String testName) throws Exception {
272 		var s = serializers.get(testName);
273 		var p = parsers.get(testName);
274 		try {
275 			if (isSkipped(testName + "verify", "")) return;
276 
277 			var r = s.serializeToString(in.get());
278 			var o = p.parse(r, type);
279 
280 			verify((T)o, testName);
281 		} catch (AssertionError e) {
282 			if (exceptionMsg == null)
283 				throw e;
284 			assertContains(exceptionMsg, e.getMessage());
285 		} catch (Exception e) {
286 			if (exceptionMsg == null)
287 				throw new BasicAssertionError(e, "{0}/{1} failed.  exception={2}", label, testName, e.getLocalizedMessage());
288 			assertContains(exceptionMsg, e.getMessage());
289 		}
290 	}
291 
292 	public void testParseJsonEquivalency(String testName) throws Exception {
293 		var s = serializers.get(testName);
294 		var js = (WriterSerializer)serializers.get("json");
295 		var exp = expected.get("json");
296 		var p = parsers.get(testName);
297 		try {
298 			var r = s.serializeToString(in.get());
299 			var o = p.parse(r, type);
300 			r = js.serialize(o);
301 			assertEquals(exp, r, fs("{0}/{1} parse-normal failed on JSON equivalency", label, testName));
302 		} catch (AssertionError e) {
303 			if (exceptionMsg == null)
304 				throw e;
305 			assertContains(exceptionMsg, e.getMessage());
306 		} catch (Exception e) {
307 			if (exceptionMsg == null)
308 				throw new BasicAssertionError(e, "{0}/{1} failed.  exception={2}", label, testName, e.getLocalizedMessage());
309 			assertContains(exceptionMsg, e.getMessage());
310 		}
311 	}
312 
313 	@Override
314 	public String toString() {
315 		return "ComboRoundTripTester: " + label;
316 	}
317 }