1 /*
   2  * Copyright (c) 2017, 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.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 /**
  27  * DataClassTest
  28  *
  29  * @test
  30  * @library /tools/javac/lib
  31  * @modules jdk.compiler/com.sun.tools.javac.file
  32  *          jdk.compiler/com.sun.tools.javac.api
  33  *          jdk.compiler/com.sun.tools.javac.util
  34  * @build combo.ComboTestHelper
  35 
  36  * @run main DataClassTest
  37  */
  38 
  39 import java.lang.reflect.AccessibleObject;
  40 import java.lang.reflect.Constructor;
  41 import java.lang.reflect.Field;
  42 import java.lang.reflect.InvocationTargetException;
  43 import java.lang.reflect.Method;
  44 import java.net.URL;
  45 import java.net.URLClassLoader;
  46 import java.util.Arrays;
  47 import java.util.List;
  48 import java.util.Map;
  49 import java.util.Objects;
  50 import javax.tools.JavaFileObject;
  51 
  52 import com.sun.tools.javac.file.PathFileObject;
  53 import combo.ComboTask;
  54 
  55 public class DataClassTest extends combo.ComboInstance<DataClassTest> {
  56 
  57     enum FieldTypeKind implements combo.ComboParameter {
  58         BYTE("byte", byte.class),
  59         SHORT("short", short.class),
  60         CHAR("char", char.class),
  61         INT("int", int.class),
  62         LONG("long", long.class),
  63         FLOAT("float", float.class),
  64         DOUBLE("double", double.class),
  65         BOOLEAN("boolean", boolean.class),
  66         OBJECT("Object", Object.class),
  67         STRING("String", String.class);
  68 
  69         String retTypeStr;
  70         Class<?> clazz;
  71 
  72         FieldTypeKind(String retTypeStr, Class<?> clazz) {
  73             this.retTypeStr = retTypeStr;
  74             this.clazz = clazz;
  75         }
  76 
  77         public String expand(String optParameter) {
  78             return retTypeStr;
  79         }
  80     }
  81 
  82     static final Map<FieldTypeKind, List<Object>> dataValues
  83             = Map.ofEntries(
  84             Map.entry(FieldTypeKind.BYTE, List.of(Byte.MIN_VALUE, (byte) -4, (byte) -1, (byte) 0, (byte) 1, (byte) 4, Byte.MAX_VALUE)),
  85             Map.entry(FieldTypeKind.SHORT, List.of(Short.MIN_VALUE, (short) -4, (short) -1, (short) 0, (short) 1, (short) 4, Short.MAX_VALUE)),
  86             Map.entry(FieldTypeKind.CHAR, List.of(Character.MIN_VALUE, 'a', 'A', 'z', (char) 0, Character.MAX_VALUE)),
  87             Map.entry(FieldTypeKind.INT, List.of(Integer.MIN_VALUE, (int) -4, (int) -1, (int) 0, (int) 1, (int) 4, Integer.MAX_VALUE)),
  88             Map.entry(FieldTypeKind.LONG, List.of(Long.MIN_VALUE, (long) -4, (long) -1, (long) 0, (long) 1, (long) 4, Long.MAX_VALUE)),
  89             Map.entry(FieldTypeKind.FLOAT, 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)),
  90             Map.entry(FieldTypeKind.DOUBLE, 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)),
  91             Map.entry(FieldTypeKind.BOOLEAN, List.of(true, false)),
  92             Map.entry(FieldTypeKind.OBJECT, Arrays.asList(null, 3, "foo", new String[] {"a"})),
  93             Map.entry(FieldTypeKind.STRING, Arrays.asList(null, "", "foo", "bar"))
  94     );
  95 
  96     static final String sourceTemplate =
  97             "record Data(#{FT[0]} f0, #{FT[1]} f1) { }";
  98 
  99     public static void main(String... args) throws Exception {
 100         new combo.ComboTestHelper<DataClassTest>()
 101                 .withArrayDimension("FT", (x, t, index) -> {
 102                     x.fieldType[index] = t;
 103                 }, 2, FieldTypeKind.values())
 104                 .run(DataClassTest::new);
 105     }
 106 
 107     FieldTypeKind[] fieldType = new FieldTypeKind[2];
 108 
 109     @Override
 110     public void doWork() throws Throwable {
 111         newCompilationTask()
 112                 .withSourceFromTemplate(sourceTemplate)
 113                 .generate(this::check);
 114     }
 115 
 116     void check(ComboTask.Result<Iterable<? extends JavaFileObject>> result) {
 117         List<Object> f0s = dataValues.get(fieldType[0]);
 118         List<Object> f1s = dataValues.get(fieldType[1]);
 119 
 120         if (result.hasErrors() || result.hasWarnings())
 121             fail("Compilation errors not expected: " + result.compilationInfo());
 122 
 123         Iterable<? extends PathFileObject> pfoIt = (Iterable<? extends PathFileObject>) result.get();
 124         PathFileObject pfo = pfoIt.iterator().next();
 125         Class<?> clazz;
 126         Constructor<?> ctor;
 127         Method getterF0, getterF1, hashCodeMethod, equalsMethod, toStringMethod;
 128         Field fieldF0, fieldF1;
 129 
 130         try {
 131             URL[] urls = new URL[] {pfo.getPath().getParent().toUri().toURL()};
 132             ClassLoader cl = new URLClassLoader(urls);
 133             clazz = cl.loadClass("Data");
 134 
 135             ctor = clazz.getConstructor(fieldType[0].clazz, fieldType[1].clazz);
 136             getterF0 = clazz.getMethod("f0");
 137             getterF1 = clazz.getMethod("f1");
 138             fieldF0 = clazz.getDeclaredField("f0");
 139             fieldF1 = clazz.getDeclaredField("f1");
 140             equalsMethod = clazz.getMethod("equals", Object.class);
 141             hashCodeMethod = clazz.getMethod("hashCode");
 142             toStringMethod = clazz.getMethod("toString");
 143 
 144             if (getterF0.getReturnType() != fieldType[0].clazz
 145                 || getterF1.getReturnType() != fieldType[1].clazz
 146                 || fieldF0.getType() != fieldType[0].clazz
 147                 || fieldF1.getType() != fieldType[1].clazz)
 148                 fail("Unexpected field or getter type: " + result.compilationInfo());
 149 
 150             for (AccessibleObject o : List.of(ctor, getterF0, getterF1, equalsMethod, hashCodeMethod, toStringMethod)) {
 151                 // @@@ Why do we need this?
 152                 o.setAccessible(true);
 153             }
 154 
 155             for (Object f0 : f0s) {
 156                 for (Object f1 : f1s) {
 157                     // Create object
 158                     Object datum = ctor.newInstance(f0, f1);
 159 
 160                     // Test getters
 161                     Object actualF0 = getterF0.invoke(datum);
 162                     Object actualF1 = getterF1.invoke(datum);
 163                     if (!Objects.equals(f0, actualF0) || !Objects.equals(f1, actualF1))
 164                         fail(String.format("Getters don't report back right values for %s %s/%s, %s %s/%s",
 165                                            fieldType[0].clazz, f0, actualF0,
 166                                            fieldType[1].clazz, f1, actualF1));
 167 
 168                     int hashCode = (int) hashCodeMethod.invoke(datum);
 169                     int expectedHash = Objects.hash(f0, f1);
 170                     // @@@ fail
 171                     if (hashCode != expectedHash) {
 172                         System.err.println(String.format("Hashcode not as expected: expected=%d, actual=%d",
 173                                            expectedHash, hashCode));
 174                     }
 175 
 176                     String toString = (String) toStringMethod.invoke(datum);
 177                     String expectedToString = String.format("Data[f0=%s, f1=%s]", f0, f1);
 178                     if (!toString.equals(expectedToString)) {
 179                         fail(String.format("ToString not as expected: expected=%s, actual=%s",
 180                                            expectedToString, toString));
 181                     }
 182 
 183                     // Test equals
 184                     for (Object f2 : f0s) {
 185                         for (Object f3 : f1s) {
 186                             Object other = ctor.newInstance(f2, f3);
 187                             boolean isEqual = (boolean) equalsMethod.invoke(datum, other);
 188                             boolean isEqualReverse = (boolean) equalsMethod.invoke(other, datum);
 189                             boolean f0f2Equal = Objects.equals(f0, f2);
 190                             boolean f1f3Equal = Objects.equals(f1, f3);
 191                             if (fieldType[0] == FieldTypeKind.FLOAT) {
 192                                 f0f2Equal = Float.compare((float)f0, (float)f2) == 0;
 193                             } else if (fieldType[0] == FieldTypeKind.DOUBLE) {
 194                                 f0f2Equal = Double.compare((double)f0, (double)f2) == 0;
 195                             }
 196                             if (fieldType[1] == FieldTypeKind.FLOAT) {
 197                                 f1f3Equal = Float.compare((float)f1, (float)f3) == 0;
 198                             } else if (fieldType[1] == FieldTypeKind.DOUBLE) {
 199                                 f1f3Equal = Double.compare((double)f1, (double)f3) == 0;
 200                             }
 201                             boolean shouldEqual = f0f2Equal && f1f3Equal;
 202                             // @@@ fail
 203                             if (shouldEqual != isEqual)
 204                                 System.err.println(String.format("Equals not as expected: %s %s/%s, %s %s/%s",
 205                                                    fieldType[0].clazz, f0, f2,
 206                                                    fieldType[1].clazz, f1, f3));
 207                             if (isEqualReverse != isEqual)
 208                                 fail(String.format("Equals not symmetric: %s %s/%s, %s %s/%s",
 209                                                    fieldType[0].clazz, f0, f2,
 210                                                    fieldType[1].clazz, f1, f3));
 211 
 212                         }
 213                     }
 214                 }
 215             }
 216         } catch (Throwable e) {
 217             throw new AssertionError(e);
 218         }
 219     }
 220 }