1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements. See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership. The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License. You may obtain a copy of the License at
9  *
10  *   http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing,
13  * software distributed under the License is distributed on an
14  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15  * KIND, either express or implied. See the License for the
16  * specific language governing permissions and limitations
17  * under the License.
18  */
19 package org.apache.thrift;
20 
21 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
22 import static org.junit.jupiter.api.Assertions.assertEquals;
23 import static org.junit.jupiter.api.Assertions.assertFalse;
24 import static org.junit.jupiter.api.Assertions.assertNotNull;
25 import static org.junit.jupiter.api.Assertions.assertNull;
26 import static org.junit.jupiter.api.Assertions.assertThrows;
27 import static org.junit.jupiter.api.Assertions.assertTrue;
28 
29 import java.io.ByteArrayInputStream;
30 import java.io.ByteArrayOutputStream;
31 import java.io.ObjectInputStream;
32 import java.io.ObjectOutputStream;
33 import java.nio.ByteBuffer;
34 import java.util.HashMap;
35 import java.util.Map;
36 import org.apache.thrift.meta_data.FieldMetaData;
37 import org.apache.thrift.meta_data.ListMetaData;
38 import org.apache.thrift.meta_data.MapMetaData;
39 import org.apache.thrift.meta_data.SetMetaData;
40 import org.apache.thrift.meta_data.StructMetaData;
41 import org.apache.thrift.protocol.TBinaryProtocol;
42 import org.apache.thrift.protocol.TType;
43 import org.junit.jupiter.api.Test;
44 import thrift.test.Bonk;
45 import thrift.test.CrazyNesting;
46 import thrift.test.HolyMoley;
47 import thrift.test.Insanity;
48 import thrift.test.JavaTestHelper;
49 import thrift.test.Nesting;
50 import thrift.test.Numberz;
51 import thrift.test.OneOfEach;
52 import thrift.test.StructA;
53 import thrift.test.StructB;
54 import thrift.test.Xtruct;
55 
56 public class TestStruct {
57   @Test
testIdentity()58   public void testIdentity() throws Exception {
59     TSerializer binarySerializer = new TSerializer(new TBinaryProtocol.Factory());
60     TDeserializer binaryDeserializer = new TDeserializer(new TBinaryProtocol.Factory());
61 
62     OneOfEach ooe = Fixtures.getOneOfEach();
63 
64     Nesting n = new Nesting();
65     n.my_ooe = ooe;
66     n.my_ooe.integer16 = 16;
67     n.my_ooe.integer32 = 32;
68     n.my_ooe.integer64 = 64;
69     n.my_ooe.double_precision = (Math.sqrt(5) + 1) / 2;
70     n.my_ooe.some_characters = ":R (me going \"rrrr\")";
71     n.my_ooe.zomg_unicode =
72         "\u04c0\u216e\u039d\u0020\u041d\u03bf\u217f"
73             + "\u043e\u0261\u0433\u0430\u03c1\u210e\u0020"
74             + "\u0391\u0074\u0074\u03b1\u217d\u03ba\u01c3"
75             + "\u203c";
76     n.my_bonk = Fixtures.getNesting().my_bonk;
77 
78     HolyMoley hm = Fixtures.getHolyMoley();
79 
80     OneOfEach ooe2 = new OneOfEach();
81     binaryDeserializer.deserialize(ooe2, binarySerializer.serialize(ooe));
82 
83     assertEquals(ooe, ooe2);
84     assertEquals(ooe.hashCode(), ooe2.hashCode());
85 
86     Nesting n2 = new Nesting();
87     binaryDeserializer.deserialize(n2, binarySerializer.serialize(n));
88 
89     assertEquals(n, n2);
90     assertEquals(n.hashCode(), n2.hashCode());
91 
92     HolyMoley hm2 = new HolyMoley();
93     binaryDeserializer.deserialize(hm2, binarySerializer.serialize(hm));
94 
95     assertEquals(hm, hm2);
96     assertEquals(hm.hashCode(), hm2.hashCode());
97   }
98 
99   @Test
testDeepCopy()100   public void testDeepCopy() throws Exception {
101     TSerializer binarySerializer = new TSerializer(new TBinaryProtocol.Factory());
102     TDeserializer binaryDeserializer = new TDeserializer(new TBinaryProtocol.Factory());
103 
104     HolyMoley hm = Fixtures.getHolyMoley();
105 
106     byte[] binaryCopy = binarySerializer.serialize(hm);
107     HolyMoley hmCopy = new HolyMoley();
108     binaryDeserializer.deserialize(hmCopy, binaryCopy);
109     HolyMoley hmCopy2 = new HolyMoley(hm);
110 
111     assertEquals(hm, hmCopy);
112     assertEquals(hmCopy, hmCopy2);
113 
114     // change binary value in original object
115     hm.big.get(0).base64.array()[0]++;
116     // make sure the change didn't propagate to the copied object
117     assertFalse(hm.equals(hmCopy2));
118     hm.big.get(0).base64.array()[0]--; // undo change
119 
120     hmCopy2.bonks.get("two").get(1).message = "What else?";
121 
122     assertFalse(hm.equals(hmCopy2));
123   }
124 
125   @Test
testCompareTo()126   public void testCompareTo() throws Exception {
127     Bonk bonk1 = new Bonk();
128     Bonk bonk2 = new Bonk();
129 
130     // Compare empty thrift objects.
131     assertEquals(0, bonk1.compareTo(bonk2));
132 
133     bonk1.setMessage("m");
134 
135     // Compare one thrift object with a filled in field and another without it.
136     assertTrue(bonk1.compareTo(bonk2) > 0);
137     assertTrue(bonk2.compareTo(bonk1) < 0);
138 
139     // Compare both have filled-in fields.
140     bonk2.setMessage("z");
141     assertTrue(bonk1.compareTo(bonk2) < 0);
142     assertTrue(bonk2.compareTo(bonk1) > 0);
143 
144     // Compare bonk1 has a field filled in that bonk2 doesn't.
145     bonk1.setType(123);
146     assertTrue(bonk1.compareTo(bonk2) > 0);
147     assertTrue(bonk2.compareTo(bonk1) < 0);
148 
149     // Compare bonk1 and bonk2 equal.
150     bonk2.setType(123);
151     bonk2.setMessage("m");
152     assertEquals(0, bonk1.compareTo(bonk2));
153   }
154 
155   @Test
156   public void testCompareToWithDataStructures() {
157     Insanity insanity1 = new Insanity();
158     Insanity insanity2 = new Insanity();
159 
160     // Both empty.
161     expectEquals(insanity1, insanity2);
162 
163     insanity1.setUserMap(new HashMap<Numberz, Long>());
164     // insanity1.map = {}, insanity2.map = null
165     expectGreaterThan(insanity1, insanity2);
166 
167     // insanity1.map = {2:1}, insanity2.map = null
168     insanity1.getUserMap().put(Numberz.TWO, 1L);
169     expectGreaterThan(insanity1, insanity2);
170 
171     // insanity1.map = {2:1}, insanity2.map = {}
172     insanity2.setUserMap(new HashMap<Numberz, Long>());
173     expectGreaterThan(insanity1, insanity2);
174 
175     // insanity1.map = {2:1}, insanity2.map = {2:2}
176     insanity2.getUserMap().put(Numberz.TWO, 2L);
177     expectLessThan(insanity1, insanity2);
178 
179     // insanity1.map = {2:1, 3:5}, insanity2.map = {2:2}
180     insanity1.getUserMap().put(Numberz.THREE, 5L);
181     expectGreaterThan(insanity1, insanity2);
182 
183     // insanity1.map = {2:1, 3:5}, insanity2.map = {2:1, 4:5}
184     insanity2.getUserMap().put(Numberz.TWO, 1L);
185     insanity2.getUserMap().put(Numberz.FIVE, 5L);
186     expectLessThan(insanity1, insanity2);
187   }
188 
189   private void expectLessThan(Insanity insanity1, Insanity insanity2) {
190     int compareTo = insanity1.compareTo(insanity2);
191     assertTrue(
192         compareTo < 0, insanity1 + " should be less than " + insanity2 + ", but is: " + compareTo);
193   }
194 
195   private void expectGreaterThan(Insanity insanity1, Insanity insanity2) {
196     int compareTo = insanity1.compareTo(insanity2);
197     assertTrue(
198         compareTo > 0,
199         insanity1 + " should be greater than " + insanity2 + ", but is: " + compareTo);
200   }
201 
202   private void expectEquals(Insanity insanity1, Insanity insanity2) {
203     int compareTo = insanity1.compareTo(insanity2);
204     assertEquals(
205         0, compareTo, insanity1 + " should be equal to " + insanity2 + ", but is: " + compareTo);
206   }
207 
208   @Test
209   public void testMetaData() throws Exception {
210     Map<CrazyNesting._Fields, FieldMetaData> mdMap = CrazyNesting.metaDataMap;
211 
212     // Check for struct fields existence
213     assertEquals(5, mdMap.size());
214     assertTrue(mdMap.containsKey(CrazyNesting._Fields.SET_FIELD));
215     assertTrue(mdMap.containsKey(CrazyNesting._Fields.LIST_FIELD));
216     assertTrue(mdMap.containsKey(CrazyNesting._Fields.STRING_FIELD));
217     assertTrue(mdMap.containsKey(CrazyNesting._Fields.BINARY_FIELD));
218     assertTrue(mdMap.containsKey(CrazyNesting._Fields.UUID_FIELD));
219 
220     // Check for struct fields contents
221     assertEquals("string_field", mdMap.get(CrazyNesting._Fields.STRING_FIELD).fieldName);
222     assertEquals("list_field", mdMap.get(CrazyNesting._Fields.LIST_FIELD).fieldName);
223     assertEquals("set_field", mdMap.get(CrazyNesting._Fields.SET_FIELD).fieldName);
224     assertEquals("binary_field", mdMap.get(CrazyNesting._Fields.BINARY_FIELD).fieldName);
225     assertEquals("uuid_field", mdMap.get(CrazyNesting._Fields.UUID_FIELD).fieldName);
226 
227     assertEquals(
228         TFieldRequirementType.DEFAULT,
229         mdMap.get(CrazyNesting._Fields.STRING_FIELD).requirementType);
230     assertEquals(
231         TFieldRequirementType.REQUIRED, mdMap.get(CrazyNesting._Fields.LIST_FIELD).requirementType);
232     assertEquals(
233         TFieldRequirementType.OPTIONAL, mdMap.get(CrazyNesting._Fields.SET_FIELD).requirementType);
234 
235     assertEquals(TType.STRING, mdMap.get(CrazyNesting._Fields.STRING_FIELD).valueMetaData.type);
236     assertFalse(mdMap.get(CrazyNesting._Fields.STRING_FIELD).valueMetaData.isBinary());
237     assertEquals(TType.LIST, mdMap.get(CrazyNesting._Fields.LIST_FIELD).valueMetaData.type);
238     assertEquals(TType.SET, mdMap.get(CrazyNesting._Fields.SET_FIELD).valueMetaData.type);
239     assertEquals(TType.STRING, mdMap.get(CrazyNesting._Fields.BINARY_FIELD).valueMetaData.type);
240     assertEquals(TType.UUID, mdMap.get(CrazyNesting._Fields.UUID_FIELD).valueMetaData.type);
241     assertTrue(mdMap.get(CrazyNesting._Fields.BINARY_FIELD).valueMetaData.isBinary());
242 
243     // Check nested structures
244     assertTrue(mdMap.get(CrazyNesting._Fields.LIST_FIELD).valueMetaData.isContainer());
245 
246     assertFalse(mdMap.get(CrazyNesting._Fields.LIST_FIELD).valueMetaData.isStruct());
247 
248     assertEquals(
249         TType.STRUCT,
250         ((MapMetaData)
251                 ((ListMetaData)
252                         ((SetMetaData)
253                                 ((MapMetaData)
254                                         ((MapMetaData)
255                                                 ((ListMetaData)
256                                                         mdMap.get(CrazyNesting._Fields.LIST_FIELD)
257                                                             .valueMetaData)
258                                                     .elemMetaData)
259                                             .valueMetaData)
260                                     .valueMetaData)
261                             .elemMetaData)
262                     .elemMetaData)
263             .keyMetaData
264             .type);
265 
266     assertEquals(
267         Insanity.class,
268         ((StructMetaData)
269                 ((MapMetaData)
270                         ((ListMetaData)
271                                 ((SetMetaData)
272                                         ((MapMetaData)
273                                                 ((MapMetaData)
274                                                         ((ListMetaData)
275                                                                 mdMap.get(
276                                                                         CrazyNesting._Fields
277                                                                             .LIST_FIELD)
278                                                                     .valueMetaData)
279                                                             .elemMetaData)
280                                                     .valueMetaData)
281                                             .valueMetaData)
282                                     .elemMetaData)
283                             .elemMetaData)
284                     .keyMetaData)
285             .structClass);
286 
287     // Check that FieldMetaData contains a map with metadata for all generated struct classes
288     assertNotNull(FieldMetaData.getStructMetaDataMap(CrazyNesting.class));
289     assertNotNull(FieldMetaData.getStructMetaDataMap(Insanity.class));
290     assertNotNull(FieldMetaData.getStructMetaDataMap(Xtruct.class));
291 
292     assertEquals(CrazyNesting.metaDataMap, FieldMetaData.getStructMetaDataMap(CrazyNesting.class));
293     assertEquals(Insanity.metaDataMap, FieldMetaData.getStructMetaDataMap(Insanity.class));
294 
295     for (Map.Entry<? extends TFieldIdEnum, FieldMetaData> mdEntry : mdMap.entrySet()) {
296       assertEquals(mdEntry.getKey(), CrazyNesting._Fields.findByName(mdEntry.getValue().fieldName));
297     }
298 
299     MapMetaData vmd =
300         (MapMetaData) Insanity.metaDataMap.get(Insanity._Fields.USER_MAP).valueMetaData;
301     assertTrue(vmd.valueMetaData.isTypedef());
302     assertFalse(vmd.keyMetaData.isTypedef());
303   }
304 
305   @Test
306   public void testToString() throws Exception {
307     JavaTestHelper object = new JavaTestHelper();
308     object.req_int = 0;
309     object.req_obj = "";
310 
311     object.req_bin =
312         ByteBuffer.wrap(
313             new byte[] {
314               0, -1, 2, -3, 4, -5, 6, -7, 8, -9, 10, -11, 12, -13, 14, -15, 16, -17, 18, -19, 20,
315               -21, 22, -23, 24, -25, 26, -27, 28, -29, 30, -31, 32, -33, 34, -35, 36, -37, 38, -39,
316               40, -41, 42, -43, 44, -45, 46, -47, 48, -49, 50, -51, 52, -53, 54, -55, 56, -57, 58,
317               -59, 60, -61, 62, -63, 64, -65, 66, -67, 68, -69, 70, -71, 72, -73, 74, -75, 76, -77,
318               78, -79, 80, -81, 82, -83, 84, -85, 86, -87, 88, -89, 90, -91, 92, -93, 94, -95, 96,
319               -97, 98, -99, 100, -101, 102, -103, 104, -105, 106, -107, 108, -109, 110, -111, 112,
320               -113, 114, -115, 116, -117, 118, -119, 120, -121, 122, -123, 124, -125, 126, -127,
321             });
322 
323     assertEquals(
324         "JavaTestHelper(req_int:0, req_obj:, req_bin:"
325             + "00 FF 02 FD 04 FB 06 F9 08 F7 0A F5 0C F3 0E F1 10 EF 12 ED 14 "
326             + "EB 16 E9 18 E7 1A E5 1C E3 1E E1 20 DF 22 DD 24 DB 26 D9 28 D7 "
327             + "2A D5 2C D3 2E D1 30 CF 32 CD 34 CB 36 C9 38 C7 3A C5 3C C3 3E "
328             + "C1 40 BF 42 BD 44 BB 46 B9 48 B7 4A B5 4C B3 4E B1 50 AF 52 AD "
329             + "54 AB 56 A9 58 A7 5A A5 5C A3 5E A1 60 9F 62 9D 64 9B 66 99 68 "
330             + "97 6A 95 6C 93 6E 91 70 8F 72 8D 74 8B 76 89 78 87 7A 85 7C 83 "
331             + "7E 81)",
332         object.toString());
333 
334     object.req_bin =
335         ByteBuffer.wrap(
336             new byte[] {
337               0, -1, 2, -3, 4, -5, 6, -7, 8, -9, 10, -11, 12, -13, 14, -15, 16, -17, 18, -19, 20,
338               -21, 22, -23, 24, -25, 26, -27, 28, -29, 30, -31, 32, -33, 34, -35, 36, -37, 38, -39,
339               40, -41, 42, -43, 44, -45, 46, -47, 48, -49, 50, -51, 52, -53, 54, -55, 56, -57, 58,
340               -59, 60, -61, 62, -63, 64, -65, 66, -67, 68, -69, 70, -71, 72, -73, 74, -75, 76, -77,
341               78, -79, 80, -81, 82, -83, 84, -85, 86, -87, 88, -89, 90, -91, 92, -93, 94, -95, 96,
342               -97, 98, -99, 100, -101, 102, -103, 104, -105, 106, -107, 108, -109, 110, -111, 112,
343               -113, 114, -115, 116, -117, 118, -119, 120, -121, 122, -123, 124, -125, 126, -127, 0,
344             });
345 
346     assertEquals(
347         "JavaTestHelper(req_int:0, req_obj:, req_bin:"
348             + "00 FF 02 FD 04 FB 06 F9 08 F7 0A F5 0C F3 0E F1 10 EF 12 ED 14 "
349             + "EB 16 E9 18 E7 1A E5 1C E3 1E E1 20 DF 22 DD 24 DB 26 D9 28 D7 "
350             + "2A D5 2C D3 2E D1 30 CF 32 CD 34 CB 36 C9 38 C7 3A C5 3C C3 3E "
351             + "C1 40 BF 42 BD 44 BB 46 B9 48 B7 4A B5 4C B3 4E B1 50 AF 52 AD "
352             + "54 AB 56 A9 58 A7 5A A5 5C A3 5E A1 60 9F 62 9D 64 9B 66 99 68 "
353             + "97 6A 95 6C 93 6E 91 70 8F 72 8D 74 8B 76 89 78 87 7A 85 7C 83 "
354             + "7E 81...)",
355         object.toString());
356 
357     object.req_bin = ByteBuffer.wrap(new byte[] {});
358     object.setOpt_binIsSet(true);
359 
360     assertEquals("JavaTestHelper(req_int:0, req_obj:, req_bin:)", object.toString());
361   }
362 
363   @Test
364   public void testBytesBufferFeatures() throws Exception {
365 
366     final String testString = "testBytesBufferFeatures";
367     final JavaTestHelper o = new JavaTestHelper();
368 
369     o.setReq_bin((ByteBuffer) null);
370     assertNull(o.getReq_bin());
371 
372     o.setReq_bin(ByteBuffer.wrap(testString.getBytes()));
373     assertArrayEquals(testString.getBytes(), o.getReq_bin());
374 
375     o.setReq_bin((byte[]) null);
376     assertNull(o.getReq_bin());
377 
378     o.setReq_bin(testString.getBytes());
379     assertArrayEquals(testString.getBytes(), o.getReq_bin());
380 
381     o.setFieldValue(JavaTestHelper._Fields.REQ_BIN, null);
382     assertNull(o.getReq_bin());
383 
384     o.setFieldValue(JavaTestHelper._Fields.REQ_BIN, testString.getBytes());
385     assertArrayEquals(testString.getBytes(), o.getReq_bin());
386 
387     o.setFieldValue(JavaTestHelper._Fields.REQ_BIN, null);
388     assertNull(o.getReq_bin());
389 
390     o.setFieldValue(JavaTestHelper._Fields.REQ_BIN, ByteBuffer.wrap(testString.getBytes()));
391     assertArrayEquals(testString.getBytes(), o.getReq_bin());
392   }
393 
394   @Test
395   public void testJavaSerializable() throws Exception {
396     ByteArrayOutputStream baos = new ByteArrayOutputStream();
397     ObjectOutputStream oos = new ObjectOutputStream(baos);
398 
399     OneOfEach ooe = Fixtures.getOneOfEach();
400 
401     // Serialize ooe the Java way...
402     oos.writeObject(ooe);
403     byte[] serialized = baos.toByteArray();
404 
405     // Attempt to deserialize it
406     ByteArrayInputStream bais = new ByteArrayInputStream(serialized);
407     ObjectInputStream ois = new ObjectInputStream(bais);
408     OneOfEach ooe2 = (OneOfEach) ois.readObject();
409 
410     assertEquals(ooe, ooe2);
411   }
412 
413   @Test
414   public void testSubStructValidation() throws Exception {
415     StructA valid = new StructA("valid");
416     StructA invalid = new StructA();
417 
418     StructB b = new StructB();
419     assertThrows(TException.class, b::validate);
420 
421     b = new StructB().setAb(valid);
422     b.validate();
423 
424     b = new StructB().setAb(invalid);
425     assertThrows(TException.class, b::validate);
426 
427     b = new StructB().setAb(valid).setAa(invalid);
428     assertThrows(TException.class, b::validate);
429   }
430 }
431