This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-bcel.git


The following commit(s) were added to refs/heads/master by this push:
     new 97f2bec8 Support for Java 16 Record feature (#290)
97f2bec8 is described below

commit 97f2bec8c46e99aa6e29b636fd414f23320f4a9f
Author: Pablo Nicolas Diaz <pablonicolasd...@users.noreply.github.com>
AuthorDate: Thu Apr 4 09:05:43 2024 -0300

    Support for Java 16 Record feature (#290)
    
    * Support for java 17 Record feature
    
    * Fix whitespace
    
    * Fix whitespace
    
    * Fix whitespace
    
    * More whitespace fixing
    
    * Fix whitespace
    
    * Add Javadoc since tag
    
    * Fix Javadoc since tag
    
    * Remove trailing whitespace
    
    * Remove trailing whitespace
    
    * Remove trailing whitespace
    
    * Remove trailing whitespace
    
    * Remove trailing whitespace
    
    * added a missing new line
    
    * visitRecordComponent declared as a default method and documented.
    
    * removed visitRecordComponent, as is unneeded from previous commit
    
    * fixed version reference
    
    * fixed comments
    
    * javadoc added
    
    * fixed checkstyle errors
    
    * fixed checkstyle and pmd errors
    
    * fixed variable names and comments
    
    * adding tests to cover new functionality
    
    * fixed checkstyle and pmd errors
    
    * added new test cases
    
    * fixing javadoc and comments from pr
    
    * Order Javadoc tags
    
    * Javadoc
    
    * Javadoc
    
    * Javadoc
    
    * Javadoc
    
    * Javadoc
    
    * Fix whitespace
    
    * fixed typo
    
    * solved pr comments
    
    * constant pool check added
    
    * visitor fixed
    
    ---------
    
    Co-authored-by: nicolas <nicolas@nicoLaptop>
    Co-authored-by: Gary Gregory <garydgreg...@users.noreply.github.com>
---
 src/main/java/org/apache/bcel/Const.java           |   5 +-
 .../java/org/apache/bcel/classfile/Attribute.java  |   2 +
 .../apache/bcel/classfile/DescendingVisitor.java   |  16 +++
 .../java/org/apache/bcel/classfile/JavaClass.java  |  26 ++++
 .../java/org/apache/bcel/classfile/Record.java     | 149 +++++++++++++++++++++
 .../apache/bcel/classfile/RecordComponentInfo.java | 135 +++++++++++++++++++
 .../java/org/apache/bcel/classfile/Visitor.java    |  23 ++++
 .../verifier/statics/StringRepresentation.java     |  13 ++
 .../org/apache/bcel/CounterVisitorTestCase.java    |   5 +
 .../org/apache/bcel/classfile/RecordTestCase.java  | 121 +++++++++++++++++
 .../org/apache/bcel/visitors/CountingVisitor.java  |  19 +++
 src/test/resources/record/SimpleRecord.class       | Bin 0 -> 1506 bytes
 src/test/resources/record/SimpleRecord.java        |   5 +
 13 files changed, 517 insertions(+), 2 deletions(-)

diff --git a/src/main/java/org/apache/bcel/Const.java 
b/src/main/java/org/apache/bcel/Const.java
index edf739ae..fd554493 100644
--- a/src/main/java/org/apache/bcel/Const.java
+++ b/src/main/java/org/apache/bcel/Const.java
@@ -3073,12 +3073,13 @@ public final class Const {
     public static final byte ATTR_MODULE_MAIN_CLASS = 24;
     public static final byte ATTR_NEST_HOST = 25;
     public static final byte ATTR_NEST_MEMBERS = 26;
+    public static final byte ATTR_RECORD = 27;
 
-    public static final short KNOWN_ATTRIBUTES = 27; // count of attributes
+    public static final short KNOWN_ATTRIBUTES = 28; // count of attributes
     private static final String[] ATTRIBUTE_NAMES = {"SourceFile", 
"ConstantValue", "Code", "Exceptions", "LineNumberTable", "LocalVariableTable",
         "InnerClasses", "Synthetic", "Deprecated", "PMGClass", "Signature", 
"StackMap", "RuntimeVisibleAnnotations", "RuntimeInvisibleAnnotations",
         "RuntimeVisibleParameterAnnotations", 
"RuntimeInvisibleParameterAnnotations", "AnnotationDefault", 
"LocalVariableTypeTable", "EnclosingMethod",
-        "StackMapTable", "BootstrapMethods", "MethodParameters", "Module", 
"ModulePackages", "ModuleMainClass", "NestHost", "NestMembers"};
+        "StackMapTable", "BootstrapMethods", "MethodParameters", "Module", 
"ModulePackages", "ModuleMainClass", "NestHost", "NestMembers", "Record"};
     /**
      * Constants used in the StackMap attribute.
      */
diff --git a/src/main/java/org/apache/bcel/classfile/Attribute.java 
b/src/main/java/org/apache/bcel/classfile/Attribute.java
index 42510a9a..0dd994c2 100644
--- a/src/main/java/org/apache/bcel/classfile/Attribute.java
+++ b/src/main/java/org/apache/bcel/classfile/Attribute.java
@@ -191,6 +191,8 @@ public abstract class Attribute implements Cloneable, Node {
             return new NestHost(nameIndex, length, dataInput, constantPool);
         case Const.ATTR_NEST_MEMBERS:
             return new NestMembers(nameIndex, length, dataInput, constantPool);
+        case Const.ATTR_RECORD:
+            return new Record(nameIndex, length, dataInput, constantPool);
         default:
             // Never reached
             throw new IllegalStateException("Unrecognized attribute type tag 
parsed: " + tag);
diff --git a/src/main/java/org/apache/bcel/classfile/DescendingVisitor.java 
b/src/main/java/org/apache/bcel/classfile/DescendingVisitor.java
index 85de2a2a..880f1009 100644
--- a/src/main/java/org/apache/bcel/classfile/DescendingVisitor.java
+++ b/src/main/java/org/apache/bcel/classfile/DescendingVisitor.java
@@ -560,4 +560,20 @@ public class DescendingVisitor implements Visitor {
         attribute.accept(visitor);
         stack.pop();
     }
+
+    @Override
+    public void visitRecord(Record record) {
+        stack.push(record);
+        record.accept(visitor);
+        accept(record.getComponents());
+        stack.pop();
+    }
+
+    @Override
+    public void visitRecordComponent(RecordComponentInfo recordComponentInfo) {
+        stack.push(recordComponentInfo);
+        recordComponentInfo.accept(visitor);
+        stack.pop();
+    }
+
 }
diff --git a/src/main/java/org/apache/bcel/classfile/JavaClass.java 
b/src/main/java/org/apache/bcel/classfile/JavaClass.java
index de4ac4b1..fe967983 100644
--- a/src/main/java/org/apache/bcel/classfile/JavaClass.java
+++ b/src/main/java/org/apache/bcel/classfile/JavaClass.java
@@ -133,8 +133,10 @@ public class JavaClass extends AccessFlags implements 
Cloneable, Node, Comparabl
     private boolean isAnonymous;
 
     private boolean isNested;
+    private boolean isRecord;
 
     private boolean computedNestedTypeStatus;
+    private boolean computedRecord;
 
     /**
      * In cases where we go ahead and create something, use the default 
SyntheticRepository, because we don't know any
@@ -904,4 +906,28 @@ public class JavaClass extends AccessFlags implements 
Cloneable, Node, Comparabl
         }
         return buf.toString();
     }
+
+    /**
+     * Tests whether this class was declared as a record
+     *
+     * @return true if a record attribute is present, false otherwise.
+     * @since 6.9.0
+     */
+    public boolean isRecord() {
+        computeIsRecord();
+        return this.isRecord;
+    }
+
+    private void computeIsRecord() {
+        if (computedRecord) {
+            return;
+        }
+        for (final Attribute attribute : this.attributes) {
+            if (attribute instanceof Record) {
+                isRecord = true;
+                break;
+            }
+        }
+        this.computedRecord = true;
+    }
 }
diff --git a/src/main/java/org/apache/bcel/classfile/Record.java 
b/src/main/java/org/apache/bcel/classfile/Record.java
new file mode 100644
index 00000000..e459a0fe
--- /dev/null
+++ b/src/main/java/org/apache/bcel/classfile/Record.java
@@ -0,0 +1,149 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package org.apache.bcel.classfile;
+
+import java.io.DataInput;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+import org.apache.bcel.Const;
+import org.apache.bcel.util.Args;
+
+/**
+ * Extends {@link Attribute} and records the classes and
+ * interfaces that are authorized to claim membership in the nest hosted by the
+ * current class or interface. There may be at most one Record attribute in a
+ * ClassFile structure.
+ *
+ * @see Attribute
+ * @since 6.9.0
+ */
+public final class Record extends Attribute {
+
+    private static final RecordComponentInfo[] EMPTY_RCI_ARRAY = new 
RecordComponentInfo[] {};
+
+    private RecordComponentInfo[] components;
+
+    /**
+     * Constructs object from input stream.
+     *
+     * @param nameIndex    Index in constant pool
+     * @param length       Content length in bytes
+     * @param input        Input stream
+     * @param constantPool Array of constants
+     * @throws IOException if an I/O error occurs.
+     */
+    Record(final int nameIndex, final int length, final DataInput input, final 
ConstantPool constantPool)
+            throws IOException {
+        this(nameIndex, length, readComponents(input, constantPool), 
constantPool);
+    }
+
+    private static RecordComponentInfo[] readComponents(final DataInput input, 
final ConstantPool constantPool)
+            throws IOException {
+        final int classCount = input.readUnsignedShort();
+        final RecordComponentInfo[] components = new 
RecordComponentInfo[classCount];
+        for (int i = 0; i < classCount; i++) {
+            components[i] = new RecordComponentInfo(input, constantPool);
+        }
+        return components;
+    }
+
+    /**
+     * Constructs a new instance using components.
+     *
+     * @param nameIndex    Index in constant pool
+     * @param length       Content length in bytes
+     * @param classes      Array of Record Component Info elements
+     * @param constantPool Array of constants
+     */
+    public Record(final int nameIndex, final int length, final 
RecordComponentInfo[] classes,
+            final ConstantPool constantPool) {
+        super(Const.ATTR_RECORD, nameIndex, length, constantPool);
+        this.components = classes != null ? classes : EMPTY_RCI_ARRAY;
+        Args.requireU2(this.components.length, "attributes.length");
+    }
+
+    /**
+     * Called by objects that are traversing the nodes of the tree implicitly
+     * defined by the contents of a Java class. For example, the hierarchy of 
methods,
+     * fields, attributes, etc. spawns a tree of objects.
+     *
+     * @param v Visitor object
+     */
+    @Override
+    public void accept(final Visitor v) {
+        v.visitRecord(this);
+    }
+
+    /**
+     * Copies this instance and its components.
+     *
+     * @return a deep copy of this instance and its components.
+     */
+    @Override
+    public Attribute copy(final ConstantPool constantPool) {
+        final Record c = (Record) clone();
+        if (components.length > 0) {
+            c.components = components.clone();
+        }
+        c.setConstantPool(constantPool);
+        return c;
+    }
+
+    /**
+     * Dumps this instance into a file stream in binary format.
+     *
+     * @param file output stream.
+     * @throws IOException if an I/O error occurs.
+     */
+    @Override
+    public void dump(final DataOutputStream file) throws IOException {
+        super.dump(file);
+        file.writeShort(components.length);
+        for (final RecordComponentInfo component : components) {
+            component.dump(file);
+        }
+    }
+
+    /**
+     * Gets all the record components.
+     *
+     * @return array of Record Component Info elements.
+     */
+    public RecordComponentInfo[] getComponents() {
+        return components;
+    }
+
+    /**
+     * Converts this instance to a String suitable for debugging.
+     *
+     * @return String a String suitable for debugging.
+     */
+    @Override
+    public String toString() {
+        final StringBuilder buf = new StringBuilder();
+        buf.append("Record(");
+        buf.append(components.length);
+        buf.append("):\n");
+        for (final RecordComponentInfo component : components) {
+            buf.append("  ").append(component.toString()).append("\n");
+        }
+        return buf.substring(0, buf.length() - 1); // remove the last newline
+    }
+
+}
diff --git a/src/main/java/org/apache/bcel/classfile/RecordComponentInfo.java 
b/src/main/java/org/apache/bcel/classfile/RecordComponentInfo.java
new file mode 100644
index 00000000..bf489d0f
--- /dev/null
+++ b/src/main/java/org/apache/bcel/classfile/RecordComponentInfo.java
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.bcel.classfile;
+
+import java.io.DataInput;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+import org.apache.bcel.Const;
+
+/**
+ * Record component info from a record. Instances from this class maps
+ * every component from a given record.
+ *
+ * @see <a 
href="https://docs.oracle.com/javase/specs/jvms/se14/preview/specs/records-jvms.html#jvms-4.7.30";>
+ *      The Java Virtual Machine Specification, Java SE 14 Edition, Records 
(preview)</a>
+ * @since 6.9.0
+ */
+public class RecordComponentInfo implements Node {
+
+    private final int index;
+    private final int descriptorIndex;
+    private final Attribute[] attributes;
+    private final ConstantPool constantPool;
+
+    /**
+     * Constructs a new instance from an input stream.
+     *
+     * @param input        Input stream
+     * @param constantPool Array of constants
+     * @throws IOException if an I/O error occurs.
+     */
+    public RecordComponentInfo(final DataInput input, ConstantPool 
constantPool) throws IOException {
+        this.index = input.readUnsignedShort();
+        this.descriptorIndex = input.readUnsignedShort();
+        final int attributesCount = input.readUnsignedShort();
+        this.attributes = new Attribute[attributesCount];
+        for (int j = 0; j < attributesCount; j++) {
+            attributes[j] = Attribute.readAttribute(input, constantPool);
+        }
+        this.constantPool = constantPool;
+    }
+
+    /**
+     * Dumps contents into a file stream in binary format.
+     *
+     * @param file Output file stream
+     * @throws IOException if an I/O error occurs.
+     */
+    public void dump(DataOutputStream file) throws IOException {
+        file.writeShort(index);
+        file.writeShort(descriptorIndex);
+        file.writeShort(attributes.length);
+        for (final Attribute attribute : attributes) {
+            attribute.dump(file);
+        }
+    }
+
+    @Override
+    public void accept(Visitor v) {
+        v.visitRecordComponent(this);
+    }
+
+    /**
+     * Gets the name index.
+     *
+     * @return index in constant pool of this record component name.
+     */
+    public int getIndex() {
+        return index;
+    }
+
+    /**
+     * Gets the description index.
+     *
+     * @return index in constant pool of this record component descriptor.
+     */
+    public int getDescriptorIndex() {
+        return descriptorIndex;
+    }
+
+    /**
+     * Gets all attributes.
+     *
+     * @return all attributes.
+     */
+    public Attribute[] getAttributes() {
+        return attributes;
+    }
+
+    /**
+     * Gets the constant pool.
+     *
+     * @return Constant pool.
+     */
+    public ConstantPool getConstantPool() {
+        return constantPool;
+    }
+
+    /**
+     * Converts this instance to a String suitable for debugging.
+     *
+     * @return a String suitable for debugging.
+     */
+    @Override
+    public String toString() {
+        final StringBuilder buf = new StringBuilder();
+        buf.append("RecordComponentInfo(");
+        buf.append(constantPool.getConstantString(index, Const.CONSTANT_Utf8));
+        buf.append(",");
+        buf.append(constantPool.getConstantString(descriptorIndex, 
Const.CONSTANT_Utf8));
+        buf.append(",");
+        buf.append(attributes.length);
+        buf.append("):\n");
+        for (final Attribute attribute : attributes) {
+            buf.append("  ").append(attribute.toString()).append("\n");
+        }
+        return buf.substring(0, buf.length() - 1); // remove the last newline
+    }
+
+}
diff --git a/src/main/java/org/apache/bcel/classfile/Visitor.java 
b/src/main/java/org/apache/bcel/classfile/Visitor.java
index 4f4b5c21..6f739324 100644
--- a/src/main/java/org/apache/bcel/classfile/Visitor.java
+++ b/src/main/java/org/apache/bcel/classfile/Visitor.java
@@ -207,6 +207,17 @@ public interface Visitor {
         // empty
     }
 
+    /**
+     * Visits a {@link Record} object.
+     *
+     * @param obj Record to visit
+     * @since 6.9.0
+     */
+    default void visitRecord(final Record obj) {
+        // empty
+    }
+
+
     /**
      * @since 6.0
      */
@@ -227,6 +238,7 @@ public interface Visitor {
 
     /**
      * Visits a {@link StackMapType} object.
+     *
      * @param obj object to visit
      * @since 6.8.0
      */
@@ -237,4 +249,15 @@ public interface Visitor {
     void visitSynthetic(Synthetic obj);
 
     void visitUnknown(Unknown obj);
+
+    /**
+     * Visits a {@link RecordComponentInfo} object.
+     *
+     * @param record component to visit
+     * @since 6.9.0
+     */
+    default void visitRecordComponent(RecordComponentInfo record) {
+     // noop
+    }
+
 }
diff --git 
a/src/main/java/org/apache/bcel/verifier/statics/StringRepresentation.java 
b/src/main/java/org/apache/bcel/verifier/statics/StringRepresentation.java
index a8941746..af12ed5a 100644
--- a/src/main/java/org/apache/bcel/verifier/statics/StringRepresentation.java
+++ b/src/main/java/org/apache/bcel/verifier/statics/StringRepresentation.java
@@ -59,6 +59,8 @@ import org.apache.bcel.classfile.NestMembers;
 import org.apache.bcel.classfile.Node;
 import org.apache.bcel.classfile.ParameterAnnotationEntry;
 import org.apache.bcel.classfile.ParameterAnnotations;
+import org.apache.bcel.classfile.Record;
+import org.apache.bcel.classfile.RecordComponentInfo;
 import org.apache.bcel.classfile.Signature;
 import org.apache.bcel.classfile.SourceFile;
 import org.apache.bcel.classfile.StackMap;
@@ -429,4 +431,15 @@ public class StringRepresentation extends 
org.apache.bcel.classfile.EmptyVisitor
     public void visitUnknown(final Unknown obj) {
         tostring = toString(obj);
     }
+
+    @Override
+    public void visitRecord(Record obj) {
+        tostring = toString(obj);
+    }
+
+    @Override
+    public void visitRecordComponent(RecordComponentInfo obj) {
+        tostring = toString(obj);
+    }
+
 }
diff --git a/src/test/java/org/apache/bcel/CounterVisitorTestCase.java 
b/src/test/java/org/apache/bcel/CounterVisitorTestCase.java
index d1fcfa5e..224369ba 100644
--- a/src/test/java/org/apache/bcel/CounterVisitorTestCase.java
+++ b/src/test/java/org/apache/bcel/CounterVisitorTestCase.java
@@ -217,4 +217,9 @@ public class CounterVisitorTestCase extends 
AbstractCounterVisitorTestCase {
     public void testUnknownCount() {
         assertEquals(0, getVisitor().unknownCount, "unknownCount");
     }
+
+    @Test
+    public void testRecordCount() {
+        assertEquals(0, getVisitor().recordCount, "recordCount");
+    }
 }
diff --git a/src/test/java/org/apache/bcel/classfile/RecordTestCase.java 
b/src/test/java/org/apache/bcel/classfile/RecordTestCase.java
new file mode 100644
index 00000000..7a2e639d
--- /dev/null
+++ b/src/test/java/org/apache/bcel/classfile/RecordTestCase.java
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.bcel.classfile;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.apache.bcel.AbstractTestCase;
+import org.apache.bcel.util.SyntheticRepository;
+import org.apache.bcel.visitors.CountingVisitor;
+import org.junit.jupiter.api.Test;
+
+public class RecordTestCase extends AbstractTestCase {
+
+    /**
+     * A record type, once compiled, should result in a class file that is
+     * marked such that we can determine from the access flags
+     * (through BCEL) that it is in fact a record.
+     *
+     * @throws IOException
+     * @throws ClassFormatException
+     */
+    @Test
+    public void testRecordClassSaysItIs() throws ClassNotFoundException, 
ClassFormatException, IOException {
+        final JavaClass clazz = new 
ClassParser("src/test/resources/record/SimpleRecord.class").parse();
+        assertTrue(clazz.isRecord(), "Expected SimpleRecord class to say it 
was a record - but it didn't !");
+        final JavaClass simpleClazz = getTestJavaClass(PACKAGE_BASE_NAME + 
".data.SimpleClass");
+        assertFalse(simpleClazz.isRecord(), "Expected SimpleClass class to say 
it was not a record - but it didn't !");
+    }
+
+    /**
+     * A simple record with two simple fields, an integer and a String field, 
should
+     * show its content in its string representation.
+     *
+     * @throws ClassNotFoundException
+     * @throws ClassFormatException
+     * @throws IOException
+     */
+    @Test
+    public void testRecordToString() throws ClassNotFoundException, 
ClassFormatException, IOException {
+        final JavaClass clazz = new 
ClassParser("src/test/resources/record/SimpleRecord.class").parse();
+        final Attribute[] attributes = clazz.getAttributes();
+        final Record recordAttribute = (Record) 
findAttribute("Record",clazz)[0];
+        assertEquals(4, attributes.length);
+        assertEquals("SourceFile: SimpleRecord.java", 
attributes[0].toString());
+        assertEquals("Record(2):\n"
+                + "  RecordComponentInfo(aNumber,I,0):\n"
+                + "  RecordComponentInfo(aString,Ljava/lang/String;,1):\n"
+                + "  RuntimeVisibleAnnotations:\n"
+                + "  @Ljavax/annotation/Nonnull;"
+                ,recordAttribute.toString());
+        final RecordComponentInfo firstComponent = 
recordAttribute.getComponents()[0];
+        assertEquals(5, firstComponent.getIndex());
+        assertEquals(6, firstComponent.getDescriptorIndex());
+        assertEquals(0, firstComponent.getAttributes().length);
+        
assertEquals(recordAttribute.getConstantPool(),firstComponent.getConstantPool());
+        assertEquals("RecordComponentInfo(aNumber,I,0):", 
firstComponent.toString());
+    }
+
+
+    /**
+     * Check that we can save and load the attribute correctly.
+     */
+    @Test
+    public void testAttributeSerializtion() throws ClassNotFoundException, 
IOException {
+        final JavaClass clazz = new 
ClassParser("src/test/resources/record/SimpleRecord.class").parse();
+        final File tfile = createTestdataFile("SimpleRecord.class");
+        final Record recordAttribute = 
(Record)findAttribute("Record",clazz)[0];
+        clazz.dump(tfile);
+        // Read in the new version and check it is OK
+        final SyntheticRepository repos2 = createRepos(".");
+        final JavaClass clazzFromRepo = repos2.loadClass("SimpleRecord");
+        assertNotNull(clazzFromRepo); // Use the variable to avoid a warning
+        final Record recordAttributeFromRepo = 
(Record)findAttribute("Record",clazzFromRepo)[0];
+        assertEquals(recordAttribute.toString(), 
recordAttributeFromRepo.toString(), "Both attributes needs to be equal");
+        tfile.deleteOnExit();
+    }
+
+    /**
+     * Check that we can copy a attribute correctly.
+     */
+    @Test
+    public void recordsCanBeCopied() throws ClassNotFoundException, 
IOException {
+        final JavaClass clazz = new 
ClassParser("src/test/resources/record/SimpleRecord.class").parse();
+        final JavaClass copyClazz = clazz.copy();
+        assertEquals(clazz.toString(), copyClazz.toString(), "both records 
should have the same value");
+    }
+
+    /**
+     * Check that a record can be visited by our visitors
+     */
+    @Test
+    public void recordsCanBeVisited() throws ClassNotFoundException, 
IOException {
+        final JavaClass clazz = new 
ClassParser("src/test/resources/record/SimpleRecord.class").parse();
+        final CountingVisitor countVisitor = new CountingVisitor();
+        final DescendingVisitor desendingVisitor = new 
DescendingVisitor(clazz, countVisitor);
+        desendingVisitor.visit();
+        assertEquals(1,countVisitor.recordCount, "should count one record");
+        assertEquals(2,countVisitor.recordComponentCount, "should count two 
record components");
+    }
+
+}
diff --git a/src/test/java/org/apache/bcel/visitors/CountingVisitor.java 
b/src/test/java/org/apache/bcel/visitors/CountingVisitor.java
index 3522df26..93695479 100644
--- a/src/test/java/org/apache/bcel/visitors/CountingVisitor.java
+++ b/src/test/java/org/apache/bcel/visitors/CountingVisitor.java
@@ -68,6 +68,8 @@ import org.apache.bcel.classfile.NestHost;
 import org.apache.bcel.classfile.NestMembers;
 import org.apache.bcel.classfile.ParameterAnnotationEntry;
 import org.apache.bcel.classfile.ParameterAnnotations;
+import org.apache.bcel.classfile.Record;
+import org.apache.bcel.classfile.RecordComponentInfo;
 import org.apache.bcel.classfile.Signature;
 import org.apache.bcel.classfile.SourceFile;
 import org.apache.bcel.classfile.StackMap;
@@ -204,6 +206,13 @@ public class CountingVisitor implements Visitor {
 
     /** @since 6.4.0 */
     public int nestMembersCount;
+
+    /** @since 6.9.0 */
+    public int recordCount;
+
+    /** @since 6.9.0 */
+    public int recordComponentCount;
+
     // CHECKSTYLE:ON
 
     @Override
@@ -517,4 +526,14 @@ public class CountingVisitor implements Visitor {
     public void visitUnknown(final Unknown obj) {
         unknownCount++;
     }
+
+    @Override
+    public void visitRecord(Record v) {
+        recordCount++;
+    }
+
+    @Override
+    public void visitRecordComponent(RecordComponentInfo v) {
+        recordComponentCount++;
+    }
 }
diff --git a/src/test/resources/record/SimpleRecord.class 
b/src/test/resources/record/SimpleRecord.class
new file mode 100644
index 00000000..bf6b8119
Binary files /dev/null and b/src/test/resources/record/SimpleRecord.class differ
diff --git a/src/test/resources/record/SimpleRecord.java 
b/src/test/resources/record/SimpleRecord.java
new file mode 100644
index 00000000..10316b07
--- /dev/null
+++ b/src/test/resources/record/SimpleRecord.java
@@ -0,0 +1,5 @@
+import javax.annotation.Nonnull;
+
+public record SimpleRecord(int aNumber,@Nonnull String aString) {
+
+}

Reply via email to