From 151155c59dd6aac20c73e63756f9f099510f5163 Mon Sep 17 00:00:00 2001
From: Matthew Hague <matthew.hague@rhul.ac.uk>
Date: Tue, 10 Nov 2020 19:13:21 +0000
Subject: [PATCH] Add support for constructing arrays in the logs

Currently only for constructing "safe" arrays that you want to use as
arguments to student code -- no failure messages provided if
construction fails beyond the normal java reflection exceptions.

Use

    Character[] cs = new Character[] { 'A', 'B', 'C' };
    Object arr = codeTester.constructArray(char.class, cs);
    codeTester.construct("StudentClass", arr);

An example would be if you want a log

    char[] c0 = new char[] { 'A', 'B', 'C' };
    StudentClass sc0 = new StudentClass(c0);

which is better than

    StudentClass sc0 = new StudentClass([c@a82324234);

which it shows if you just passed "new char[] { 'A', 'B', 'C' }".
---
 .../uk/ac/rhul/cs/javatester/CodeTester.java  | 54 ++++++++++++++++++-
 1 file changed, 53 insertions(+), 1 deletion(-)

diff --git a/src/main/java/uk/ac/rhul/cs/javatester/CodeTester.java b/src/main/java/uk/ac/rhul/cs/javatester/CodeTester.java
index b7c6be0..53f9b16 100644
--- a/src/main/java/uk/ac/rhul/cs/javatester/CodeTester.java
+++ b/src/main/java/uk/ac/rhul/cs/javatester/CodeTester.java
@@ -5,6 +5,7 @@ import java.io.File;
 
 import java.lang.ClassLoader;
 import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Array;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
@@ -280,6 +281,26 @@ public class CodeTester {
         throw new BaseTester.FailedTestException(expandMsg(msg));
     }
 
+    /**
+     * Attempts to construct an array and log it
+     *
+     * @param klass the type of the array (can be e.g. int.class for
+     * primitives)
+     * @param values the values to fill array with
+     * @return the array
+     */
+    public Object constructArray(Class<?> klass, Object[] values) {
+        Object array = Array.newInstance(klass, values.length);
+        for (int i = 0; i < values.length; i++) {
+            Array.set(array, i, values[i]);
+        }
+
+        recordConstructArray(array, values);
+
+        return array;
+    }
+
+
     /**
      * Convenience for constructMsg with default messages
      */
@@ -655,6 +676,28 @@ public class CodeTester {
         setLastCall("new " + className + "(" + paramsString + ")");
     }
 
+    /**
+     * Record construction of an array with given values
+     *
+     * The values will be converted to (internal) variable names if they
+     * are object already constructed by this object. Else we rely on
+     * the toString method returning something useful.
+     */
+    private void recordConstructArray(Object array, Object[] values) {
+        String name = getNewVariableName(array);
+        String className = getClassString(array.getClass());
+        String valuesString = getParamsString(values);
+
+        if (getIsRecording()) {
+            lines.add(className + " " + name +
+                      " = new " + className +
+                      " { " + valuesString + " }");
+        }
+
+        setLastCall("new " + className + " { " + valuesString + " }");
+    }
+
+
     /**
      * Record a call method object with given params.
      */
@@ -748,6 +791,8 @@ public class CodeTester {
         if (name == null) {
             if (p instanceof String)
                 name = "\"" + p + "\"";
+            else if (p instanceof Character)
+                name = "'" + p + "'";
             else
                 name = p.toString();
         }
@@ -787,7 +832,14 @@ public class CodeTester {
             if (Character.isUpperCase(c))
                 sb.append(Character.toLowerCase(c));
         }
-        return sb.toString();
+
+        String base = sb.toString();
+
+        // primitive arrays will be empty this way
+        if (base.isEmpty())
+            return Character.toString(className.charAt(0));
+        else
+            return base;
     }
 
     /**
-- 
GitLab