OLD | NEW |
| (Empty) |
1 // Protocol Buffers - Google's data interchange format | |
2 // Copyright 2008 Google Inc. All rights reserved. | |
3 // http://code.google.com/p/protobuf/ | |
4 // | |
5 // Redistribution and use in source and binary forms, with or without | |
6 // modification, are permitted provided that the following conditions are | |
7 // met: | |
8 // | |
9 // * Redistributions of source code must retain the above copyright | |
10 // notice, this list of conditions and the following disclaimer. | |
11 // * Redistributions in binary form must reproduce the above | |
12 // copyright notice, this list of conditions and the following disclaimer | |
13 // in the documentation and/or other materials provided with the | |
14 // distribution. | |
15 // * Neither the name of Google Inc. nor the names of its | |
16 // contributors may be used to endorse or promote products derived from | |
17 // this software without specific prior written permission. | |
18 // | |
19 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
20 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | |
21 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | |
22 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | |
23 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | |
24 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | |
25 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
26 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
27 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
28 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
29 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
30 | |
31 package com.google.protobuf.test; | |
32 import com.google.protobuf.*; | |
33 | |
34 import com.google.protobuf.Descriptors.FieldDescriptor; | |
35 import protobuf_unittest.UnittestOptimizeFor.TestOptimizedForSize; | |
36 import protobuf_unittest.UnittestProto; | |
37 import protobuf_unittest.UnittestProto.ForeignMessage; | |
38 import protobuf_unittest.UnittestProto.TestAllExtensions; | |
39 import protobuf_unittest.UnittestProto.TestAllTypes; | |
40 import protobuf_unittest.UnittestProto.TestPackedTypes; | |
41 import protobuf_unittest.UnittestProto.TestRequired; | |
42 import protobuf_unittest.UnittestProto.TestRequiredForeign; | |
43 import protobuf_unittest.UnittestProto.TestUnpackedTypes; | |
44 | |
45 import junit.framework.TestCase; | |
46 | |
47 import java.util.Map; | |
48 | |
49 /** | |
50 * Unit test for {@link AbstractMessage}. | |
51 * | |
52 * @author kenton@google.com Kenton Varda | |
53 */ | |
54 public class AbstractMessageTest extends TestCase { | |
55 /** | |
56 * Extends AbstractMessage and wraps some other message object. The methods | |
57 * of the Message interface which aren't explicitly implemented by | |
58 * AbstractMessage are forwarded to the wrapped object. This allows us to | |
59 * test that AbstractMessage's implementations work even if the wrapped | |
60 * object does not use them. | |
61 */ | |
62 private static class AbstractMessageWrapper extends AbstractMessage { | |
63 private final Message wrappedMessage; | |
64 | |
65 public AbstractMessageWrapper(Message wrappedMessage) { | |
66 this.wrappedMessage = wrappedMessage; | |
67 } | |
68 | |
69 public Descriptors.Descriptor getDescriptorForType() { | |
70 return wrappedMessage.getDescriptorForType(); | |
71 } | |
72 public AbstractMessageWrapper getDefaultInstanceForType() { | |
73 return new AbstractMessageWrapper( | |
74 wrappedMessage.getDefaultInstanceForType()); | |
75 } | |
76 public Map<Descriptors.FieldDescriptor, Object> getAllFields() { | |
77 return wrappedMessage.getAllFields(); | |
78 } | |
79 public boolean hasField(Descriptors.FieldDescriptor field) { | |
80 return wrappedMessage.hasField(field); | |
81 } | |
82 public Object getField(Descriptors.FieldDescriptor field) { | |
83 return wrappedMessage.getField(field); | |
84 } | |
85 public int getRepeatedFieldCount(Descriptors.FieldDescriptor field) { | |
86 return wrappedMessage.getRepeatedFieldCount(field); | |
87 } | |
88 public Object getRepeatedField( | |
89 Descriptors.FieldDescriptor field, int index) { | |
90 return wrappedMessage.getRepeatedField(field, index); | |
91 } | |
92 public UnknownFieldSet getUnknownFields() { | |
93 return wrappedMessage.getUnknownFields(); | |
94 } | |
95 public Builder newBuilderForType() { | |
96 return new Builder(wrappedMessage.newBuilderForType()); | |
97 } | |
98 public Builder toBuilder() { | |
99 return new Builder(wrappedMessage.toBuilder()); | |
100 } | |
101 | |
102 static class Builder extends AbstractMessage.Builder<Builder> { | |
103 private final Message.Builder wrappedBuilder; | |
104 | |
105 public Builder(Message.Builder wrappedBuilder) { | |
106 this.wrappedBuilder = wrappedBuilder; | |
107 } | |
108 | |
109 public AbstractMessageWrapper build() { | |
110 return new AbstractMessageWrapper(wrappedBuilder.build()); | |
111 } | |
112 public AbstractMessageWrapper buildPartial() { | |
113 return new AbstractMessageWrapper(wrappedBuilder.buildPartial()); | |
114 } | |
115 public Builder clone() { | |
116 return new Builder(wrappedBuilder.clone()); | |
117 } | |
118 public boolean isInitialized() { | |
119 return clone().buildPartial().isInitialized(); | |
120 } | |
121 public Descriptors.Descriptor getDescriptorForType() { | |
122 return wrappedBuilder.getDescriptorForType(); | |
123 } | |
124 public AbstractMessageWrapper getDefaultInstanceForType() { | |
125 return new AbstractMessageWrapper( | |
126 wrappedBuilder.getDefaultInstanceForType()); | |
127 } | |
128 public Map<Descriptors.FieldDescriptor, Object> getAllFields() { | |
129 return wrappedBuilder.getAllFields(); | |
130 } | |
131 public Builder newBuilderForField(Descriptors.FieldDescriptor field) { | |
132 return new Builder(wrappedBuilder.newBuilderForField(field)); | |
133 } | |
134 public boolean hasField(Descriptors.FieldDescriptor field) { | |
135 return wrappedBuilder.hasField(field); | |
136 } | |
137 public Object getField(Descriptors.FieldDescriptor field) { | |
138 return wrappedBuilder.getField(field); | |
139 } | |
140 public Builder setField(Descriptors.FieldDescriptor field, Object value) { | |
141 wrappedBuilder.setField(field, value); | |
142 return this; | |
143 } | |
144 public Builder clearField(Descriptors.FieldDescriptor field) { | |
145 wrappedBuilder.clearField(field); | |
146 return this; | |
147 } | |
148 public int getRepeatedFieldCount(Descriptors.FieldDescriptor field) { | |
149 return wrappedBuilder.getRepeatedFieldCount(field); | |
150 } | |
151 public Object getRepeatedField( | |
152 Descriptors.FieldDescriptor field, int index) { | |
153 return wrappedBuilder.getRepeatedField(field, index); | |
154 } | |
155 public Builder setRepeatedField(Descriptors.FieldDescriptor field, | |
156 int index, Object value) { | |
157 wrappedBuilder.setRepeatedField(field, index, value); | |
158 return this; | |
159 } | |
160 public Builder addRepeatedField( | |
161 Descriptors.FieldDescriptor field, Object value) { | |
162 wrappedBuilder.addRepeatedField(field, value); | |
163 return this; | |
164 } | |
165 public UnknownFieldSet getUnknownFields() { | |
166 return wrappedBuilder.getUnknownFields(); | |
167 } | |
168 public Builder setUnknownFields(UnknownFieldSet unknownFields) { | |
169 wrappedBuilder.setUnknownFields(unknownFields); | |
170 return this; | |
171 } | |
172 @Override | |
173 public Message.Builder getFieldBuilder(FieldDescriptor field) { | |
174 return wrappedBuilder.getFieldBuilder(field); | |
175 } | |
176 } | |
177 public Parser<? extends Message> getParserForType() { | |
178 return wrappedMessage.getParserForType(); | |
179 } | |
180 } | |
181 | |
182 // ================================================================= | |
183 | |
184 TestUtil.ReflectionTester reflectionTester = | |
185 new TestUtil.ReflectionTester(TestAllTypes.getDescriptor(), null); | |
186 | |
187 TestUtil.ReflectionTester extensionsReflectionTester = | |
188 new TestUtil.ReflectionTester(TestAllExtensions.getDescriptor(), | |
189 TestUtil.getExtensionRegistry()); | |
190 | |
191 public void testClear() throws Exception { | |
192 AbstractMessageWrapper message = | |
193 new AbstractMessageWrapper.Builder( | |
194 TestAllTypes.newBuilder(TestUtil.getAllSet())) | |
195 .clear().build(); | |
196 TestUtil.assertClear((TestAllTypes) message.wrappedMessage); | |
197 } | |
198 | |
199 public void testCopy() throws Exception { | |
200 AbstractMessageWrapper message = | |
201 new AbstractMessageWrapper.Builder(TestAllTypes.newBuilder()) | |
202 .mergeFrom(TestUtil.getAllSet()).build(); | |
203 TestUtil.assertAllFieldsSet((TestAllTypes) message.wrappedMessage); | |
204 } | |
205 | |
206 public void testSerializedSize() throws Exception { | |
207 TestAllTypes message = TestUtil.getAllSet(); | |
208 Message abstractMessage = new AbstractMessageWrapper(TestUtil.getAllSet()); | |
209 | |
210 assertEquals(message.getSerializedSize(), | |
211 abstractMessage.getSerializedSize()); | |
212 } | |
213 | |
214 public void testSerialization() throws Exception { | |
215 Message abstractMessage = new AbstractMessageWrapper(TestUtil.getAllSet()); | |
216 | |
217 TestUtil.assertAllFieldsSet( | |
218 TestAllTypes.parseFrom(abstractMessage.toByteString())); | |
219 | |
220 assertEquals(TestUtil.getAllSet().toByteString(), | |
221 abstractMessage.toByteString()); | |
222 } | |
223 | |
224 public void testParsing() throws Exception { | |
225 AbstractMessageWrapper.Builder builder = | |
226 new AbstractMessageWrapper.Builder(TestAllTypes.newBuilder()); | |
227 AbstractMessageWrapper message = | |
228 builder.mergeFrom(TestUtil.getAllSet().toByteString()).build(); | |
229 TestUtil.assertAllFieldsSet((TestAllTypes) message.wrappedMessage); | |
230 } | |
231 | |
232 public void testParsingUninitialized() throws Exception { | |
233 TestRequiredForeign.Builder builder = TestRequiredForeign.newBuilder(); | |
234 builder.getOptionalMessageBuilder().setDummy2(10); | |
235 ByteString bytes = builder.buildPartial().toByteString(); | |
236 Message.Builder abstractMessageBuilder = | |
237 new AbstractMessageWrapper.Builder(TestRequiredForeign.newBuilder()); | |
238 // mergeFrom() should not throw initialization error. | |
239 abstractMessageBuilder.mergeFrom(bytes).buildPartial(); | |
240 try { | |
241 abstractMessageBuilder.mergeFrom(bytes).build(); | |
242 fail(); | |
243 } catch (UninitializedMessageException ex) { | |
244 // pass | |
245 } | |
246 | |
247 // test DynamicMessage directly. | |
248 Message.Builder dynamicMessageBuilder = DynamicMessage.newBuilder( | |
249 TestRequiredForeign.getDescriptor()); | |
250 // mergeFrom() should not throw initialization error. | |
251 dynamicMessageBuilder.mergeFrom(bytes).buildPartial(); | |
252 try { | |
253 dynamicMessageBuilder.mergeFrom(bytes).build(); | |
254 fail(); | |
255 } catch (UninitializedMessageException ex) { | |
256 // pass | |
257 } | |
258 } | |
259 | |
260 public void testPackedSerialization() throws Exception { | |
261 Message abstractMessage = | |
262 new AbstractMessageWrapper(TestUtil.getPackedSet()); | |
263 | |
264 TestUtil.assertPackedFieldsSet( | |
265 TestPackedTypes.parseFrom(abstractMessage.toByteString())); | |
266 | |
267 assertEquals(TestUtil.getPackedSet().toByteString(), | |
268 abstractMessage.toByteString()); | |
269 } | |
270 | |
271 public void testPackedParsing() throws Exception { | |
272 AbstractMessageWrapper.Builder builder = | |
273 new AbstractMessageWrapper.Builder(TestPackedTypes.newBuilder()); | |
274 AbstractMessageWrapper message = | |
275 builder.mergeFrom(TestUtil.getPackedSet().toByteString()).build(); | |
276 TestUtil.assertPackedFieldsSet((TestPackedTypes) message.wrappedMessage); | |
277 } | |
278 | |
279 public void testUnpackedSerialization() throws Exception { | |
280 Message abstractMessage = | |
281 new AbstractMessageWrapper(TestUtil.getUnpackedSet()); | |
282 | |
283 TestUtil.assertUnpackedFieldsSet( | |
284 TestUnpackedTypes.parseFrom(abstractMessage.toByteString())); | |
285 | |
286 assertEquals(TestUtil.getUnpackedSet().toByteString(), | |
287 abstractMessage.toByteString()); | |
288 } | |
289 | |
290 public void testParsePackedToUnpacked() throws Exception { | |
291 AbstractMessageWrapper.Builder builder = | |
292 new AbstractMessageWrapper.Builder(TestUnpackedTypes.newBuilder()); | |
293 AbstractMessageWrapper message = | |
294 builder.mergeFrom(TestUtil.getPackedSet().toByteString()).build(); | |
295 TestUtil.assertUnpackedFieldsSet( | |
296 (TestUnpackedTypes) message.wrappedMessage); | |
297 } | |
298 | |
299 public void testParseUnpackedToPacked() throws Exception { | |
300 AbstractMessageWrapper.Builder builder = | |
301 new AbstractMessageWrapper.Builder(TestPackedTypes.newBuilder()); | |
302 AbstractMessageWrapper message = | |
303 builder.mergeFrom(TestUtil.getUnpackedSet().toByteString()).build(); | |
304 TestUtil.assertPackedFieldsSet((TestPackedTypes) message.wrappedMessage); | |
305 } | |
306 | |
307 public void testUnpackedParsing() throws Exception { | |
308 AbstractMessageWrapper.Builder builder = | |
309 new AbstractMessageWrapper.Builder(TestUnpackedTypes.newBuilder()); | |
310 AbstractMessageWrapper message = | |
311 builder.mergeFrom(TestUtil.getUnpackedSet().toByteString()).build(); | |
312 TestUtil.assertUnpackedFieldsSet( | |
313 (TestUnpackedTypes) message.wrappedMessage); | |
314 } | |
315 | |
316 public void testOptimizedForSize() throws Exception { | |
317 // We're mostly only checking that this class was compiled successfully. | |
318 TestOptimizedForSize message = | |
319 TestOptimizedForSize.newBuilder().setI(1).build(); | |
320 message = TestOptimizedForSize.parseFrom(message.toByteString()); | |
321 assertEquals(2, message.getSerializedSize()); | |
322 } | |
323 | |
324 // ----------------------------------------------------------------- | |
325 // Tests for isInitialized(). | |
326 | |
327 private static final TestRequired TEST_REQUIRED_UNINITIALIZED = | |
328 TestRequired.getDefaultInstance(); | |
329 private static final TestRequired TEST_REQUIRED_INITIALIZED = | |
330 TestRequired.newBuilder().setA(1).setB(2).setC(3).build(); | |
331 | |
332 public void testIsInitialized() throws Exception { | |
333 TestRequired.Builder builder = TestRequired.newBuilder(); | |
334 AbstractMessageWrapper.Builder abstractBuilder = | |
335 new AbstractMessageWrapper.Builder(builder); | |
336 | |
337 assertFalse(abstractBuilder.isInitialized()); | |
338 assertEquals("a, b, c", abstractBuilder.getInitializationErrorString()); | |
339 builder.setA(1); | |
340 assertFalse(abstractBuilder.isInitialized()); | |
341 assertEquals("b, c", abstractBuilder.getInitializationErrorString()); | |
342 builder.setB(1); | |
343 assertFalse(abstractBuilder.isInitialized()); | |
344 assertEquals("c", abstractBuilder.getInitializationErrorString()); | |
345 builder.setC(1); | |
346 assertTrue(abstractBuilder.isInitialized()); | |
347 assertEquals("", abstractBuilder.getInitializationErrorString()); | |
348 } | |
349 | |
350 public void testForeignIsInitialized() throws Exception { | |
351 TestRequiredForeign.Builder builder = TestRequiredForeign.newBuilder(); | |
352 AbstractMessageWrapper.Builder abstractBuilder = | |
353 new AbstractMessageWrapper.Builder(builder); | |
354 | |
355 assertTrue(abstractBuilder.isInitialized()); | |
356 assertEquals("", abstractBuilder.getInitializationErrorString()); | |
357 | |
358 builder.setOptionalMessage(TEST_REQUIRED_UNINITIALIZED); | |
359 assertFalse(abstractBuilder.isInitialized()); | |
360 assertEquals( | |
361 "optional_message.a, optional_message.b, optional_message.c", | |
362 abstractBuilder.getInitializationErrorString()); | |
363 | |
364 builder.setOptionalMessage(TEST_REQUIRED_INITIALIZED); | |
365 assertTrue(abstractBuilder.isInitialized()); | |
366 assertEquals("", abstractBuilder.getInitializationErrorString()); | |
367 | |
368 builder.addRepeatedMessage(TEST_REQUIRED_UNINITIALIZED); | |
369 assertFalse(abstractBuilder.isInitialized()); | |
370 assertEquals( | |
371 "repeated_message[0].a, repeated_message[0].b, repeated_message[0].c", | |
372 abstractBuilder.getInitializationErrorString()); | |
373 | |
374 builder.setRepeatedMessage(0, TEST_REQUIRED_INITIALIZED); | |
375 assertTrue(abstractBuilder.isInitialized()); | |
376 assertEquals("", abstractBuilder.getInitializationErrorString()); | |
377 } | |
378 | |
379 // ----------------------------------------------------------------- | |
380 // Tests for mergeFrom | |
381 | |
382 static final TestAllTypes MERGE_SOURCE = | |
383 TestAllTypes.newBuilder() | |
384 .setOptionalInt32(1) | |
385 .setOptionalString("foo") | |
386 .setOptionalForeignMessage(ForeignMessage.getDefaultInstance()) | |
387 .addRepeatedString("bar") | |
388 .build(); | |
389 | |
390 static final TestAllTypes MERGE_DEST = | |
391 TestAllTypes.newBuilder() | |
392 .setOptionalInt64(2) | |
393 .setOptionalString("baz") | |
394 .setOptionalForeignMessage(ForeignMessage.newBuilder().setC(3).build()) | |
395 .addRepeatedString("qux") | |
396 .build(); | |
397 | |
398 static final String MERGE_RESULT_TEXT = | |
399 "optional_int32: 1\n" + | |
400 "optional_int64: 2\n" + | |
401 "optional_string: \"foo\"\n" + | |
402 "optional_foreign_message {\n" + | |
403 " c: 3\n" + | |
404 "}\n" + | |
405 "repeated_string: \"qux\"\n" + | |
406 "repeated_string: \"bar\"\n"; | |
407 | |
408 public void testMergeFrom() throws Exception { | |
409 AbstractMessageWrapper result = | |
410 new AbstractMessageWrapper.Builder( | |
411 TestAllTypes.newBuilder(MERGE_DEST)) | |
412 .mergeFrom(MERGE_SOURCE).build(); | |
413 | |
414 assertEquals(MERGE_RESULT_TEXT, result.toString()); | |
415 } | |
416 | |
417 // ----------------------------------------------------------------- | |
418 // Tests for equals and hashCode | |
419 | |
420 public void testEqualsAndHashCode() throws Exception { | |
421 TestAllTypes a = TestUtil.getAllSet(); | |
422 TestAllTypes b = TestAllTypes.newBuilder().build(); | |
423 TestAllTypes c = TestAllTypes.newBuilder(b).addRepeatedString("x").build(); | |
424 TestAllTypes d = TestAllTypes.newBuilder(c).addRepeatedString("y").build(); | |
425 TestAllExtensions e = TestUtil.getAllExtensionsSet(); | |
426 TestAllExtensions f = TestAllExtensions.newBuilder(e) | |
427 .addExtension(UnittestProto.repeatedInt32Extension, 999).build(); | |
428 | |
429 checkEqualsIsConsistent(a); | |
430 checkEqualsIsConsistent(b); | |
431 checkEqualsIsConsistent(c); | |
432 checkEqualsIsConsistent(d); | |
433 checkEqualsIsConsistent(e); | |
434 checkEqualsIsConsistent(f); | |
435 | |
436 checkNotEqual(a, b); | |
437 checkNotEqual(a, c); | |
438 checkNotEqual(a, d); | |
439 checkNotEqual(a, e); | |
440 checkNotEqual(a, f); | |
441 | |
442 checkNotEqual(b, c); | |
443 checkNotEqual(b, d); | |
444 checkNotEqual(b, e); | |
445 checkNotEqual(b, f); | |
446 | |
447 checkNotEqual(c, d); | |
448 checkNotEqual(c, e); | |
449 checkNotEqual(c, f); | |
450 | |
451 checkNotEqual(d, e); | |
452 checkNotEqual(d, f); | |
453 | |
454 checkNotEqual(e, f); | |
455 | |
456 // Deserializing into the TestEmptyMessage such that every field | |
457 // is an {@link UnknownFieldSet.Field}. | |
458 UnittestProto.TestEmptyMessage eUnknownFields = | |
459 UnittestProto.TestEmptyMessage.parseFrom(e.toByteArray()); | |
460 UnittestProto.TestEmptyMessage fUnknownFields = | |
461 UnittestProto.TestEmptyMessage.parseFrom(f.toByteArray()); | |
462 checkNotEqual(eUnknownFields, fUnknownFields); | |
463 checkEqualsIsConsistent(eUnknownFields); | |
464 checkEqualsIsConsistent(fUnknownFields); | |
465 | |
466 // Subsequent reconstitutions should be identical | |
467 UnittestProto.TestEmptyMessage eUnknownFields2 = | |
468 UnittestProto.TestEmptyMessage.parseFrom(e.toByteArray()); | |
469 checkEqualsIsConsistent(eUnknownFields, eUnknownFields2); | |
470 } | |
471 | |
472 | |
473 /** | |
474 * Asserts that the given proto has symmetric equals and hashCode methods. | |
475 */ | |
476 private void checkEqualsIsConsistent(Message message) { | |
477 // Object should be equal to itself. | |
478 assertEquals(message, message); | |
479 | |
480 // Object should be equal to a dynamic copy of itself. | |
481 DynamicMessage dynamic = DynamicMessage.newBuilder(message).build(); | |
482 checkEqualsIsConsistent(message, dynamic); | |
483 } | |
484 | |
485 /** | |
486 * Asserts that the given protos are equal and have the same hash code. | |
487 */ | |
488 private void checkEqualsIsConsistent(Message message1, Message message2) { | |
489 assertEquals(message1, message2); | |
490 assertEquals(message2, message1); | |
491 assertEquals(message2.hashCode(), message1.hashCode()); | |
492 } | |
493 | |
494 /** | |
495 * Asserts that the given protos are not equal and have different hash codes. | |
496 * | |
497 * @warning It's valid for non-equal objects to have the same hash code, so | |
498 * this test is stricter than it needs to be. However, this should happen | |
499 * relatively rarely. | |
500 */ | |
501 private void checkNotEqual(Message m1, Message m2) { | |
502 String equalsError = String.format("%s should not be equal to %s", m1, m2); | |
503 assertFalse(equalsError, m1.equals(m2)); | |
504 assertFalse(equalsError, m2.equals(m1)); | |
505 | |
506 assertFalse( | |
507 String.format("%s should have a different hash code from %s", m1, m2), | |
508 m1.hashCode() == m2.hashCode()); | |
509 } | |
510 } | |
OLD | NEW |