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 }