1 /*
   2  * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 
  25 /*
  26  * @test
  27  * @summary test Object methods on inline types
  28  * @compile -XDallowWithFieldOperator ObjectMethods.java
  29  * @run testng/othervm -XX:+EnableValhalla -Dvalue.bsm.salt=1 ObjectMethods
  30  * @run testng/othervm -XX:+EnableValhalla -Dvalue.bsm.salt=1 -XX:ValueFieldMaxFlatSize=0 ObjectMethods
  31  */
  32 
  33 import java.lang.reflect.Modifier;
  34 import java.util.Arrays;
  35 import java.util.List;
  36 import java.util.stream.Stream;
  37 
  38 import org.testng.annotations.BeforeTest;
  39 import org.testng.annotations.DataProvider;
  40 import org.testng.annotations.Test;
  41 import static org.testng.Assert.*;
  42 
  43 public class ObjectMethods {
  44     static final int SALT = 1;
  45     static final Point P1 = Point.makePoint(1, 2);
  46     static final Point P2 = Point.makePoint(30, 40);
  47     static final Line LINE1 = Line.makeLine(1, 2, 3, 4);
  48     static final Line LINE2 = Line.makeLine(10, 20, 3, 4);
  49     static final MutablePath MUTABLE_PATH = MutablePath.makePath(10, 20, 30, 40);
  50     static final MixedValues MIXED_VALUES = new MixedValues(P1, LINE1, MUTABLE_PATH, "value");
  51     static final Value VALUE = new Value.Builder()
  52                                         .setChar('z')
  53                                         .setBoolean(false)
  54                                         .setByte((byte)0x1)
  55                                         .setShort((short)3)
  56                                         .setLong(4L)
  57                                         .setPoint(Point.makePoint(200, 200))
  58                                         .setNumber(Value.Number.intValue(10)).build();
  59 
  60     @DataProvider(name="equalsTests")
  61     Object[][] equalsTests() {
  62         return new Object[][]{
  63             { P1, P1, true},
  64             { P1, Point.makePoint(1, 2), true},
  65             { P1, P2, false},
  66             { P1, LINE1, false},
  67             { LINE1, Line.makeLine(1, 2, 3, 4), true},
  68             { LINE1, LINE2, false},
  69             { LINE1, LINE1, true},
  70             { VALUE, new Value.Builder()
  71                               .setChar('z')
  72                               .setBoolean(false)
  73                               .setByte((byte)0x1)
  74                               .setShort((short)3)
  75                               .setLong(4L)
  76                               .setPoint(Point.makePoint(200, 200))
  77                               .setNumber(Value.Number.intValue(10)).build(), true},
  78             { new Value.Builder().setNumber(new Value.IntNumber(10)).build(),
  79               new Value.Builder().setNumber(new Value.IntNumber(10)).build(), false},
  80             // reference classes containing fields of inline type
  81             { MUTABLE_PATH, MutablePath.makePath(10, 20, 30, 40), false},
  82             { MIXED_VALUES, MIXED_VALUES, true},
  83             { MIXED_VALUES, new MixedValues(P1, LINE1, MUTABLE_PATH, "value"), false},
  84             // uninitialized default value
  85             { MyValue1.default, MyValue1.default, true},
  86             { MyValue1.default, MyValue1.make(0,0, null), true},
  87             { MyValue1.make(10, 20, P1), MyValue1.make(10, 20, Point.makePoint(1,2)), true},
  88         };
  89     }
  90 
  91     @Test(dataProvider="equalsTests")
  92     public void testEquals(Object o1, Object o2, boolean expected) {
  93         assertTrue(o1.equals(o2) == expected);
  94     }
  95 
  96     @DataProvider(name="toStringTests")
  97     Object[][] toStringTests() {
  98         return new Object[][] {
  99             { Point.makePoint(100, 200), "[Point x=100 y=200]" },
 100             { Line.makeLine(1, 2, 3, 4), "[Line p1=[Point x=1 y=2] p2=[Point x=3 y=4]]"},
 101             { new Value.Builder()
 102                        .setChar('z')
 103                        .setBoolean(false)
 104                        .setByte((byte)0x1)
 105                        .setShort((short)3)
 106                        .setLong(4L)
 107                        .setPoint(Point.makePoint(200, 200))
 108                        .setNumber(Value.Number.intValue(10)).build(),
 109               "[Value char_v=z byte_v=1 boolean_v=false int_v=0 short_v=3 long_v=4 double_v=0.0 " +
 110               "float_v=0.0 number_v=[Value$IntValue i=10] point_v=[Point x=200 y=200] ref_v=null]" },
 111             { new Value.Builder()
 112                 .setReference(List.of("ref"))
 113                 .setNumber(new Value.IntNumber(99)).build(),
 114               "[Value char_v=\u0000 byte_v=0 boolean_v=false int_v=0 short_v=0 long_v=0 double_v=0.0 " +
 115               "float_v=0.0 number_v=99 point_v=[Point x=0 y=0] ref_v=[ref]]" },
 116             // enclosing instance field `this$0` should be filtered
 117             { MyValue1.default, "[ObjectMethods$MyValue1 p=[Point x=0 y=0] box=null]" },
 118             { MyValue1.make(0,0, null), "[ObjectMethods$MyValue1 p=[Point x=0 y=0] box=null]" },
 119             { MyValue1.make(0,0, P1), "[ObjectMethods$MyValue1 p=[Point x=0 y=0] box=[Point x=1 y=2]]" },
 120         };
 121     }
 122 
 123     @Test(dataProvider="toStringTests")
 124     public void testToString(Object o, String s) {
 125         assertTrue(o.toString().equals(s), o.toString());
 126     }
 127 
 128     @DataProvider(name="hashcodeTests")
 129     Object[][] hashcodeTests() {
 130         Value v = new Value.Builder()
 131                            .setChar('z')
 132                            .setBoolean(false)
 133                            .setByte((byte)0x1)
 134                            .setShort((short)3)
 135                            .setLong(4L)
 136                            .setFloat(1.2f)
 137                            .setDouble(0.5)
 138                            .setPoint(Point.makePoint(200, 200))
 139                            .setNumber(Value.Number.intValue(10))
 140                            .setReference(new Object()).build();
 141         // this is sensitive to the order of the returned fields from Class::getDeclaredFields
 142         return new Object[][]{
 143             { P1,                   hash(Point.class, 1, 2) },
 144             { LINE1,                hash(Line.class, Point.makePoint(1, 2), Point.makePoint(3, 4)) },
 145             { v,                    hash(hashCodeComponents(v))},
 146             { Point.makePoint(0,0), hash(Point.class, 0, 0) },
 147             { Point.default,        hash(Point.class, 0, 0) },
 148             { MyValue1.default,     hash(MyValue1.class, Point.default, null) },
 149             { MyValue1.make(0,0, null), hash(MyValue1.class, Point.makePoint(0,0), null) },
 150         };
 151     }
 152 
 153     @Test(dataProvider="hashcodeTests")
 154     public void testHashCode(Object o, int hash) {
 155         assertEquals(o.hashCode(), hash);
 156     }
 157 
 158     private static Object[] hashCodeComponents(Object o) {
 159         Class<?> type = o.getClass();
 160         // filter static fields and synthetic fields
 161         Stream<Object> fields = Arrays.stream(type.getDeclaredFields())
 162             .filter(f -> !Modifier.isStatic(f.getModifiers()) && !f.isSynthetic())
 163             .map(f -> {
 164                 try {
 165                     return f.get(o);
 166                 } catch (IllegalAccessException e) {
 167                     throw new RuntimeException(e);
 168                 }
 169             });
 170         return Stream.concat(Stream.of(type), fields).toArray(Object[]::new);
 171     }
 172 
 173     private static int hash(Object... values) {
 174         int hc = SALT;
 175         for (Object o : values) {
 176             hc = 31 * hc + (o != null ? o.hashCode() : 0);
 177         }
 178         return hc;
 179     }
 180 
 181     static inline class MyValue1 {
 182         Point p = Point.default;
 183         Point? box = null;
 184 
 185         static MyValue1 make(int x, int y, Point? box) {
 186             MyValue1 v = MyValue1.default;
 187             v = __WithField(v.p, Point.makePoint(x, y));
 188             v = __WithField(v.box, box);
 189             return v;
 190         }
 191     }
 192 }