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 }