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 }
--- EOF ---