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