mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-26 22:34:27 +02:00
8225054: Compiler implementation for records
8225052: javax.lang.model support for records 8225053: Preview APIs support for records 8225055: Javadoc for records 8226314: com.sun.source support for records 8227113: Specification for java.lang.Record 8233526: JVM support for records Implement records in the compiler and the JVM, including serialization, reflection and APIs support Co-authored-by: Brian Goetz <brian.goetz@oracle.com> Co-authored-by: Maurizio Cimadamore <maurizio.cimadamore@oracle.com> Co-authored-by: Harold Seigel <harold.seigel@oracle.com> Co-authored-by: Joe Darcy <joe.darcy@oracle.com> Co-authored-by: Jonathan Gibbons <jonathan.gibbons@oracle.com> Co-authored-by: Chris Hegarty <chris.hegarty@oracle.com> Co-authored-by: Jan Lahoda <jan.lahoda@oracle.com> Reviewed-by: mcimadamore, briangoetz, alanb, darcy, chegar, jrose, jlahoda, coleenp, dholmes, lfoltan, mchung, sadayapalam, hannesw, sspitsyn
This commit is contained in:
parent
0a375cfa2d
commit
827e5e3226
351 changed files with 24958 additions and 6395 deletions
|
@ -26,7 +26,9 @@
|
|||
package java.io;
|
||||
|
||||
import java.io.ObjectStreamClass.WeakClassKey;
|
||||
import java.io.ObjectStreamClass.RecordSupport;
|
||||
import java.lang.System.Logger;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.ref.ReferenceQueue;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
@ -218,6 +220,39 @@ import sun.reflect.misc.ReflectUtil;
|
|||
* Similarly, any serialPersistentFields or serialVersionUID field declarations
|
||||
* are also ignored--all enum types have a fixed serialVersionUID of 0L.
|
||||
*
|
||||
* @implSpec
|
||||
* <a id="record-serialization"></a>
|
||||
* Records are serialized differently than ordinary serializable or externalizable
|
||||
* objects. The serialized form of a record object is a sequence of values derived
|
||||
* from the record components. The stream format of a record object is the same as
|
||||
* that of an ordinary object in the stream. During deserialization, if the local
|
||||
* class equivalent of the specified stream class descriptor is a record class,
|
||||
* then first the stream fields are read and reconstructed to serve as the record's
|
||||
* component values; and second, a record object is created by invoking the
|
||||
* record's <i>canonical</i> constructor with the component values as arguments (or the
|
||||
* default value for component's type if a component value is absent from the
|
||||
* stream).
|
||||
* Like other serializable or externalizable objects, record objects can function
|
||||
* as the target of back references appearing subsequently in the serialization
|
||||
* stream. However, a cycle in the graph where the record object is referred to,
|
||||
* either directly or transitively, by one of its components, is not preserved.
|
||||
* The record components are deserialized prior to the invocation of the record
|
||||
* constructor, hence this limitation (see
|
||||
* <a href="{@docRoot}/../specs/serialization/serial-arch.html#cyclic-references">
|
||||
* [Section 1.14, "Circular References"</a> for additional information).
|
||||
* The process by which record objects are serialized or externalized cannot be
|
||||
* customized; any class-specific writeObject, readObject, readObjectNoData,
|
||||
* writeExternal, and readExternal methods defined by record classes are
|
||||
* ignored during serialization and deserialization. However, a substitute object
|
||||
* to be serialized or a designate replacement may be specified, by the
|
||||
* writeReplace and readResolve methods, respectively. Any
|
||||
* serialPersistentFields field declaration is ignored. Documenting serializable
|
||||
* fields and data for record classes is unnecessary, since there is no variation
|
||||
* in the serial form, other than whether a substitute or replacement object is
|
||||
* used. The serialVersionUID of a record class is 0L unless explicitly
|
||||
* declared. The requirement for matching serialVersionUID values is waived for
|
||||
* record classes.
|
||||
*
|
||||
* @author Mike Warres
|
||||
* @author Roger Riggs
|
||||
* @see java.io.DataInput
|
||||
|
@ -2047,6 +2082,11 @@ public class ObjectInputStream
|
|||
return result;
|
||||
}
|
||||
|
||||
@SuppressWarnings("preview")
|
||||
private static boolean isRecord(Class<?> cls) {
|
||||
return cls.isRecord();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads and returns "ordinary" (i.e., not a String, Class,
|
||||
* ObjectStreamClass, array, or enum constant) object, or null if object's
|
||||
|
@ -2085,7 +2125,12 @@ public class ObjectInputStream
|
|||
handles.markException(passHandle, resolveEx);
|
||||
}
|
||||
|
||||
if (desc.isExternalizable()) {
|
||||
final boolean isRecord = cl != null && isRecord(cl) ? true : false;
|
||||
if (isRecord) {
|
||||
assert obj == null;
|
||||
obj = readRecord(desc);
|
||||
handles.setObject(passHandle, obj);
|
||||
} else if (desc.isExternalizable()) {
|
||||
readExternalData((Externalizable) obj, desc);
|
||||
} else {
|
||||
readSerialData(obj, desc);
|
||||
|
@ -2171,6 +2216,43 @@ public class ObjectInputStream
|
|||
*/
|
||||
}
|
||||
|
||||
/** Reads a record. */
|
||||
private Object readRecord(ObjectStreamClass desc) throws IOException {
|
||||
ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
|
||||
if (slots.length != 1) {
|
||||
// skip any superclass stream field values
|
||||
for (int i = 0; i < slots.length-1; i++) {
|
||||
ObjectStreamClass slotDesc = slots[i].desc;
|
||||
if (slots[i].hasData) {
|
||||
defaultReadFields(null, slotDesc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FieldValues fieldValues = defaultReadFields(null, desc);
|
||||
|
||||
// retrieve the canonical constructor
|
||||
MethodHandle ctrMH = desc.getRecordConstructor();
|
||||
|
||||
// bind the stream field values
|
||||
ctrMH = RecordSupport.bindCtrValues(ctrMH, desc, fieldValues);
|
||||
|
||||
try {
|
||||
return ctrMH.invoke();
|
||||
} catch (Exception e) {
|
||||
InvalidObjectException ioe = new InvalidObjectException(e.getMessage());
|
||||
ioe.initCause(e);
|
||||
throw ioe;
|
||||
} catch (Error e) {
|
||||
throw e;
|
||||
} catch (Throwable t) {
|
||||
ObjectStreamException ose = new InvalidObjectException(
|
||||
"ReflectiveOperationException during deserialization");
|
||||
ose.initCause(t);
|
||||
throw ose;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads (or attempts to skip, if obj is null or is tagged with a
|
||||
* ClassNotFoundException) instance data for each serializable class of
|
||||
|
@ -2317,7 +2399,7 @@ public class ObjectInputStream
|
|||
}
|
||||
}
|
||||
|
||||
private class FieldValues {
|
||||
/*package-private*/ class FieldValues {
|
||||
final byte[] primValues;
|
||||
final Object[] objValues;
|
||||
|
||||
|
|
|
@ -150,6 +150,10 @@ import sun.reflect.misc.ReflectUtil;
|
|||
* defaultWriteObject and writeFields initially terminate any existing
|
||||
* block-data record.
|
||||
*
|
||||
* @implSpec
|
||||
* Records are serialized differently than ordinary serializable or externalizable
|
||||
* objects, see <a href="ObjectInputStream.html#record-serialization">record serialization</a>.
|
||||
*
|
||||
* @author Mike Warres
|
||||
* @author Roger Riggs
|
||||
* @see java.io.DataOutput
|
||||
|
@ -1431,7 +1435,10 @@ public class ObjectOutputStream
|
|||
bout.writeByte(TC_OBJECT);
|
||||
writeClassDesc(desc, false);
|
||||
handles.assign(unshared ? null : obj);
|
||||
if (desc.isExternalizable() && !desc.isProxy()) {
|
||||
|
||||
if (desc.isRecord()) {
|
||||
writeRecordData(obj, desc);
|
||||
} else if (desc.isExternalizable() && !desc.isProxy()) {
|
||||
writeExternalData((Externalizable) obj);
|
||||
} else {
|
||||
writeSerialData(obj, desc);
|
||||
|
@ -1475,6 +1482,21 @@ public class ObjectOutputStream
|
|||
curPut = oldPut;
|
||||
}
|
||||
|
||||
/** Writes the record component values for the given record object. */
|
||||
@SuppressWarnings("preview")
|
||||
private void writeRecordData(Object obj, ObjectStreamClass desc)
|
||||
throws IOException
|
||||
{
|
||||
assert obj.getClass().isRecord();
|
||||
ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
|
||||
if (slots.length != 1) {
|
||||
throw new InvalidClassException(
|
||||
"expected a single record slot length, but found: " + slots.length);
|
||||
}
|
||||
|
||||
defaultWriteFields(obj, desc); // #### seems unnecessary to use the accessors
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes instance data for each serializable class of given object, from
|
||||
* superclass to subclass.
|
||||
|
|
|
@ -25,6 +25,8 @@
|
|||
|
||||
package java.io;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.ref.Reference;
|
||||
import java.lang.ref.ReferenceQueue;
|
||||
import java.lang.ref.SoftReference;
|
||||
|
@ -32,6 +34,7 @@ import java.lang.ref.WeakReference;
|
|||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.RecordComponent;
|
||||
import java.lang.reflect.UndeclaredThrowableException;
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
|
@ -44,6 +47,8 @@ import java.security.NoSuchAlgorithmException;
|
|||
import java.security.PermissionCollection;
|
||||
import java.security.Permissions;
|
||||
import java.security.PrivilegedAction;
|
||||
import java.security.PrivilegedActionException;
|
||||
import java.security.PrivilegedExceptionAction;
|
||||
import java.security.ProtectionDomain;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
@ -123,6 +128,8 @@ public class ObjectStreamClass implements Serializable {
|
|||
private boolean isProxy;
|
||||
/** true if represents enum type */
|
||||
private boolean isEnum;
|
||||
/** true if represents record type */
|
||||
private boolean isRecord;
|
||||
/** true if represented class implements Serializable */
|
||||
private boolean serializable;
|
||||
/** true if represented class implements Externalizable */
|
||||
|
@ -184,6 +191,8 @@ public class ObjectStreamClass implements Serializable {
|
|||
|
||||
/** serialization-appropriate constructor, or null if none */
|
||||
private Constructor<?> cons;
|
||||
/** record canonical constructor, or null */
|
||||
private MethodHandle canonicalCtr;
|
||||
/** protection domains that need to be checked when calling the constructor */
|
||||
private ProtectionDomain[] domains;
|
||||
|
||||
|
@ -261,6 +270,9 @@ public class ObjectStreamClass implements Serializable {
|
|||
public long getSerialVersionUID() {
|
||||
// REMIND: synchronize instead of relying on volatile?
|
||||
if (suid == null) {
|
||||
if (isRecord)
|
||||
return 0L;
|
||||
|
||||
suid = AccessController.doPrivileged(
|
||||
new PrivilegedAction<Long>() {
|
||||
public Long run() {
|
||||
|
@ -467,6 +479,11 @@ public class ObjectStreamClass implements Serializable {
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("preview")
|
||||
private static boolean isRecord(Class<?> cls) {
|
||||
return cls.isRecord();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates local class descriptor representing given class.
|
||||
*/
|
||||
|
@ -475,6 +492,7 @@ public class ObjectStreamClass implements Serializable {
|
|||
name = cl.getName();
|
||||
isProxy = Proxy.isProxyClass(cl);
|
||||
isEnum = Enum.class.isAssignableFrom(cl);
|
||||
isRecord = isRecord(cl);
|
||||
serializable = Serializable.class.isAssignableFrom(cl);
|
||||
externalizable = Externalizable.class.isAssignableFrom(cl);
|
||||
|
||||
|
@ -505,7 +523,9 @@ public class ObjectStreamClass implements Serializable {
|
|||
fields = NO_FIELDS;
|
||||
}
|
||||
|
||||
if (externalizable) {
|
||||
if (isRecord) {
|
||||
canonicalCtr = canonicalRecordCtr(cl);
|
||||
} else if (externalizable) {
|
||||
cons = getExternalizableConstructor(cl);
|
||||
} else {
|
||||
cons = getSerializableConstructor(cl);
|
||||
|
@ -542,14 +562,18 @@ public class ObjectStreamClass implements Serializable {
|
|||
if (deserializeEx == null) {
|
||||
if (isEnum) {
|
||||
deserializeEx = new ExceptionInfo(name, "enum type");
|
||||
} else if (cons == null) {
|
||||
} else if (cons == null && !isRecord) {
|
||||
deserializeEx = new ExceptionInfo(name, "no valid constructor");
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < fields.length; i++) {
|
||||
if (fields[i].getField() == null) {
|
||||
defaultSerializeEx = new ExceptionInfo(
|
||||
name, "unmatched serializable field(s) declared");
|
||||
if (isRecord && canonicalCtr == null) {
|
||||
deserializeEx = new ExceptionInfo(name, "record canonical constructor not found");
|
||||
} else {
|
||||
for (int i = 0; i < fields.length; i++) {
|
||||
if (fields[i].getField() == null) {
|
||||
defaultSerializeEx = new ExceptionInfo(
|
||||
name, "unmatched serializable field(s) declared");
|
||||
}
|
||||
}
|
||||
}
|
||||
initialized = true;
|
||||
|
@ -682,7 +706,7 @@ public class ObjectStreamClass implements Serializable {
|
|||
}
|
||||
|
||||
if (model.serializable == osc.serializable &&
|
||||
!cl.isArray() &&
|
||||
!cl.isArray() && !isRecord(cl) &&
|
||||
suid != osc.getSerialVersionUID()) {
|
||||
throw new InvalidClassException(osc.name,
|
||||
"local class incompatible: " +
|
||||
|
@ -714,6 +738,10 @@ public class ObjectStreamClass implements Serializable {
|
|||
}
|
||||
|
||||
this.cl = cl;
|
||||
if (cl != null) {
|
||||
this.isRecord = isRecord(cl);
|
||||
this.canonicalCtr = osc.canonicalCtr;
|
||||
}
|
||||
this.resolveEx = resolveEx;
|
||||
this.superDesc = superDesc;
|
||||
name = model.name;
|
||||
|
@ -739,12 +767,14 @@ public class ObjectStreamClass implements Serializable {
|
|||
deserializeEx = localDesc.deserializeEx;
|
||||
}
|
||||
domains = localDesc.domains;
|
||||
assert isRecord(cl) ? localDesc.cons == null : true;
|
||||
cons = localDesc.cons;
|
||||
}
|
||||
|
||||
fieldRefl = getReflector(fields, localDesc);
|
||||
// reassign to matched fields so as to reflect local unshared settings
|
||||
fields = fieldRefl.getFields();
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
|
@ -966,6 +996,15 @@ public class ObjectStreamClass implements Serializable {
|
|||
return isEnum;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if class descriptor represents a record type, false
|
||||
* otherwise.
|
||||
*/
|
||||
boolean isRecord() {
|
||||
requireInitialized();
|
||||
return isRecord;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if represented class implements Externalizable, false
|
||||
* otherwise.
|
||||
|
@ -1518,6 +1557,37 @@ public class ObjectStreamClass implements Serializable {
|
|||
return reflFactory.newConstructorForSerialization(cl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the canonical constructor for the given record class, or null if
|
||||
* the not found ( which should never happen for correctly generated record
|
||||
* classes ).
|
||||
*/
|
||||
@SuppressWarnings("preview")
|
||||
private static MethodHandle canonicalRecordCtr(Class<?> cls) {
|
||||
assert isRecord(cls) : "Expected record, got: " + cls;
|
||||
PrivilegedAction<MethodHandle> pa = () -> {
|
||||
Class<?>[] paramTypes = Arrays.stream(cls.getRecordComponents())
|
||||
.map(RecordComponent::getType)
|
||||
.toArray(Class<?>[]::new);
|
||||
try {
|
||||
Constructor<?> ctr = cls.getConstructor(paramTypes);
|
||||
ctr.setAccessible(true);
|
||||
return MethodHandles.lookup().unreflectConstructor(ctr);
|
||||
} catch (IllegalAccessException | NoSuchMethodException e) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
return AccessController.doPrivileged(pa);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the canonical constructor, if the local class equivalent of this
|
||||
* stream class descriptor is a record class, otherwise null.
|
||||
*/
|
||||
MethodHandle getRecordConstructor() {
|
||||
return canonicalCtr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns non-static, non-abstract method with given signature provided it
|
||||
* is defined by or accessible (via inheritance) by the given class, or
|
||||
|
@ -1641,12 +1711,16 @@ public class ObjectStreamClass implements Serializable {
|
|||
private static ObjectStreamField[] getSerialFields(Class<?> cl)
|
||||
throws InvalidClassException
|
||||
{
|
||||
if (!Serializable.class.isAssignableFrom(cl))
|
||||
return NO_FIELDS;
|
||||
|
||||
ObjectStreamField[] fields;
|
||||
if (Serializable.class.isAssignableFrom(cl) &&
|
||||
!Externalizable.class.isAssignableFrom(cl) &&
|
||||
if (isRecord(cl)) {
|
||||
fields = getDefaultSerialFields(cl);
|
||||
Arrays.sort(fields);
|
||||
} else if (!Externalizable.class.isAssignableFrom(cl) &&
|
||||
!Proxy.isProxyClass(cl) &&
|
||||
!cl.isInterface())
|
||||
{
|
||||
!cl.isInterface()) {
|
||||
if ((fields = getDeclaredSerialFields(cl)) == null) {
|
||||
fields = getDefaultSerialFields(cl);
|
||||
}
|
||||
|
@ -2438,4 +2512,115 @@ public class ObjectStreamClass implements Serializable {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Record specific support for retrieving and binding stream field values. */
|
||||
static final class RecordSupport {
|
||||
|
||||
/** Binds the given stream field values to the given method handle. */
|
||||
@SuppressWarnings("preview")
|
||||
static MethodHandle bindCtrValues(MethodHandle ctrMH,
|
||||
ObjectStreamClass desc,
|
||||
ObjectInputStream.FieldValues fieldValues) {
|
||||
RecordComponent[] recordComponents;
|
||||
try {
|
||||
Class<?> cls = desc.forClass();
|
||||
PrivilegedExceptionAction<RecordComponent[]> pa = cls::getRecordComponents;
|
||||
recordComponents = AccessController.doPrivileged(pa);
|
||||
} catch (PrivilegedActionException e) {
|
||||
throw new InternalError(e.getCause());
|
||||
}
|
||||
|
||||
Object[] args = new Object[recordComponents.length];
|
||||
for (int i = 0; i < recordComponents.length; i++) {
|
||||
String name = recordComponents[i].getName();
|
||||
Class<?> type= recordComponents[i].getType();
|
||||
Object o = streamFieldValue(name, type, desc, fieldValues);
|
||||
args[i] = o;
|
||||
}
|
||||
|
||||
return MethodHandles.insertArguments(ctrMH, 0, args);
|
||||
}
|
||||
|
||||
/** Returns the number of primitive fields for the given descriptor. */
|
||||
private static int numberPrimValues(ObjectStreamClass desc) {
|
||||
ObjectStreamField[] fields = desc.getFields();
|
||||
int primValueCount = 0;
|
||||
for (int i = 0; i < fields.length; i++) {
|
||||
if (fields[i].isPrimitive())
|
||||
primValueCount++;
|
||||
else
|
||||
break; // can be no more
|
||||
}
|
||||
return primValueCount;
|
||||
}
|
||||
|
||||
/** Returns the default value for the given type. */
|
||||
private static Object defaultValueFor(Class<?> pType) {
|
||||
if (pType == Integer.TYPE)
|
||||
return 0;
|
||||
else if (pType == Byte.TYPE)
|
||||
return (byte)0;
|
||||
else if (pType == Long.TYPE)
|
||||
return 0L;
|
||||
else if (pType == Float.TYPE)
|
||||
return 0.0f;
|
||||
else if (pType == Double.TYPE)
|
||||
return 0.0d;
|
||||
else if (pType == Short.TYPE)
|
||||
return (short)0;
|
||||
else if (pType == Character.TYPE)
|
||||
return '\u0000';
|
||||
else if (pType == Boolean.TYPE)
|
||||
return false;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the stream field value for the given name. The default value
|
||||
* for the given type is returned if the field value is absent.
|
||||
*/
|
||||
private static Object streamFieldValue(String pName,
|
||||
Class<?> pType,
|
||||
ObjectStreamClass desc,
|
||||
ObjectInputStream.FieldValues fieldValues) {
|
||||
ObjectStreamField[] fields = desc.getFields();
|
||||
|
||||
for (int i = 0; i < fields.length; i++) {
|
||||
ObjectStreamField f = fields[i];
|
||||
String fName = f.getName();
|
||||
if (!fName.equals(pName))
|
||||
continue;
|
||||
|
||||
Class<?> fType = f.getField().getType();
|
||||
if (!pType.isAssignableFrom(fType))
|
||||
throw new InternalError(fName + " unassignable, pType:" + pType + ", fType:" + fType);
|
||||
|
||||
if (f.isPrimitive()) {
|
||||
if (pType == Integer.TYPE)
|
||||
return Bits.getInt(fieldValues.primValues, f.getOffset());
|
||||
else if (fType == Byte.TYPE)
|
||||
return fieldValues.primValues[f.getOffset()];
|
||||
else if (fType == Long.TYPE)
|
||||
return Bits.getLong(fieldValues.primValues, f.getOffset());
|
||||
else if (fType == Float.TYPE)
|
||||
return Bits.getFloat(fieldValues.primValues, f.getOffset());
|
||||
else if (fType == Double.TYPE)
|
||||
return Bits.getDouble(fieldValues.primValues, f.getOffset());
|
||||
else if (fType == Short.TYPE)
|
||||
return Bits.getShort(fieldValues.primValues, f.getOffset());
|
||||
else if (fType == Character.TYPE)
|
||||
return Bits.getChar(fieldValues.primValues, f.getOffset());
|
||||
else if (fType == Boolean.TYPE)
|
||||
return Bits.getBoolean(fieldValues.primValues, f.getOffset());
|
||||
else
|
||||
throw new InternalError("Unexpected type: " + fType);
|
||||
} else { // reference
|
||||
return fieldValues.objValues[i - numberPrimValues(desc)];
|
||||
}
|
||||
}
|
||||
|
||||
return defaultValueFor(pType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue