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.Objects;
  37 import java.util.stream.Stream;
  38 
  39 import org.testng.annotations.BeforeTest;
  40 import org.testng.annotations.DataProvider;
  41 import org.testng.annotations.Test;
  42 import static org.testng.Assert.*;
  43 
  44 public class ObjectMethods {
  45     static final int SALT = 1;
  46     static final Point P1 = Point.makePoint(1, 2);
  47     static final Point P2 = Point.makePoint(30, 40);
  48     static final Line LINE1 = Line.makeLine(1, 2, 3, 4);
  49     static final Line LINE2 = Line.makeLine(10, 20, 3, 4);
  50     static final MutablePath MUTABLE_PATH = MutablePath.makePath(10, 20, 30, 40);
  51     static final MixedValues MIXED_VALUES = new MixedValues(P1, LINE1, MUTABLE_PATH, "value");
  52     static final Value VALUE = new Value.Builder()
  53                                         .setChar('z')
  54                                         .setBoolean(false)
  55                                         .setByte((byte)0x1)
  56                                         .setShort((short)3)
  57                                         .setLong(4L)
  58                                         .setPoint(Point.makePoint(200, 200))
  59                                         .setNumber(Value.Number.intValue(10)).build();
  60 
  61     @DataProvider(name="equalsTests")
  62     Object[][] equalsTests() {
  63         return new Object[][]{
  64             { P1, P1, true},
  65             { P1, Point.makePoint(1, 2), true},
  66             { P1, P2, false},
  67             { P1, LINE1, false},
  68             { LINE1, Line.makeLine(1, 2, 3, 4), true},
  69             { LINE1, LINE2, false},
  70             { LINE1, LINE1, true},
  71             { VALUE, new Value.Builder()
  72                               .setChar('z')
  73                               .setBoolean(false)
  74                               .setByte((byte)0x1)
  75                               .setShort((short)3)
  76                               .setLong(4L)
  77                               .setPoint(Point.makePoint(200, 200))
  78                               .setNumber(Value.Number.intValue(10)).build(), true},
  79             { new Value.Builder().setNumber(new Value.IntNumber(10)).build(),
  80               new Value.Builder().setNumber(new Value.IntNumber(10)).build(), false},
  81             // reference classes containing fields of inline type
  82             { MUTABLE_PATH, MutablePath.makePath(10, 20, 30, 40), false},
  83             { MIXED_VALUES, MIXED_VALUES, true},
  84             { MIXED_VALUES, new MixedValues(P1, LINE1, MUTABLE_PATH, "value"), false},
  85             // uninitialized default value
  86             { MyValue1.default, MyValue1.default, true},
  87             { MyValue1.default, MyValue1.make(0,0, null), true},
  88             { MyValue1.make(10, 20, P1), MyValue1.make(10, 20, Point.makePoint(1,2)), true},
  89         };
  90     }
  91 
  92     @Test(dataProvider="equalsTests")
  93     public void testEquals(Object o1, Object o2, boolean expected) {
  94         assertTrue(o1.equals(o2) == expected);
  95     }
  96 
  97     @DataProvider(name="toStringTests")
  98     Object[][] toStringTests() {
  99         return new Object[][] {
 100             { Point.makePoint(100, 200), "[Point x=100 y=200]" },
 101             { Line.makeLine(1, 2, 3, 4), "[Line p1=[Point x=1 y=2] p2=[Point x=3 y=4]]"},
 102             { new Value.Builder()
 103                        .setChar('z')
 104                        .setBoolean(false)
 105                        .setByte((byte)0x1)
 106                        .setShort((short)3)
 107                        .setLong(4L)
 108                        .setPoint(Point.makePoint(200, 200))
 109                        .setNumber(Value.Number.intValue(10)).build(),
 110               "[Value char_v=z byte_v=1 boolean_v=false int_v=0 short_v=3 long_v=4 double_v=0.0 " +
 111               "float_v=0.0 number_v=[Value$IntValue i=10] point_v=[Point x=200 y=200] ref_v=null]" },
 112             { new Value.Builder()
 113                 .setReference(List.of("ref"))
 114                 .setNumber(new Value.IntNumber(99)).build(),
 115               "[Value char_v=\u0000 byte_v=0 boolean_v=false int_v=0 short_v=0 long_v=0 double_v=0.0 " +
 116               "float_v=0.0 number_v=99 point_v=[Point x=0 y=0] ref_v=[ref]]" },
 117             // enclosing instance field `this$0` should be filtered
 118             { MyValue1.default, "[ObjectMethods$MyValue1 p=[Point x=0 y=0] box=null]" },
 119             { MyValue1.make(0,0, null), "[ObjectMethods$MyValue1 p=[Point x=0 y=0] box=null]" },
 120             { MyValue1.make(0,0, P1), "[ObjectMethods$MyValue1 p=[Point x=0 y=0] box=[Point x=1 y=2]]" },
 121         };
 122     }
 123 
 124     @Test(dataProvider="toStringTests")
 125     public void testToString(Object o, String s) {
 126         assertTrue(o.toString().equals(s), o.toString());
 127     }
 128 
 129     @DataProvider(name="hashcodeTests")
 130     Object[][] hashcodeTests() {
 131         Value v = new Value.Builder()
 132                            .setChar('z')
 133                            .setBoolean(false)
 134                            .setByte((byte)0x1)
 135                            .setShort((short)3)
 136                            .setLong(4L)
 137                            .setFloat(1.2f)
 138                            .setDouble(0.5)
 139                            .setPoint(Point.makePoint(200, 200))
 140                            .setNumber(Value.Number.intValue(10))
 141                            .setReference(new Object()).build();
 142         // this is sensitive to the order of the returned fields from Class::getDeclaredFields
 143         return new Object[][]{
 144             { P1,                   hash(Point.class.asValueType(), 1, 2) },
 145             { LINE1,                hash(Line.class.asValueType(), Point.makePoint(1, 2), Point.makePoint(3, 4)) },
 146             { v,                    hash(hashCodeComponents(v))},
 147             { Point.makePoint(0,0), hash(Point.class.asValueType(), 0, 0) },
 148             { Point.default,        hash(Point.class.asValueType(), 0, 0) },
 149             { MyValue1.default,     hash(MyValue1.class.asValueType(), Point.default, null) },
 150             { MyValue1.make(0,0, null), hash(MyValue1.class.asValueType(), Point.makePoint(0,0), null) },
 151         };
 152     }
 153 
 154     @Test(dataProvider="hashcodeTests")
 155     public void testHashCode(Object o, int hash) {
 156         assertEquals(o.hashCode(), hash);
 157     }
 158 
 159     private static Object[] hashCodeComponents(Object o) {
 160         Class<?> type = o.getClass().asValueType();
 161         // filter static fields and synthetic fields
 162         Stream<Object> fields = Arrays.stream(type.getDeclaredFields())
 163             .filter(f -> !Modifier.isStatic(f.getModifiers()) && !f.isSynthetic())
 164             .map(f -> {
 165                 try {
 166                     return f.get(o);
 167                 } catch (IllegalAccessException e) {
 168                     throw new RuntimeException(e);
 169                 }
 170             });
 171         return Stream.concat(Stream.of(type), fields).toArray(Object[]::new);
 172     }
 173 
 174     private static int hash(Object... values) {
 175         int hc = SALT;
 176         for (Object o : values) {
 177             hc = 31 * hc + (o != null ? o.hashCode() : 0);
 178         }
 179         return hc;
 180     }
 181 
 182     static inline class MyValue1 {
 183         Point p = Point.default;
 184         Point? box = Point.default;
 185 
 186         static MyValue1 make(int x, int y, Point? box) {
 187             MyValue1 v = MyValue1.default;
 188             v = __WithField(v.p, Point.makePoint(x, y));
 189             v = __WithField(v.box, box);
 190             return v;
 191         }
 192     }
 193 }