1 /*
  2  * Copyright (c) 2017, 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  * CheckRecordMembers
 26  *
 27  * @test
 28  * @bug 8246774
 29  * @summary check that the accessors, equals, hashCode and toString methods
 30  *          work as expected
 31  * @library /tools/javac/lib
 32  * @modules jdk.compiler/com.sun.tools.javac.file
 33  *          jdk.compiler/com.sun.tools.javac.api
 34  *          jdk.compiler/com.sun.tools.javac.util
 35  * @build combo.ComboTestHelper
 36  * @run main CheckRecordMembers
 37  */
 38 
 39 import java.lang.reflect.Constructor;
 40 import java.lang.reflect.Field;
 41 import java.lang.reflect.Method;
 42 import java.net.URL;
 43 import java.net.URLClassLoader;
 44 import java.util.Arrays;
 45 import java.util.List;
 46 import java.util.Objects;
 47 import javax.tools.JavaFileObject;
 48 
 49 import com.sun.tools.javac.file.PathFileObject;
 50 import combo.ComboTask;
 51 
 52 public class CheckRecordMembers extends combo.ComboInstance<CheckRecordMembers> {
 53 
 54     enum FieldTypeKind implements combo.ComboParameter {
 55         BYTE("byte", byte.class,
 56              List.of(Byte.MIN_VALUE, (byte) -4, (byte) -1, (byte) 0, (byte) 1, (byte) 4, Byte.MAX_VALUE)),
 57         SHORT("short", short.class,
 58               List.of(Short.MIN_VALUE, (short) -4, (short) -1, (short) 0, (short) 1, (short) 4, Short.MAX_VALUE)),
 59         CHAR("char", char.class,
 60              List.of(Character.MIN_VALUE, 'a', 'A', 'z', (char) 0, Character.MAX_VALUE)),
 61         INT("int", int.class,
 62             List.of(Integer.MIN_VALUE, (int) -4, (int) -1, (int) 0, (int) 1, (int) 4, Integer.MAX_VALUE)),
 63         LONG("long", long.class,
 64              List.of(Long.MIN_VALUE, (long) -4, (long) -1, (long) 0, (long) 1, (long) 4, Long.MAX_VALUE)),
 65         FLOAT("float", float.class,
 66               List.of(Float.MIN_VALUE, Float.NaN, Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY, 0.0f, -0.0f, 1.0f, -1.0f, 2.0f, -2.0f, Float.MAX_VALUE)),
 67         DOUBLE("double", double.class,
 68                List.of(Double.MIN_VALUE, Double.NaN, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, 0.0d, -0.0d, 1.0d, -1.0d, 2.0d, -2.0d, Double.MAX_VALUE)),
 69         BOOLEAN("boolean", boolean.class,
 70                 List.of(true, false)),
 71         OBJECT("Object", Object.class,
 72                Arrays.asList(null, 3, "foo", new String[] {"a"})),
 73         STRING("String", String.class,
 74                Arrays.asList(null, "", "foo", "bar"));
 75 
 76         final String retTypeStr;
 77         final Class<?> clazz;
 78         final List<Object> dataValues;
 79 
 80         FieldTypeKind(String retTypeStr, Class<?> clazz, List<Object> values) {
 81             this.retTypeStr = retTypeStr;
 82             this.clazz = clazz;
 83             dataValues = values;
 84         }
 85 
 86         public String expand(String optParameter) {
 87             return retTypeStr;
 88         }
 89     }
 90 
 91     static final String sourceTemplate =
 92             "public record Data(#{FT0} f0, #{FT1} f1) { }";
 93 
 94     public static void main(String... args) throws Exception {
 95         new combo.ComboTestHelper<CheckRecordMembers>()
 96                 .withDimension("FT0", (x, t) -> { x.ft0 = t; }, FieldTypeKind.values())
 97                 .withDimension("FT1", (x, t) -> { x.ft1 = t; }, FieldTypeKind.values())
 98                 .run(CheckRecordMembers::new);
 99     }
100 
101     FieldTypeKind ft0, ft1;
102 
103     @Override
104     public void doWork() throws Throwable {
105         newCompilationTask()
106                 .withSourceFromTemplate("Data", sourceTemplate)
107                 .generate(this::check);
108     }
109 
110     void check(ComboTask.Result<Iterable<? extends JavaFileObject>> result) {
111         if (result.hasErrors() || result.hasWarnings())
112             fail("Compilation errors not expected: " + result.compilationInfo());
113 
114         List<Object> f0s = ft0.dataValues;
115         List<Object> f1s = ft1.dataValues;
116 
117         Iterable<? extends PathFileObject> pfoIt = (Iterable<? extends PathFileObject>) result.get();
118         PathFileObject pfo = pfoIt.iterator().next();
119         Class<?> clazz;
120         Constructor<?> ctor;
121         Method getterF0, getterF1, hashCodeMethod, equalsMethod, toStringMethod;
122         Field fieldF0, fieldF1;
123 
124         try {
125             URL[] urls = new URL[] {pfo.getPath().getParent().toUri().toURL()};
126             ClassLoader cl = new URLClassLoader(urls);
127             clazz = cl.loadClass("Data");
128 
129             ctor = clazz.getConstructor(ft0.clazz, ft1.clazz);
130             getterF0 = clazz.getMethod("f0");
131             getterF1 = clazz.getMethod("f1");
132             fieldF0 = clazz.getDeclaredField("f0");
133             fieldF1 = clazz.getDeclaredField("f1");
134             equalsMethod = clazz.getMethod("equals", Object.class);
135             hashCodeMethod = clazz.getMethod("hashCode");
136             toStringMethod = clazz.getMethod("toString");
137 
138             if (getterF0.getReturnType() != ft0.clazz
139                 || getterF1.getReturnType() != ft1.clazz
140                 || fieldF0.getType() != ft0.clazz
141                 || fieldF1.getType() != ft1.clazz)
142                 fail("Unexpected field or getter type: " + result.compilationInfo());
143 
144             for (Object f0 : f0s) {
145                 for (Object f1 : f1s) {
146                     // Create object
147                     Object datum = ctor.newInstance(f0, f1);
148 
149                     // Test getters
150                     Object actualF0 = getterF0.invoke(datum);
151                     Object actualF1 = getterF1.invoke(datum);
152                     if (!Objects.equals(f0, actualF0) || !Objects.equals(f1, actualF1))
153                         fail(String.format("Getters don't report back right values for %s %s/%s, %s %s/%s",
154                                            ft0.clazz, f0, actualF0,
155                                            ft1.clazz, f1, actualF1));
156 
157                     int hashCode = (int) hashCodeMethod.invoke(datum);
158                     int expectedHash = Objects.hash(f0, f1);
159                     // @@@ fail
160                     if (hashCode != expectedHash) {
161                         System.err.println(String.format("Hashcode not as expected: expected=%d, actual=%d",
162                                            expectedHash, hashCode));
163                     }
164 
165                     String toString = (String) toStringMethod.invoke(datum);
166                     String expectedToString = String.format("Data[f0=%s, f1=%s]", f0, f1);
167                     if (!toString.equals(expectedToString)) {
168                         fail(String.format("ToString not as expected: expected=%s, actual=%s",
169                                            expectedToString, toString));
170                     }
171 
172                     // Test equals
173                     for (Object f2 : f0s) {
174                         for (Object f3 : f1s) {
175                             Object other = ctor.newInstance(f2, f3);
176                             boolean isEqual = (boolean) equalsMethod.invoke(datum, other);
177                             boolean isEqualReverse = (boolean) equalsMethod.invoke(other, datum);
178                             boolean f0f2Equal = Objects.equals(f0, f2);
179                             boolean f1f3Equal = Objects.equals(f1, f3);
180                             if (ft0 == FieldTypeKind.FLOAT) {
181                                 f0f2Equal = Float.compare((float)f0, (float)f2) == 0;
182                             } else if (ft0 == FieldTypeKind.DOUBLE) {
183                                 f0f2Equal = Double.compare((double)f0, (double)f2) == 0;
184                             }
185                             if (ft1 == FieldTypeKind.FLOAT) {
186                                 f1f3Equal = Float.compare((float)f1, (float)f3) == 0;
187                             } else if (ft1 == FieldTypeKind.DOUBLE) {
188                                 f1f3Equal = Double.compare((double)f1, (double)f3) == 0;
189                             }
190                             boolean shouldEqual = f0f2Equal && f1f3Equal;
191                             // @@@ fail
192                             if (shouldEqual != isEqual)
193                                 System.err.println(String.format("Equals not as expected: %s %s/%s, %s %s/%s",
194                                                    ft0.clazz, f0, f2,
195                                                    ft1.clazz, f1, f3));
196                             if (isEqualReverse != isEqual)
197                                 fail(String.format("Equals not symmetric: %s %s/%s, %s %s/%s",
198                                                    ft0.clazz, f0, f2,
199                                                    ft1.clazz, f1, f3));
200 
201                         }
202                     }
203                 }
204             }
205         } catch (Throwable e) {
206             throw new AssertionError(e);
207         }
208     }
209 }