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