OpenJDK / amber / amber
changeset 58344:e7a60c0654d6 records
records implementation
line wrap: on
line diff
--- a/make/hotspot/symbols/symbols-unix Wed Oct 16 17:00:39 2019 -0400 +++ b/make/hotspot/symbols/symbols-unix Wed Oct 16 17:02:29 2019 -0400 @@ -122,6 +122,7 @@ JVM_GetPrimitiveArrayElement JVM_GetProperties JVM_GetProtectionDomain +JVM_GetRecordComponents JVM_GetSimpleBinaryName JVM_GetStackAccessControlContext JVM_GetSystemPackage @@ -144,6 +145,7 @@ JVM_IsInterface JVM_IsInterrupted JVM_IsPrimitiveClass +JVM_IsRecord JVM_IsSameClassPackage JVM_IsSupportedJNIVersion JVM_IsThreadAlive
--- a/src/hotspot/share/classfile/classFileParser.cpp Wed Oct 16 17:00:39 2019 -0400 +++ b/src/hotspot/share/classfile/classFileParser.cpp Wed Oct 16 17:02:29 2019 -0400 @@ -3223,6 +3223,159 @@ return length; } +u2 ClassFileParser::parse_classfile_record_attribute(const ClassFileStream* const cfs, + const ConstantPool* cp, + const u1* const record_attribute_start, + TRAPS) { + const u1* const current_mark = cfs->current(); + int components_count = 0; + unsigned int calculate_attr_size = 0; + if (record_attribute_start != NULL) { + cfs->set_current(record_attribute_start); + cfs->guarantee_more(2, CHECK_0); // num of components + components_count = (int)cfs->get_u2_fast(); + calculate_attr_size = 2; + } + + Array<RecordComponent*>* const record_components = + MetadataFactory::new_array<RecordComponent*>(_loader_data, components_count, NULL, CHECK_0); + _record_components = record_components; + + for (int x = 0; x < components_count; x++) { + cfs->guarantee_more(6, CHECK_0); // name_index, descriptor_index, attributes_count + + const u2 name_index = cfs->get_u2_fast(); + check_property(valid_symbol_at(name_index), + "Invalid constant pool index %u for name in Record attribute in class file %s", + name_index, CHECK_0); + const Symbol* const name = cp->symbol_at(name_index); + verify_legal_field_name(name, CHECK_0); + + const u2 descriptor_index = cfs->get_u2_fast(); + check_property(valid_symbol_at(descriptor_index), + "Invalid constant pool index %u for descriptor in Record attribute in class file %s", + descriptor_index, CHECK_0); + const Symbol* const descr = cp->symbol_at(descriptor_index); + verify_legal_field_signature(name, descr, CHECK_0); + + const u2 attributes_count = cfs->get_u2_fast(); + calculate_attr_size += 6; + u2 generic_sig_index = 0; + const u1* runtime_visible_annotations = NULL; + int runtime_visible_annotations_length = 0; + const u1* runtime_invisible_annotations = NULL; + int runtime_invisible_annotations_length = 0; + bool runtime_invisible_annotations_exists = false; + const u1* runtime_visible_type_annotations = NULL; + int runtime_visible_type_annotations_length = 0; + const u1* runtime_invisible_type_annotations = NULL; + int runtime_invisible_type_annotations_length = 0; + bool runtime_invisible_type_annotations_exists = false; + + for (int y = 0; y < attributes_count; y++) { + cfs->guarantee_more(6, CHECK_0); // attribute_name_index, attribute_length + const u2 attribute_name_index = cfs->get_u2_fast(); + const u4 attribute_length = cfs->get_u4_fast(); + calculate_attr_size += 6; + check_property( + valid_symbol_at(attribute_name_index), + "Invalid Record attribute name index %u in class file %s", + attribute_name_index, CHECK_0); + + const Symbol* const attribute_name = cp->symbol_at(attribute_name_index); + if (attribute_name == vmSymbols::tag_signature()) { + if (generic_sig_index != 0) { + classfile_parse_error( + "Multiple Signature attributes for Record component in class file %s", + CHECK_0); + } + if (attribute_length != 2) { + classfile_parse_error( + "Invalid Signature attribute length %u in Record component in class file %s", + attribute_length, CHECK_0); + } + generic_sig_index = parse_generic_signature_attribute(cfs, CHECK_0); + + } else if (attribute_name == vmSymbols::tag_runtime_visible_annotations()) { + if (runtime_visible_annotations != NULL) { + classfile_parse_error( + "Multiple RuntimeVisibleAnnotations attributes for Record component in class file %s", CHECK_0); + } + runtime_visible_annotations_length = attribute_length; + runtime_visible_annotations = cfs->current(); + + assert(runtime_visible_annotations != NULL, "null record component visible annotation"); + cfs->guarantee_more(runtime_visible_annotations_length, CHECK_0); + cfs->skip_u1_fast(runtime_visible_annotations_length); + + } else if (attribute_name == vmSymbols::tag_runtime_invisible_annotations()) { + if (runtime_invisible_annotations_exists) { + classfile_parse_error( + "Multiple RuntimeInvisibleAnnotations attributes for Record component in class file %s", CHECK_0); + } + runtime_invisible_annotations_exists = true; + if (PreserveAllAnnotations) { + runtime_invisible_annotations_length = attribute_length; + runtime_invisible_annotations = cfs->current(); + assert(runtime_invisible_annotations != NULL, "null record component invisible annotation"); + } + cfs->skip_u1(attribute_length, CHECK_0); + + } else if (attribute_name == vmSymbols::tag_runtime_visible_type_annotations()) { + if (runtime_visible_type_annotations != NULL) { + classfile_parse_error( + "Multiple RuntimeVisibleTypeAnnotations attributes for Record component in class file %s", CHECK_0); + } + runtime_visible_type_annotations_length = attribute_length; + runtime_visible_type_annotations = cfs->current(); + + assert(runtime_visible_type_annotations != NULL, "null record component visible type annotation"); + cfs->guarantee_more(runtime_visible_type_annotations_length, CHECK_0); + cfs->skip_u1_fast(runtime_visible_type_annotations_length); + + } else if (attribute_name == vmSymbols::tag_runtime_invisible_type_annotations()) { + if (runtime_invisible_type_annotations_exists) { + classfile_parse_error( + "Multiple RuntimeInvisibleTypeAnnotations attributes for Record component in class file %s", CHECK_0); + } + runtime_invisible_type_annotations_exists = true; + if (PreserveAllAnnotations) { + runtime_invisible_type_annotations_length = attribute_length; + runtime_invisible_type_annotations = cfs->current(); + assert(runtime_invisible_type_annotations != NULL, "null record component invisible type annotation"); + } + cfs->skip_u1(attribute_length, CHECK_0); + + } else { + // Skip unknown attributes + cfs->skip_u1(attribute_length, CHECK_0); + } + calculate_attr_size += attribute_length; + } // End of attributes For loop + + AnnotationArray* annotations = assemble_annotations(runtime_visible_annotations, + runtime_visible_annotations_length, + runtime_invisible_annotations, + runtime_invisible_annotations_length, + CHECK_0); + AnnotationArray* type_annotations = assemble_annotations(runtime_visible_type_annotations, + runtime_visible_type_annotations_length, + runtime_invisible_type_annotations, + runtime_invisible_type_annotations_length, + CHECK_0); + + RecordComponent* record_component = + RecordComponent::allocate(_loader_data, name_index, descriptor_index, + attributes_count, generic_sig_index, + annotations, type_annotations, CHECK_0); + record_components->at_put(x, record_component); + } // End of component processing loop + + // Restore buffer's current position. + cfs->set_current(current_mark); + return calculate_attr_size; +} + void ClassFileParser::parse_classfile_synthetic_attribute(TRAPS) { set_class_synthetic_flag(true); } @@ -3320,6 +3473,12 @@ CHECK); } +bool ClassFileParser::supports_records() { + return _major_version == JAVA_14_VERSION /* TBD && + _minor_version == JAVA_PREVIEW_MINOR_VERSION && + Arguments::enable_preview() */ ; +} + void ClassFileParser::parse_classfile_attributes(const ClassFileStream* const cfs, ConstantPool* cp, ClassFileParser::ClassAnnotationCollector* parsed_annotations, @@ -3338,6 +3497,7 @@ bool parsed_innerclasses_attribute = false; bool parsed_nest_members_attribute = false; bool parsed_nest_host_attribute = false; + bool parsed_record_attribute = false; bool parsed_enclosingmethod_attribute = false; bool parsed_bootstrap_methods_attribute = false; const u1* runtime_visible_annotations = NULL; @@ -3357,6 +3517,8 @@ u2 enclosing_method_method_index = 0; const u1* nest_members_attribute_start = NULL; u4 nest_members_attribute_length = 0; + const u1* record_attribute_start = NULL; + u4 record_attribute_length = 0; // Iterate over attributes while (attributes_count--) { @@ -3539,6 +3701,23 @@ "Nest-host class_info_index %u has bad constant type in class file %s", class_info_index, CHECK); _nest_host = class_info_index; + } else if (tag == vmSymbols::tag_record()) { + // Skip over Record attribute if not supported or if super class is + // not java.lang.Record. + if (supports_records() && + cp->klass_name_at(_super_class_index) == vmSymbols::java_lang_Record()) { + if (parsed_record_attribute) { + classfile_parse_error("Multiple Record attributes in class file %s", CHECK); + } + // Check that class is final and not abstract. + if (!_access_flags.is_final() || _access_flags.is_abstract()) { + classfile_parse_error("Record attribute in non-final or abstract class file %s", CHECK); + } + parsed_record_attribute = true; + record_attribute_start = cfs->current(); + record_attribute_length = attribute_length; + } + cfs->skip_u1(attribute_length, CHECK); } else { // Unknown attribute cfs->skip_u1(attribute_length, CHECK); @@ -3590,6 +3769,19 @@ } } + if (parsed_record_attribute) { + const unsigned int calculated_attr_length = parse_classfile_record_attribute( + cfs, + cp, + record_attribute_start, + CHECK); + if (_need_verify) { + guarantee_property(record_attribute_length == calculated_attr_length, + "Record attribute has wrong length in class file %s", + CHECK); + } + } + if (_max_bootstrap_specifier_index >= 0) { guarantee_property(parsed_bootstrap_methods_attribute, "Missing BootstrapMethods attribute in class file %s", CHECK); @@ -3644,7 +3836,8 @@ // Transfer ownership of metadata allocated to the InstanceKlass. void ClassFileParser::apply_parsed_class_metadata( InstanceKlass* this_klass, - int java_fields_count, TRAPS) { + int java_fields_count, + TRAPS) { assert(this_klass != NULL, "invariant"); _cp->set_pool_holder(this_klass); @@ -3656,6 +3849,7 @@ this_klass->set_nest_host_index(_nest_host); this_klass->set_local_interfaces(_local_interfaces); this_klass->set_annotations(_combined_annotations); + this_klass->set_record_components(_record_components); // Delay the setting of _transitive_interfaces until after initialize_supers() in // fill_instance_klass(). It is because the _transitive_interfaces may be shared with // its _super. If an OOM occurs while loading the current klass, its _super field @@ -4526,6 +4720,7 @@ static void check_super_class_access(const InstanceKlass* this_klass, TRAPS) { assert(this_klass != NULL, "invariant"); const Klass* const super = this_klass->super(); + if (super != NULL) { // If the loader is not the boot loader then throw an exception if its @@ -4727,12 +4922,13 @@ const bool is_super = (flags & JVM_ACC_SUPER) != 0; const bool is_enum = (flags & JVM_ACC_ENUM) != 0; const bool is_annotation = (flags & JVM_ACC_ANNOTATION) != 0; - const bool major_gte_15 = _major_version >= JAVA_1_5_VERSION; - - if ((is_abstract && is_final) || + const bool major_gte_1_5 = _major_version >= JAVA_1_5_VERSION; + const bool major_gte_14 = _major_version >= JAVA_14_VERSION; + + if ((is_abstract && is_final && !major_gte_14) || (is_interface && !is_abstract) || - (is_interface && major_gte_15 && (is_super || is_enum)) || - (!is_interface && major_gte_15 && is_annotation)) { + (is_interface && major_gte_1_5 && (is_super || is_enum)) || + (!is_interface && major_gte_1_5 && is_annotation)) { ResourceMark rm(THREAD); Exceptions::fthrow( THREAD_AND_LOCATION, @@ -5506,6 +5702,7 @@ assert(NULL == _nest_members, "invariant"); assert(NULL == _local_interfaces, "invariant"); assert(NULL == _combined_annotations, "invariant"); + assert(NULL == _record_components, "invariant"); if (_has_final_method) { ik->set_has_final_method(); @@ -5787,6 +5984,7 @@ _inner_classes(NULL), _nest_members(NULL), _nest_host(0), + _record_components(NULL), _local_interfaces(NULL), _transitive_interfaces(NULL), _combined_annotations(NULL), @@ -5897,6 +6095,7 @@ _combined_annotations = NULL; _class_annotations = _class_type_annotations = NULL; _fields_annotations = _fields_type_annotations = NULL; + _record_components = NULL; } // Destructor to clean up @@ -5924,6 +6123,10 @@ MetadataFactory::free_array<u2>(_loader_data, _nest_members); } + if (_record_components != NULL) { + InstanceKlass::deallocate_record_components(_loader_data, _record_components); + } + // Free interfaces InstanceKlass::deallocate_interfaces(_loader_data, _super_klass, _local_interfaces, _transitive_interfaces);
--- a/src/hotspot/share/classfile/classFileParser.hpp Wed Oct 16 17:00:39 2019 -0400 +++ b/src/hotspot/share/classfile/classFileParser.hpp Wed Oct 16 17:02:29 2019 -0400 @@ -28,6 +28,7 @@ #include "memory/referenceType.hpp" #include "oops/annotations.hpp" #include "oops/constantPool.hpp" +#include "oops/recordComponent.hpp" #include "oops/typeArrayOop.hpp" #include "utilities/accessFlags.hpp" @@ -98,6 +99,7 @@ Array<u2>* _inner_classes; Array<u2>* _nest_members; u2 _nest_host; + Array<RecordComponent*>* _record_components; Array<InstanceKlass*>* _local_interfaces; Array<InstanceKlass*>* _transitive_interfaces; Annotations* _combined_annotations; @@ -287,6 +289,13 @@ const u1* const nest_members_attribute_start, TRAPS); + u2 parse_classfile_record_attribute(const ClassFileStream* const cfs, + const ConstantPool* cp, + const u1* const record_attribute_start, + TRAPS); + + bool supports_records(); + void parse_classfile_attributes(const ClassFileStream* const cfs, ConstantPool* cp, ClassAnnotationCollector* parsed_annotations,
--- a/src/hotspot/share/classfile/javaClasses.cpp Wed Oct 16 17:00:39 2019 -0400 +++ b/src/hotspot/share/classfile/javaClasses.cpp Wed Oct 16 17:02:29 2019 -0400 @@ -50,6 +50,7 @@ #include "oops/objArrayOop.inline.hpp" #include "oops/oop.inline.hpp" #include "oops/symbol.hpp" +#include "oops/recordComponent.hpp" #include "oops/typeArrayOop.inline.hpp" #include "prims/jvmtiExport.hpp" #include "prims/resolvedMethodTable.hpp" @@ -3021,6 +3022,65 @@ field->obj_field_put(annotations_offset, value); } +oop java_lang_reflect_RecordComponent::create(InstanceKlass* holder, RecordComponent* component, TRAPS) { + // Allocate java.lang.reflect.RecordComponent instance + InstanceKlass* ik = SystemDictionary::RecordComponent_klass(); + assert(ik != NULL, "must be loaded"); + if (ik->should_be_initialized()) { + ik->initialize(CHECK_0); + } + + Handle element = ik->allocate_instance_handle(CHECK_0); + + Handle decl_class(THREAD, holder->java_mirror()); + java_lang_reflect_RecordComponent::set_clazz(element(), decl_class()); + + Symbol* name = holder->constants()->symbol_at(component->name_index()); // name_index is a utf8 + oop component_name = StringTable::intern(name, CHECK_0); + java_lang_reflect_RecordComponent::set_name(element(), component_name); + + Symbol* type = holder->constants()->symbol_at(component->descriptor_index()); + Handle component_type_h = + SystemDictionary::find_java_mirror_for_type(type, holder, SignatureStream::NCDFError, CHECK_0); + java_lang_reflect_RecordComponent::set_type(element(), component_type_h()); + + Method* accessor_method = NULL; + { + // Prepend "()" to type to create the full method signature. + ResourceMark rm(THREAD); + int sig_len = type->utf8_length() + 3; // "()" and null char + char* sig = NEW_RESOURCE_ARRAY(char, sig_len); + jio_snprintf(sig, sig_len, "()%s", type->as_C_string()); + TempNewSymbol full_sig = SymbolTable::new_symbol(sig); + accessor_method = holder->find_instance_method(name, full_sig); + } + + if (accessor_method != NULL) { // TBD should a null accessor method be an error? + methodHandle method(THREAD, accessor_method); + oop m = Reflection::new_method(method, false, CHECK_0); + java_lang_reflect_RecordComponent::set_accessor(element(), m); + } else { + java_lang_reflect_RecordComponent::set_accessor(element(), NULL); + } + + int sig_index = component->generic_signature_index(); + if (sig_index > 0) { + Symbol* sig = holder->constants()->symbol_at(sig_index); // name_index is a utf8 + oop component_sig = StringTable::intern(sig, CHECK_0); + java_lang_reflect_RecordComponent::set_signature(element(), component_sig); + } else { + java_lang_reflect_RecordComponent::set_signature(element(), NULL); + } + + typeArrayOop annotation_oop = Annotations::make_java_array(component->annotations(), CHECK_0); + java_lang_reflect_RecordComponent::set_annotations(element(), annotation_oop); + + typeArrayOop type_annotation_oop = Annotations::make_java_array(component->type_annotations(), CHECK_0); + java_lang_reflect_RecordComponent::set_typeAnnotations(element(), type_annotation_oop); + + return element(); +} + #define CONSTANTPOOL_FIELDS_DO(macro) \ macro(_oop_offset, k, "constantPoolOop", object_signature, false) @@ -4163,6 +4223,13 @@ int java_lang_Byte_ByteCache::_static_cache_offset; int java_lang_Boolean::_static_TRUE_offset; int java_lang_Boolean::_static_FALSE_offset; +int java_lang_reflect_RecordComponent::clazz_offset; +int java_lang_reflect_RecordComponent::name_offset; +int java_lang_reflect_RecordComponent::type_offset; +int java_lang_reflect_RecordComponent::accessor_offset; +int java_lang_reflect_RecordComponent::signature_offset; +int java_lang_reflect_RecordComponent::annotations_offset; +int java_lang_reflect_RecordComponent::typeAnnotations_offset; @@ -4514,6 +4581,55 @@ return (hardcoded_offset * heapOopSize) + instanceOopDesc::base_offset_in_bytes(); } +#define RECORDCOMPONENT_FIELDS_DO(macro) \ + macro(clazz_offset, k, "clazz", class_signature, false); \ + macro(name_offset, k, "name", string_signature, false); \ + macro(type_offset, k, "type", class_signature, false); \ + macro(accessor_offset, k, "accessor", reflect_method_signature, false); \ + macro(signature_offset, k, "signature", string_signature, false); \ + macro(annotations_offset, k, "annotations", byte_array_signature, false); \ + macro(typeAnnotations_offset, k, "typeAnnotations", byte_array_signature, false); + +// Support for java_lang_reflect_RecordComponent +void java_lang_reflect_RecordComponent::compute_offsets() { + InstanceKlass* k = SystemDictionary::RecordComponent_klass(); + RECORDCOMPONENT_FIELDS_DO(FIELD_COMPUTE_OFFSET); +} + +#if INCLUDE_CDS +void java_lang_reflect_RecordComponent::serialize_offsets(SerializeClosure* f) { + RECORDCOMPONENT_FIELDS_DO(FIELD_SERIALIZE_OFFSET); +} +#endif + +void java_lang_reflect_RecordComponent::set_clazz(oop element, oop value) { + element->obj_field_put(clazz_offset, value); +} + +void java_lang_reflect_RecordComponent::set_name(oop element, oop value) { + element->obj_field_put(name_offset, value); +} + +void java_lang_reflect_RecordComponent::set_type(oop element, oop value) { + element->obj_field_put(type_offset, value); +} + +void java_lang_reflect_RecordComponent::set_accessor(oop element, oop value) { + element->obj_field_put(accessor_offset, value); +} + +void java_lang_reflect_RecordComponent::set_signature(oop element, oop value) { + element->obj_field_put(signature_offset, value); +} + +void java_lang_reflect_RecordComponent::set_annotations(oop element, oop value) { + element->obj_field_put(annotations_offset, value); +} + +void java_lang_reflect_RecordComponent::set_typeAnnotations(oop element, oop value) { + element->obj_field_put(typeAnnotations_offset, value); +} + // Compute hard-coded offsets // Invoked before SystemDictionary::initialize, so pre-loaded classes // are not available to determine the offset_of_static_fields.
--- a/src/hotspot/share/classfile/javaClasses.hpp Wed Oct 16 17:00:39 2019 -0400 +++ b/src/hotspot/share/classfile/javaClasses.hpp Wed Oct 16 17:02:29 2019 -0400 @@ -28,6 +28,7 @@ #include "classfile/systemDictionary.hpp" #include "jvmtifiles/jvmti.h" #include "oops/oop.hpp" +#include "oops/recordComponent.hpp" #include "runtime/os.hpp" // Interface for manipulating the basic Java classes. @@ -72,6 +73,7 @@ f(java_lang_reflect_Method) \ f(java_lang_reflect_Constructor) \ f(java_lang_reflect_Field) \ + f(java_lang_reflect_RecordComponent) \ f(java_nio_Buffer) \ f(reflect_ConstantPool) \ f(reflect_UnsafeStaticFieldAccessorImpl) \ @@ -1442,6 +1444,39 @@ friend class JavaClasses; }; +// Interface to java.lang.reflect.RecordComponent objects + +class java_lang_reflect_RecordComponent: AllStatic { + private: + static int clazz_offset; + static int name_offset; + static int type_offset; + static int accessor_offset; + static int signature_offset; + static int annotations_offset; + static int typeAnnotations_offset; + + // Setters + static void set_clazz(oop element, oop value); + static void set_name(oop element, oop value); + static void set_type(oop element, oop value); + static void set_accessor(oop element, oop value); + static void set_signature(oop element, oop value); + static void set_annotations(oop element, oop value); + static void set_typeAnnotations(oop element, oop value); + + public: + // Create an instance of RecordComponent + static oop create(InstanceKlass* holder, RecordComponent* component, TRAPS); + + static void compute_offsets(); + static void serialize_offsets(SerializeClosure* f) NOT_CDS_RETURN; + + // Debugging + friend class JavaClasses; +}; + + // Interface to java.lang.AssertionStatusDirectives objects class java_lang_AssertionStatusDirectives: AllStatic {
--- a/src/hotspot/share/classfile/systemDictionary.hpp Wed Oct 16 17:00:39 2019 -0400 +++ b/src/hotspot/share/classfile/systemDictionary.hpp Wed Oct 16 17:02:29 2019 -0400 @@ -119,6 +119,7 @@ do_klass(AccessController_klass, java_security_AccessController ) \ do_klass(SecureClassLoader_klass, java_security_SecureClassLoader ) \ do_klass(ClassNotFoundException_klass, java_lang_ClassNotFoundException ) \ + do_klass(Record_klass, java_lang_Record ) \ do_klass(NoClassDefFoundError_klass, java_lang_NoClassDefFoundError ) \ do_klass(LinkageError_klass, java_lang_LinkageError ) \ do_klass(ClassCastException_klass, java_lang_ClassCastException ) \ @@ -216,6 +217,9 @@ /* force inline of iterators */ \ do_klass(Iterator_klass, java_util_Iterator ) \ \ + /* support for records */ \ + do_klass(RecordComponent_klass, java_lang_reflect_RecordComponent ) \ + \ /*end*/
--- a/src/hotspot/share/classfile/vmSymbols.hpp Wed Oct 16 17:00:39 2019 -0400 +++ b/src/hotspot/share/classfile/vmSymbols.hpp Wed Oct 16 17:02:29 2019 -0400 @@ -94,6 +94,7 @@ template(java_lang_reflect_Field, "java/lang/reflect/Field") \ template(java_lang_reflect_Parameter, "java/lang/reflect/Parameter") \ template(java_lang_reflect_Array, "java/lang/reflect/Array") \ + template(java_lang_reflect_RecordComponent, "java/lang/reflect/RecordComponent") \ template(java_lang_StringBuffer, "java/lang/StringBuffer") \ template(java_lang_StringBuilder, "java/lang/StringBuilder") \ template(java_lang_CharSequence, "java/lang/CharSequence") \ @@ -127,6 +128,7 @@ template(jdk_internal_vm_PostVMInitHook, "jdk/internal/vm/PostVMInitHook") \ template(sun_net_www_ParseUtil, "sun/net/www/ParseUtil") \ template(java_util_Iterator, "java/util/Iterator") \ + template(java_lang_Record, "java/lang/Record") \ \ template(jdk_internal_loader_ClassLoaders_AppClassLoader, "jdk/internal/loader/ClassLoaders$AppClassLoader") \ template(jdk_internal_loader_ClassLoaders_PlatformClassLoader, "jdk/internal/loader/ClassLoaders$PlatformClassLoader") \ @@ -159,6 +161,7 @@ template(tag_deprecated, "Deprecated") \ template(tag_source_debug_extension, "SourceDebugExtension") \ template(tag_signature, "Signature") \ + template(tag_record, "Record") \ template(tag_runtime_visible_annotations, "RuntimeVisibleAnnotations") \ template(tag_runtime_invisible_annotations, "RuntimeInvisibleAnnotations") \ template(tag_runtime_visible_parameter_annotations, "RuntimeVisibleParameterAnnotations") \ @@ -559,6 +562,7 @@ template(char_StringBuffer_signature, "(C)Ljava/lang/StringBuffer;") \ template(int_String_signature, "(I)Ljava/lang/String;") \ template(boolean_boolean_int_signature, "(ZZ)I") \ + template(reflect_method_signature, "Ljava/lang/reflect/Method;") \ /* signature symbols needed by intrinsics */ \ VM_INTRINSICS_DO(VM_INTRINSIC_IGNORE, VM_SYMBOL_IGNORE, VM_SYMBOL_IGNORE, template, VM_ALIAS_IGNORE) \ \
--- a/src/hotspot/share/include/jvm.h Wed Oct 16 17:00:39 2019 -0400 +++ b/src/hotspot/share/include/jvm.h Wed Oct 16 17:02:29 2019 -0400 @@ -522,6 +522,7 @@ JNIEXPORT jobjectArray JNICALL JVM_GetClassDeclaredConstructors(JNIEnv *env, jclass ofClass, jboolean publicOnly); + /* Differs from JVM_GetClassModifiers in treatment of inner classes. This returns the access flags for the class as specified in the class file rather than searching the InnerClasses attribute (if @@ -542,6 +543,14 @@ JNIEXPORT jobjectArray JNICALL JVM_GetNestMembers(JNIEnv *env, jclass current); +/* Records - since JDK 14 */ + +JNIEXPORT jboolean JNICALL +JVM_IsRecord(JNIEnv *env, jclass cls); + +JNIEXPORT jobjectArray JNICALL +JVM_GetRecordComponents(JNIEnv *env, jclass ofClass); + /* The following two reflection routines are still needed due to startup time issues */ /* * java.lang.reflect.Method
--- a/src/hotspot/share/memory/allocation.hpp Wed Oct 16 17:00:39 2019 -0400 +++ b/src/hotspot/share/memory/allocation.hpp Wed Oct 16 17:02:29 2019 -0400 @@ -305,7 +305,8 @@ f(ConstantPool) \ f(ConstantPoolCache) \ f(Annotations) \ - f(MethodCounters) + f(MethodCounters) \ + f(RecordComponent) #define METASPACE_OBJ_TYPE_DECLARE(name) name ## Type, #define METASPACE_OBJ_TYPE_NAME_CASE(name) case name ## Type: return #name;
--- a/src/hotspot/share/memory/heapInspection.hpp Wed Oct 16 17:00:39 2019 -0400 +++ b/src/hotspot/share/memory/heapInspection.hpp Wed Oct 16 17:02:29 2019 -0400 @@ -88,6 +88,8 @@ "Number of bytes used by the InstanceKlass::inner_classes() array") \ f(nest_members_bytes, IK_nest_members, \ "Number of bytes used by the InstanceKlass::nest_members() array") \ + f(record_components_bytes, IK_record_components, \ + "Number of bytes used by the InstanceKlass::record_components() array") \ f(signers_bytes, IK_signers, \ "Number of bytes used by the InstanceKlass::singers() array") \ f(class_annotations_bytes, class_annotations, \
--- a/src/hotspot/share/memory/metaspaceShared.cpp Wed Oct 16 17:00:39 2019 -0400 +++ b/src/hotspot/share/memory/metaspaceShared.cpp Wed Oct 16 17:02:29 2019 -0400 @@ -835,6 +835,7 @@ case MetaspaceObj::ConstantPoolCacheType: case MetaspaceObj::AnnotationsType: case MetaspaceObj::MethodCountersType: + case MetaspaceObj::RecordComponentType: // These have no vtables. break; case MetaspaceObj::ClassType:
--- a/src/hotspot/share/oops/instanceKlass.cpp Wed Oct 16 17:00:39 2019 -0400 +++ b/src/hotspot/share/oops/instanceKlass.cpp Wed Oct 16 17:02:29 2019 -0400 @@ -435,6 +435,7 @@ _nest_members(NULL), _nest_host_index(0), _nest_host(NULL), + _record_components(NULL), _static_field_size(parser.static_field_size()), _nonstatic_oop_map_size(nonstatic_oop_map_size(parser.total_oop_map_count())), _itable_len(parser.itable_size()), @@ -497,6 +498,18 @@ } } +void InstanceKlass::deallocate_record_components(ClassLoaderData* loader_data, + Array<RecordComponent*>* record_components) { + if (record_components != NULL && !record_components->is_shared()) { + for (int i = 0; i < record_components->length(); i++) { + RecordComponent* record_component = record_components->at(i); + if (record_component == NULL) continue; // maybe null if error processing + MetadataFactory::free_metadata(loader_data, record_component); + } + MetadataFactory::free_array<RecordComponent*>(loader_data, record_components); + } +} + // This function deallocates the metadata and C heap pointers that the // InstanceKlass points to. void InstanceKlass::deallocate_contents(ClassLoaderData* loader_data) { @@ -525,6 +538,9 @@ deallocate_methods(loader_data, methods()); set_methods(NULL); + deallocate_record_components(loader_data, record_components()); + set_record_components(NULL); + if (method_ordering() != NULL && method_ordering() != Universe::the_empty_int_array() && !method_ordering()->is_shared()) { @@ -2313,6 +2329,7 @@ } it->push(&_nest_members); + it->push(&_record_components); } void InstanceKlass::remove_unshareable_info() { @@ -3228,6 +3245,8 @@ } st->print(BULLET"inner classes: "); inner_classes()->print_value_on(st); st->cr(); st->print(BULLET"nest members: "); nest_members()->print_value_on(st); st->cr(); + // TBD - need to check for NULL? + st->print(BULLET"record components: "); record_components()->print_value_on(st); st->cr(); if (java_mirror() != NULL) { st->print(BULLET"java mirror: "); java_mirror()->print_value_on(st); @@ -3490,6 +3509,7 @@ n += (sz->_fields_bytes = sz->count_array(fields())); n += (sz->_inner_classes_bytes = sz->count_array(inner_classes())); n += (sz->_nest_members_bytes = sz->count_array(nest_members())); + n += (sz->_record_components_bytes = sz->count_array(record_components())); sz->_ro_bytes += n; const ConstantPool* cp = constants(); @@ -3512,6 +3532,17 @@ } } } + + const Array<RecordComponent*>* components = record_components(); + if (components != NULL) { + for (int i = 0; i < components->length(); i++) { + RecordComponent* component = components->at(i); + if (component) { + component->collect_statistics(sz); + } + } + } + } #endif // INCLUDE_SERVICES
--- a/src/hotspot/share/oops/instanceKlass.hpp Wed Oct 16 17:00:39 2019 -0400 +++ b/src/hotspot/share/oops/instanceKlass.hpp Wed Oct 16 17:02:29 2019 -0400 @@ -32,6 +32,7 @@ #include "oops/fieldInfo.hpp" #include "oops/instanceOop.hpp" #include "oops/klassVtable.hpp" +#include "oops/recordComponent.hpp" #include "runtime/handles.hpp" #include "runtime/os.hpp" #include "utilities/accessFlags.hpp" @@ -182,6 +183,9 @@ // By always being set it makes nest-member access checks simpler. InstanceKlass* _nest_host; + // The contents of the Record attribute. + Array<RecordComponent*>* _record_components; + // the source debug extension for this klass, NULL if not specified. // Specified as UTF-8 string without terminating zero byte in the classfile, // it is stored in the instanceklass as a NULL-terminated UTF-8 string @@ -446,9 +450,17 @@ jushort nest_host_index() const { return _nest_host_index; } void set_nest_host_index(u2 i) { _nest_host_index = i; } + // record components + Array<RecordComponent*>* record_components() const { return _record_components; } + void set_record_components(Array<RecordComponent*>* record_components) { + _record_components = record_components; + } + bool is_record() const { return _record_components != NULL; } + private: // Called to verify that k is a member of this nest - does not look at k's nest-host bool has_nest_member(InstanceKlass* k, TRAPS) const; + public: // Returns nest-host class, resolving and validating it if needed // Returns NULL if an exception occurs during loading, or validation fails @@ -1142,6 +1154,8 @@ const Klass* super_klass, Array<InstanceKlass*>* local_interfaces, Array<InstanceKlass*>* transitive_interfaces); + void static deallocate_record_components(ClassLoaderData* loader_data, + Array<RecordComponent*>* record_component); // The constant pool is on stack if any of the methods are executing or // referenced by handles.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hotspot/share/oops/recordComponent.cpp Wed Oct 16 17:02:29 2019 -0400 @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#include "precompiled.hpp" +#include "logging/log.hpp" +#include "memory/metadataFactory.hpp" +#include "memory/metaspace.hpp" +#include "memory/metaspaceClosure.hpp" +#include "oops/annotations.hpp" +#include "oops/instanceKlass.hpp" +#include "oops/recordComponent.hpp" +#include "utilities/globalDefinitions.hpp" + +RecordComponent* RecordComponent::allocate(ClassLoaderData* loader_data, + u2 name_index, u2 descriptor_index, + u2 attributes_count, + u2 generic_signature_index, + AnnotationArray* annotations, + AnnotationArray* type_annotations, TRAPS) { + return new (loader_data, size(), MetaspaceObj::RecordComponentType, THREAD) + RecordComponent(name_index, descriptor_index, attributes_count, + generic_signature_index, annotations, type_annotations); +} + +void RecordComponent::deallocate_contents(ClassLoaderData* loader_data) { + if (annotations() != NULL) { + MetadataFactory::free_array<u1>(loader_data, annotations()); + } + if (type_annotations() != NULL) { + MetadataFactory::free_array<u1>(loader_data, type_annotations()); + } +} + +void RecordComponent::metaspace_pointers_do(MetaspaceClosure* it) { + log_trace(cds)("Iter(RecordComponent): %p", this); + it->push(&_annotations); + it->push(&_type_annotations); +} + +void RecordComponent::print_value_on(outputStream* st) const { + st->print("RecordComponent(" INTPTR_FORMAT ")", p2i(this)); +} + +#ifndef PRODUCT +void RecordComponent::print_on(outputStream* st) const { + st->print("name_index: %d", _name_index); + st->print(" - descriptor_index: %d", _descriptor_index); + st->print(" - attributes_count: %d", _attributes_count); + if (_generic_signature_index != 0) { + st->print(" - generic_signature_index: %d", _generic_signature_index); + } + st->cr(); + if (_annotations != NULL) { + st->print_cr("record component annotations"); + _annotations->print_value_on(st); + } + if (_type_annotations != NULL) { + st->print_cr("record component type annotations"); + _type_annotations->print_value_on(st); + } +} +#endif // PRODUCT
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/hotspot/share/oops/recordComponent.hpp Wed Oct 16 17:02:29 2019 -0400 @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +#ifndef SHARE_OOPS_RECORDCOMPONENT_HPP +#define SHARE_OOPS_RECORDCOMPONENT_HPP + +#include "memory/heapInspection.hpp" +#include "oops/annotations.hpp" +#include "oops/metadata.hpp" +#include "utilities/globalDefinitions.hpp" + +// This class stores information extracted from the Record class attribute. +class RecordComponent: public MetaspaceObj { + private: + AnnotationArray* _annotations; + AnnotationArray* _type_annotations; + u2 _name_index; + u2 _descriptor_index; + u2 _attributes_count; + + // generic_signature_index gets set if the Record component has a Signature + // attribute. A zero value indicates that there was no Signature attribute. + u2 _generic_signature_index; + + public: + RecordComponent(u2 name_index, u2 descriptor_index, u2 attributes_count, + u2 generic_signature_index, AnnotationArray* annotations, + AnnotationArray* type_annotations): + _annotations(annotations), _type_annotations(type_annotations), + _name_index(name_index), _descriptor_index(descriptor_index), + _attributes_count(attributes_count), + _generic_signature_index(generic_signature_index) { } + + // Allocate instance of this class + static RecordComponent* allocate(ClassLoaderData* loader_data, + u2 name_index, u2 descriptor_index, + u2 attributes_count, + u2 generic_signature_index, + AnnotationArray* annotations, + AnnotationArray* type_annotations, TRAPS); + + void deallocate_contents(ClassLoaderData* loader_data); + + u2 name_index() const { return _name_index; } + + u2 descriptor_index() const { return _descriptor_index; } + + u2 attributes_count() const { return _attributes_count; } + + u2 generic_signature_index() const { return _generic_signature_index; } + + AnnotationArray* annotations() const { return _annotations; } + AnnotationArray* type_annotations() const { return _type_annotations; } + + // Size of RecordComponent, not including size of any annotations. + static int size() { return sizeof(RecordComponent) / wordSize; } + + void metaspace_pointers_do(MetaspaceClosure* it); + MetaspaceObj::Type type() const { return RecordComponentType; } + + // Record_components should be stored in the read-only region of CDS archive. + static bool is_read_only_by_default() { return true; } + DEBUG_ONLY(bool on_stack() { return false; }) // for template + +#if INCLUDE_SERVICES + void collect_statistics(KlassSizeStats *sz) const { + // TBD is this right? + if (_annotations != NULL) { + sz->_annotations_bytes += sz->count(_annotations); + sz->_ro_bytes += sz->count(_annotations); + } + if (_type_annotations != NULL) { + sz->_annotations_bytes += sz->count(_type_annotations); + sz->_ro_bytes += sz->count(_type_annotations); + } + } +#endif + + bool is_klass() const { return false; } + +#ifndef PRODUCT + void print_on(outputStream* st) const; +#endif + void print_value_on(outputStream* st) const; + +}; + +#endif // SHARE_OOPS_RECORDCOMPONENT_HPP
--- a/src/hotspot/share/prims/jvm.cpp Wed Oct 16 17:00:39 2019 -0400 +++ b/src/hotspot/share/prims/jvm.cpp Wed Oct 16 17:02:29 2019 -0400 @@ -50,6 +50,7 @@ #include "oops/fieldStreams.hpp" #include "oops/instanceKlass.hpp" #include "oops/method.hpp" +#include "oops/recordComponent.hpp" #include "oops/objArrayKlass.hpp" #include "oops/objArrayOop.inline.hpp" #include "oops/oop.inline.hpp" @@ -1680,6 +1681,46 @@ } JVM_END +JVM_ENTRY(jboolean, JVM_IsRecord(JNIEnv *env, jclass cls)) + JVMWrapper("JVM_IsRecord"); + InstanceKlass* k = InstanceKlass::cast(java_lang_Class::as_Klass(JNIHandles::resolve_non_null(cls))); + return k->is_record(); +JVM_END + +JVM_ENTRY(jobjectArray, JVM_GetRecordComponents(JNIEnv* env, jclass ofClass)) +{ + JVMWrapper("JVM_GetRecordComponents"); + Klass* c = java_lang_Class::as_Klass(JNIHandles::resolve_non_null(ofClass)); + assert(c->is_instance_klass(), "must be"); + InstanceKlass* ik = InstanceKlass::cast(c); + + if (ik->is_record()) { + Array<RecordComponent*>* components = ik->record_components(); + assert(components != NULL, "components should not be NULL"); + { + JvmtiVMObjectAllocEventCollector oam; + constantPoolHandle cp(THREAD, ik->constants()); + int length = components->length(); + assert(length >= 0, "unexpected record_components length"); + objArrayOop record_components = + oopFactory::new_objArray(SystemDictionary::RecordComponent_klass(), length, CHECK_NULL); + objArrayHandle components_h (THREAD, record_components); + + for (int x = 0; x < length; x++) { + RecordComponent* component = components->at(x); + assert(component != NULL, "unexpected NULL record component"); + oop component_oop = java_lang_reflect_RecordComponent::create(ik, component, CHECK_NULL); + components_h->obj_at_put(x, component_oop); + } + return (jobjectArray)JNIHandles::make_local(components_h()); + } + } + + objArrayOop result = oopFactory::new_objArray(SystemDictionary::RecordComponent_klass(), 0, CHECK_NULL); + return (jobjectArray)JNIHandles::make_local(env, result); +} +JVM_END + static bool select_method(const methodHandle& method, bool want_constructor) { if (want_constructor) { return (method->is_initializer() && !method->is_static());
--- a/src/hotspot/share/prims/jvmtiClassFileReconstituter.cpp Wed Oct 16 17:00:39 2019 -0400 +++ b/src/hotspot/share/prims/jvmtiClassFileReconstituter.cpp Wed Oct 16 17:02:29 2019 -0400 @@ -27,6 +27,7 @@ #include "interpreter/bytecodeStream.hpp" #include "memory/universe.hpp" #include "oops/fieldStreams.hpp" +#include "oops/recordComponent.hpp" #include "prims/jvmtiClassFileReconstituter.hpp" #include "runtime/handles.inline.hpp" #include "runtime/signature.hpp" @@ -423,6 +424,57 @@ } } +// Record { +// u2 attribute_name_index; +// u4 attribute_length; +// u2 components_count; +// component_info components[components_count]; +// } +// component_info { +// u2 name_index; +// u2 descriptor_index +// u2 attributs_count; +// attribute_info_attributes[attributes_count]; +// } +void JvmtiClassFileReconstituter::write_record_attribute() { + Array<RecordComponent*>* components = ik()->record_components(); + int number_of_components = components->length(); + + // Each component has a u2 for name, descr, attribute count + int length = sizeof(u2) + (sizeof(u2) * 3 * number_of_components); + for (int x = 0; x < number_of_components; x++) { + RecordComponent* component = components->at(x); + if (component->generic_signature_index() != 0) { + length += 8; // Signature attribute size + assert(component->attributes_count() > 0, "Bad component attributes count"); + } + if (component->annotations() != NULL) { + length += 6 + component->annotations()->length(); + } + if (component->type_annotations() != NULL) { + length += 6 + component->type_annotations()->length(); + } + } + + write_attribute_name_index("Record"); + write_u4(length); + write_u2(number_of_components); + for (int i = 0; i < number_of_components; i++) { + RecordComponent* component = components->at(i); + write_u2(component->name_index()); + write_u2(component->descriptor_index()); + write_u2(component->attributes_count()); + if (component->generic_signature_index() != 0) { + write_signature_attribute(component->generic_signature_index()); + } + if (component->annotations() != NULL) { + write_annotations_attribute("RuntimeVisibleAnnotations", component->annotations()); + } + if (component->type_annotations() != NULL) { + write_annotations_attribute("RuntimeVisibleTypeAnnotations", component->type_annotations()); + } + } +} // Write InnerClasses attribute // JVMSpec| InnerClasses_attribute { @@ -699,6 +751,9 @@ if (ik()->nest_members() != Universe::the_empty_short_array()) { ++attr_count; } + if (ik()->record_components() != NULL) { + ++attr_count; + } write_u2(attr_count); @@ -729,6 +784,9 @@ if (ik()->nest_members() != Universe::the_empty_short_array()) { write_nest_members_attribute(); } + if (ik()->record_components() != NULL) { + write_record_attribute(); + } } // Write the method information portion of ClassFile structure
--- a/src/hotspot/share/prims/jvmtiClassFileReconstituter.hpp Wed Oct 16 17:00:39 2019 -0400 +++ b/src/hotspot/share/prims/jvmtiClassFileReconstituter.hpp Wed Oct 16 17:02:29 2019 -0400 @@ -118,6 +118,7 @@ void write_bootstrapmethod_attribute(); void write_nest_host_attribute(); void write_nest_members_attribute(); + void write_record_attribute(); address writeable_address(size_t size); void write_u1(u1 x);
--- a/src/hotspot/share/prims/jvmtiRedefineClasses.cpp Wed Oct 16 17:00:39 2019 -0400 +++ b/src/hotspot/share/prims/jvmtiRedefineClasses.cpp Wed Oct 16 17:02:29 2019 -0400 @@ -292,6 +292,11 @@ if (InstanceKlass::cast(k)->is_unsafe_anonymous()) { return false; } + + // Cannot redefine or retransform a record. + if (InstanceKlass::cast(k)->is_record()) { + /* TBD: can we support redefining a record with annotations ? */ return false; + } return true; }
--- a/src/java.base/share/classes/java/io/ObjectInputStream.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/java.base/share/classes/java/io/ObjectInputStream.java Wed Oct 16 17:02:29 2019 -0400 @@ -26,7 +26,9 @@ package java.io; import java.io.ObjectStreamClass.WeakClassKey; +import java.io.ObjectStreamClass.RecordReflector; 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,34 @@ * Similarly, any serialPersistentFields or serialVersionUID field declarations * are also ignored--all enum types have a fixed serialVersionUID of 0L. * + * @apiNote + * 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 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 or + * serialVersionUID field declarations are also ignored -- all record classes + * have a fixed serialVersionUID of 0L. + * * @author Mike Warres * @author Roger Riggs * @see java.io.DataInput @@ -2085,7 +2115,12 @@ handles.markException(passHandle, resolveEx); } - if (desc.isExternalizable()) { + final boolean isRecord = cl != null && cl.isRecord() ? 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 +2206,43 @@ */ } + /** 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); + + // lookup the canonical constructor + MethodHandle ctrMH = RecordReflector.canonicalCtr(desc.forClass()); + + // bind the stream field values + ctrMH = RecordReflector.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 +2389,7 @@ } } - private class FieldValues { + /*package-private*/ class FieldValues { final byte[] primValues; final Object[] objValues;
--- a/src/java.base/share/classes/java/io/ObjectOutputStream.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/java.base/share/classes/java/io/ObjectOutputStream.java Wed Oct 16 17:02:29 2019 -0400 @@ -150,6 +150,26 @@ * defaultWriteObject and writeFields initially terminate any existing * block-data record. * + * @apiNote + * 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). + * The process by which record objects are serialized 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 or + * serialVersionUID field declarations are also ignored -- all record classes + * have a fixed serialVersionUID of`0L. + * * @author Mike Warres * @author Roger Riggs * @see java.io.DataOutput @@ -1431,7 +1451,11 @@ bout.writeByte(TC_OBJECT); writeClassDesc(desc, false); handles.assign(unshared ? null : obj); - if (desc.isExternalizable() && !desc.isProxy()) { + + final boolean isRecord = obj.getClass().isRecord() ? true : false; + if (isRecord) { + writeRecordData(obj,desc); + } else if (desc.isExternalizable() && !desc.isProxy()) { writeExternalData((Externalizable) obj); } else { writeSerialData(obj, desc); @@ -1475,6 +1499,18 @@ curPut = oldPut; } + /** Writes the record component values for the given record object. */ + private void writeRecordData(Object obj, ObjectStreamClass desc) + throws IOException + { + assert obj.getClass().isRecord(); + ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout(); + if (slots.length != 1) + throw new InternalError("expected slot length: " + slots.length); + + defaultWriteFields(obj, desc); // TODO: use record accessors + } + /** * Writes instance data for each serializable class of given object, from * superclass to subclass.
--- a/src/java.base/share/classes/java/io/ObjectStreamClass.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/java.base/share/classes/java/io/ObjectStreamClass.java Wed Oct 16 17:02:29 2019 -0400 @@ -25,6 +25,9 @@ package java.io; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.ref.SoftReference; @@ -32,6 +35,7 @@ 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 +48,8 @@ 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 +129,8 @@ 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 */ @@ -475,6 +483,7 @@ name = cl.getName(); isProxy = Proxy.isProxyClass(cl); isEnum = Enum.class.isAssignableFrom(cl); + isRecord = cl.isRecord(); serializable = Serializable.class.isAssignableFrom(cl); externalizable = Externalizable.class.isAssignableFrom(cl); @@ -495,7 +504,11 @@ return null; } + if (isRecord) { + suid = 0L; + } else { suid = getDeclaredSUID(cl); + } try { fields = getSerialFields(cl); computeFieldOffsets(); @@ -505,7 +518,9 @@ fields = NO_FIELDS; } - if (externalizable) { + if (isRecord) { + cons = null; // ctr will be found later + } else if (externalizable) { cons = getExternalizableConstructor(cl); } else { cons = getSerializableConstructor(cl); @@ -542,16 +557,18 @@ 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"); } } + if (!isRecord) { 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 +699,7 @@ } if (model.serializable == osc.serializable && - !cl.isArray() && + !cl.isArray() && !cl.isRecord() && suid != osc.getSerialVersionUID()) { throw new InvalidClassException(osc.name, "local class incompatible: " + @@ -714,6 +731,9 @@ } this.cl = cl; + if (cl != null) { + this.isRecord = cl.isRecord(); + } this.resolveEx = resolveEx; this.superDesc = superDesc; name = model.name; @@ -739,12 +759,14 @@ deserializeEx = localDesc.deserializeEx; } domains = localDesc.domains; + assert cl.isRecord() ? 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; } @@ -1641,12 +1663,16 @@ 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 (cl.isRecord()) { + 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 +2464,133 @@ } } } + + /** A reflector implementation for record classes. */ + static final class RecordReflector { + + // TODO: add cache to avoid subsequent reflective calls for the same record class + + /** Returns the canonical constructor for the given record class. */ + static MethodHandle canonicalCtr(Class<?> cls) { + assert cls.isRecord() : "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) { + throw new InternalError("should not reach here", e); + } + }; + return AccessController.doPrivileged(pa); + } + + /** Binds the given stream field values to the given method handle. */ + 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); + } + } }
--- a/src/java.base/share/classes/java/lang/Class.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/java.base/share/classes/java/lang/Class.java Wed Oct 16 17:02:29 2019 -0400 @@ -46,6 +46,7 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Proxy; +import java.lang.reflect.RecordComponent; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.lang.constant.Constable; @@ -2267,6 +2268,57 @@ return copyFields(privateGetDeclaredFields(false)); } + /** + * Returns an array containing {@code RecordComponent} objects reflecting all the + * declared record components of the record represented by this {@code Class} object. + * The components are returned in the same order that they are declared in the + * record header. + * + * @return The array of {@code RecordComponent} objects representing all the + * record components of this record. The array is empty if this class + * is not a record, or if this class is a record with no components. + * @throws SecurityException + * If a security manager, <i>s</i>, is present and any of the + * following conditions is met: + * + * <ul> + * + * <li> the caller's class loader is not the same as the + * class loader of this class and invocation of + * {@link SecurityManager#checkPermission + * s.checkPermission} method with + * {@code RuntimePermission("accessDeclaredMembers")} + * denies access to the declared methods within this class + * + * <li> the caller's class loader is not the same as or an + * ancestor of the class loader for the current class and + * invocation of {@link SecurityManager#checkPackageAccess + * s.checkPackageAccess()} denies access to the package + * of this class + * + * </ul> + * + * @since 14 + */ + @CallerSensitive + public RecordComponent[] getRecordComponents() throws SecurityException { + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + checkMemberAccess(sm, Member.DECLARED, Reflection.getCallerClass(), true); + } + if (isPrimitive() || isArray()) { + return new RecordComponent[0]; + } + Object[] recordComponents = getRecordComponents0(); + if (recordComponents == null || recordComponents.length == 0) { + return new RecordComponent[0]; + } + RecordComponent[] result = new RecordComponent[recordComponents.length]; + for (int i = 0; i < recordComponents.length; i++) { + result[i] = (RecordComponent)recordComponents[i]; + } + return result; + } /** * Returns an array containing {@code Method} objects reflecting all the @@ -3425,6 +3477,9 @@ private native Method[] getDeclaredMethods0(boolean publicOnly); private native Constructor<T>[] getDeclaredConstructors0(boolean publicOnly); private native Class<?>[] getDeclaredClasses0(); + private native String[] getRecordComponentNames0(); + private native Object[] getRecordComponents0(); + private native boolean isRecord0(); /** * Helper method to get the method name from arguments. @@ -3531,6 +3586,18 @@ this.getSuperclass() == java.lang.Enum.class; } + /** + * Returns true if and only if this class was declared as a record in the + * source code. + * + * @return true if and only if this class was declared as a record in the + * source code + * @since 1.12 + */ + public boolean isRecord() { + return isRecord0(); + } + // Fetches the factory for reflective objects private static ReflectionFactory getReflectionFactory() { if (reflectionFactory == null) {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/java.base/share/classes/java/lang/Record.java Wed Oct 16 17:02:29 2019 -0400 @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package java.lang; + +/** + * This is the common base class of all Java language record classes. + * + * <p>More information about records, including descriptions of the + * implicitly declared methods synthesized by the compiler, can be + * found in section 8.10 of + * <cite>The Java™ Language Specification</cite>. + * + * <p>A <em>record class</em> is a shallowly immutable, transparent carrier for + * a fixed set of values, called the <em>record components</em>. The Java™ + * language provides concise syntax for declaring record classes, whereby the + * record components are declared in the record header. The list of record + * components declared in the record header form the <em>record descriptor</em>. + * + * <p>A record class has the following mandated members: a public <em>canonical + * constructor</em>, whose descriptor is the same as the record descriptor; + * a private final field corresponding to each component, whose name and + * type are the same as that of the component; a public accessor method + * corresponding to each component, whose name and return type are the same as + * that of the component. If not explicitly declared in the body of the record, + * implicit implementations for these members are provided. + * + * <p>The implicit declaration of the canonical constructor initializes the + * component fields from the corresponding constructor arguments. The implicit + * declaration of the accessor methods returns the value of the corresponding + * component field. The implicit declaration of the {@link Object#equals(Object)}, + * {@link Object#hashCode()}, and {@link Object#toString()} methods are derived + * from all of the component fields. + * + * <p>The primary reasons to provide an explicit declaration for the + * canonical constructor or accessor methods are to validate constructor + * arguments, perform defensive copies on mutable components, or normalize groups + * of components (such as reducing a rational number to lowest terms.) + * + * <p>For all record classes, the following invariant must hold: if a record R's + * components are {@code c1, c2, ... cn}, then if a record instance is copied + * as follows: + * <pre> + * R copy = new R(r.c1(), r.c2(), ..., r.cn()); + * </pre> + * then it must be the case that {@code r.equals(copy)}. + * + * @jls 8.10 + * @since 14 + */ +public abstract class Record { + /** + * Indicates whether some other object is "equal to" this one. In addition + * to the general contract of {@link Object#equals(Object)}, + * record classes must further participate in the invariant that when + * a record instance is "copied" by passing the result of the record component + * accessor methods to the canonical constructor, as follows: + * <pre> + * R copy = new R(r.c1(), r.c2(), ..., r.cn()); + * </pre> + * then it must be the case that {@code r.equals(copy)}. + * + * @implNote + * The implicitly provided implementation returns {@code true} if and + * only if the argument is an instance of the same record type as this object, + * and each component of this record is equal to the corresponding component + * of the argument, according to {@link java.util.Objects#equals(Object,Object)} for components + * whose types are reference types, and the primitive wrapper equality + * comparison for components whose types are primitive types. + * + * @see java.util.Objects#equals(Object,Object) + * + * @param obj the reference object with which to compare. + * @return {@code true} if this object is the same as the obj + * argument; {@code false} otherwise. + */ + @Override + public abstract boolean equals(Object obj); + + /** + * {@inheritDoc} + * + * @implNote + * The implicitly provided implementation returns a hash code value derived + * by combining the hash code value for all the components, according to + * {@link Object#hashCode()} for components whose types are reference types, + * or the primitive wrapper hash code for components whose types are primitive + * types. + * + * @see Object#hashCode() + * + * @return a hash code value for this object. + */ + @Override + public abstract int hashCode(); + + /** + * {@inheritDoc} + * + * @implNote + * The implicitly provided implementation returns a string that is derived + * from the name of the record class and the names and string representations + * of all the components, according to {@link Object#toString()} for components + * whose types are reference types, and the primitive wrapper {@code toString} + * method for components whose types are primitive types. + * + * @see Object#toString() () + * + * @return a string representation of the object. + */ + @Override + public abstract String toString(); +}
--- a/src/java.base/share/classes/java/lang/annotation/ElementType.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/java.base/share/classes/java/lang/annotation/ElementType.java Wed Oct 16 17:02:29 2019 -0400 @@ -114,5 +114,12 @@ * * @since 9 */ - MODULE + MODULE, + + /** + * Record component + * + * @since 14 + */ + RECORD_COMPONENT; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/java.base/share/classes/java/lang/reflect/RecordComponent.java Wed Oct 16 17:02:29 2019 -0400 @@ -0,0 +1,246 @@ +package java.lang.reflect; + +import jdk.internal.access.SharedSecrets; +import sun.reflect.annotation.AnnotationParser; +import sun.reflect.annotation.TypeAnnotation; +import sun.reflect.annotation.TypeAnnotationParser; +import sun.reflect.generics.factory.CoreReflectionFactory; +import sun.reflect.generics.factory.GenericsFactory; +import sun.reflect.generics.repository.FieldRepository; +import sun.reflect.generics.scope.ClassScope; + +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.Map; +import java.util.Objects; + +/** + * A {@code RecordComponent} provides information about, and dynamic access to, a + * record component in a record class. Record components can only be created by the VM + * runtime, thus no public constructor is provided. + * + * @see AnnotatedElement + * @see java.lang.Class + * + * @since 14 + */ +public final +class RecordComponent implements AnnotatedElement { + // declaring class + private Class<?> clazz; + private String name; + private Class<?> type; + private Method accessor; + private String signature; + // generic info repository; lazily initialized + private transient FieldRepository genericInfo; + private byte[] annotations; + private byte[] typeAnnotations; + private RecordComponent root; + + // only the JVM can create record components + private RecordComponent() {} + + /** + * Returns the name of the record component represented by this {@code RecordComponent} object. + * + * @return the name of the record component represented by this {@code RecordComponent} object. + */ + public String getName() { + return name; + } + + private Class<?> getDeclaringClass() { + return clazz; + } + + /** + * Returns a {@code Class} object that identifies the + * declared type for the record component represented by this + * {@code RecordComponent} object. + * + * @return a {@code Class} object identifying the declared + * type of the record component represented by this object + */ + public Class<?> getType() { + return type; + } + + /** + * Returns a {@code String} object that identifies the + * generic type. + * + * @return a {@code String} object identifying the generic declared + * type of the record component represented by this object + */ + public String getGenericSignature() { + return signature; + } + + /** + * Returns a {@code Type} object that represents the declared type for + * the record component represented by this {@code RecordComponent} object. + * + * <p>If the declared type of the record component is a parameterized type, + * the {@code Type} object returned must accurately reflect the + * actual type arguments used in the source code. + * + * <p>If the type of the underlying record component is a type variable or a + * parameterized type, it is created. Otherwise, it is resolved. + * + * @return a {@code Type} object that represents the declared type for + * the record component represented by this {@code RecordComponent} object + * @throws GenericSignatureFormatError if the generic record component + * signature does not conform to the format specified in + * <cite>The Java™ Virtual Machine Specification</cite> + * @throws TypeNotPresentException if the generic type + * signature of the underlying record component refers to a non-existent + * type declaration + * @throws MalformedParameterizedTypeException if the generic + * signature of the underlying record component refers to a parameterized type + * that cannot be instantiated for any reason + */ + public Type getGenericType() { + if (getGenericSignature() != null) + return getGenericInfo().getGenericType(); + else + return getType(); + } + + // Accessor for generic info repository + private FieldRepository getGenericInfo() { + // lazily initialize repository if necessary + if (genericInfo == null) { + // create and cache generic info repository + genericInfo = FieldRepository.make(getGenericSignature(), + getFactory()); + } + return genericInfo; //return cached repository + } + + // Accessor for factory + private GenericsFactory getFactory() { + Class<?> c = getDeclaringClass(); + // create scope and factory + return CoreReflectionFactory.make(c, ClassScope.make(c)); + } + + /** + * Returns an {@code AnnotatedType} object that represents the use of a type to specify + * the annotated type of the record component represented by this + * {@code RecordComponent}. + * + * @return an object representing the declared type of the record component + * represented by this {@code RecordComponent} + */ + public AnnotatedType getAnnotatedType() { + if (typeAnnotations != null) { + // debug + // System.out.println("length of type annotations " + typeAnnotations.length); + } + return TypeAnnotationParser.buildAnnotatedType(typeAnnotations, + SharedSecrets.getJavaLangAccess(). + getConstantPool(getDeclaringClass()), + this, + getDeclaringClass(), + getGenericType(), + TypeAnnotation.TypeAnnotationTarget.FIELD); + } + + /** + * Returns a {@code Method} object that represents the accessor for the + * record component represented by this {@code RecordComponent} object. + * + * @return a {@code Method} object that represents the accessor for the + * record component represented by this {@code RecordComponent} object. + */ + public Method getAccessor() { + return accessor; + } + + /** + * @throws NullPointerException {@inheritDoc} + */ + @Override + public <T extends Annotation> T getAnnotation(Class<T> annotationClass) { + Objects.requireNonNull(annotationClass); + return annotationClass.cast(declaredAnnotations().get(annotationClass)); + } + + private transient volatile Map<Class<? extends Annotation>, Annotation> declaredAnnotations; + + private Map<Class<? extends Annotation>, Annotation> declaredAnnotations() { + Map<Class<? extends Annotation>, Annotation> declAnnos; + if ((declAnnos = declaredAnnotations) == null) { + synchronized (this) { + if ((declAnnos = declaredAnnotations) == null) { + RecordComponent root = this.root; + if (root != null) { + declAnnos = root.declaredAnnotations(); + } else { + declAnnos = AnnotationParser.parseAnnotations( + annotations, + SharedSecrets.getJavaLangAccess() + .getConstantPool(getDeclaringClass()), + getDeclaringClass()); + } + declaredAnnotations = declAnnos; + } + } + } + return declAnnos; + } + + @Override + public Annotation[] getAnnotations() { + return getDeclaredAnnotations(); + } + + @Override + public Annotation[] getDeclaredAnnotations() { return AnnotationParser.toArray(declaredAnnotations()); } + + /** + * Returns {@code true} if this {@code RecordComponent} was declared with + * variable arity; returns {@code false} otherwise. + * + * @return {@code true} if an only if this {@code RecordComponent} was declared + * with a variable arity. + */ + public boolean isVarArgs() { + RecordComponent[] recordComponents = getDeclaringClass().getRecordComponents(); + if (recordComponents == null || recordComponents.length == 0) { + return false; + } + try { + Constructor<?> canonical = getDeclaringClass() + .getConstructor(Arrays.stream(recordComponents).map(RecordComponent::getAccessor) + .map(Method::getReturnType).toArray(Class<?>[]::new)); + return canonical.isVarArgs() && this.getName().equals(recordComponents[recordComponents.length - 1].getName()); + } catch (NoSuchMethodException nsme) { + throw new IncompatibleClassChangeError( + String.format("a canonical constructor couldn't be found for record %s", + getDeclaringClass().getCanonicalName())); + } + } + + /** + * Returns a string describing this {@code RecordComponent}, including + * its generic type. The format is the access modifiers for the + * record component, always {@code private} and {@code final}, in that + * order, followed by the generic record component type, followed by a + * space, followed by the fully-qualified name of the class declaring + * the record component, followed by a period, followed by the name of + * the record component. + * + * @return a string describing this {@code RecordComponent}, including + * its generic type + */ + public String toGenericString() { + int mod = Modifier.PRIVATE | Modifier.FINAL; + Type type = getGenericType(); + return (((mod == 0) ? "" : (Modifier.toString(mod) + " ")) + + type.getTypeName() + " " + + getDeclaringClass().getTypeName() + "." + + getName()); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/java.base/share/classes/java/lang/runtime/ObjectMethods.java Wed Oct 16 17:02:29 2019 -0400 @@ -0,0 +1,344 @@ +/* + * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.lang.invoke; + +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Objects; + +/** + * Bootstrap methods for state-driven implementations of core methods, + * including {@link Object#equals(Object)}, {@link Object#hashCode()}, and + * {@link Object#toString()}. These methods may be used, for example, by + * Java™ compiler implementations to implement the bodies of {@link Object} + * methods for record clases. + */ +public class ObjectMethods { + + private ObjectMethods() { } + + private static final MethodType DESCRIPTOR_MT = MethodType.methodType(MethodType.class); + private static final MethodType NAMES_MT = MethodType.methodType(List.class); + private static final MethodHandle FALSE = MethodHandles.constant(boolean.class, false); + private static final MethodHandle TRUE = MethodHandles.constant(boolean.class, true); + private static final MethodHandle ZERO = MethodHandles.constant(int.class, 0); + private static final MethodHandle CLASS_IS_INSTANCE; + private static final MethodHandle OBJECT_EQUALS; + private static final MethodHandle OBJECTS_EQUALS; + private static final MethodHandle OBJECTS_HASHCODE; + private static final MethodHandle OBJECTS_TOSTRING; + private static final MethodHandle OBJECT_EQ; + private static final MethodHandle OBJECT_HASHCODE; + private static final MethodHandle OBJECT_TO_STRING; + private static final MethodHandle STRING_FORMAT; + private static final MethodHandle HASH_COMBINER; + + private static final HashMap<Class<?>, MethodHandle> primitiveEquals = new HashMap<>(); + private static final HashMap<Class<?>, MethodHandle> primitiveHashers = new HashMap<>(); + private static final HashMap<Class<?>, MethodHandle> primitiveToString = new HashMap<>(); + + static { + try { + MethodHandles.Lookup publicLookup = MethodHandles.publicLookup(); + MethodHandles.Lookup lookup = MethodHandles.Lookup.IMPL_LOOKUP; + + ClassLoader loader = AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() { + @Override public ClassLoader run() { return ClassLoader.getPlatformClassLoader(); } + }); + + CLASS_IS_INSTANCE = publicLookup.findVirtual(Class.class, "isInstance", + MethodType.methodType(boolean.class, Object.class)); + OBJECT_EQUALS = publicLookup.findVirtual(Object.class, "equals", + MethodType.methodType(boolean.class, Object.class)); + OBJECT_HASHCODE = publicLookup.findVirtual(Object.class, "hashCode", + MethodType.fromMethodDescriptorString("()I", loader)); + OBJECT_TO_STRING = publicLookup.findVirtual(Object.class, "toString", + MethodType.methodType(String.class)); + STRING_FORMAT = publicLookup.findStatic(String.class, "format", + MethodType.methodType(String.class, String.class, Object[].class)); + OBJECTS_EQUALS = publicLookup.findStatic(Objects.class, "equals", + MethodType.methodType(boolean.class, Object.class, Object.class)); + OBJECTS_HASHCODE = publicLookup.findStatic(Objects.class, "hashCode", + MethodType.methodType(int.class, Object.class)); + OBJECTS_TOSTRING = publicLookup.findStatic(Objects.class, "toString", + MethodType.methodType(String.class, Object.class)); + + OBJECT_EQ = lookup.findStatic(ObjectMethods.class, "eq", + MethodType.methodType(boolean.class, Object.class, Object.class)); + HASH_COMBINER = lookup.findStatic(ObjectMethods.class, "hashCombiner", + MethodType.fromMethodDescriptorString("(II)I", loader)); + + primitiveEquals.put(byte.class, lookup.findStatic(ObjectMethods.class, "eq", + MethodType.fromMethodDescriptorString("(BB)Z", loader))); + primitiveEquals.put(short.class, lookup.findStatic(ObjectMethods.class, "eq", + MethodType.fromMethodDescriptorString("(SS)Z", loader))); + primitiveEquals.put(char.class, lookup.findStatic(ObjectMethods.class, "eq", + MethodType.fromMethodDescriptorString("(CC)Z", loader))); + primitiveEquals.put(int.class, lookup.findStatic(ObjectMethods.class, "eq", + MethodType.fromMethodDescriptorString("(II)Z", loader))); + primitiveEquals.put(long.class, lookup.findStatic(ObjectMethods.class, "eq", + MethodType.fromMethodDescriptorString("(JJ)Z", loader))); + primitiveEquals.put(float.class, lookup.findStatic(ObjectMethods.class, "eq", + MethodType.fromMethodDescriptorString("(FF)Z", loader))); + primitiveEquals.put(double.class, lookup.findStatic(ObjectMethods.class, "eq", + MethodType.fromMethodDescriptorString("(DD)Z", loader))); + primitiveEquals.put(boolean.class, lookup.findStatic(ObjectMethods.class, "eq", + MethodType.fromMethodDescriptorString("(ZZ)Z", loader))); + + primitiveHashers.put(byte.class, lookup.findStatic(Byte.class, "hashCode", + MethodType.fromMethodDescriptorString("(B)I", loader))); + primitiveHashers.put(short.class, lookup.findStatic(Short.class, "hashCode", + MethodType.fromMethodDescriptorString("(S)I", loader))); + primitiveHashers.put(char.class, lookup.findStatic(Character.class, "hashCode", + MethodType.fromMethodDescriptorString("(C)I", loader))); + primitiveHashers.put(int.class, lookup.findStatic(Integer.class, "hashCode", + MethodType.fromMethodDescriptorString("(I)I", loader))); + primitiveHashers.put(long.class, lookup.findStatic(Long.class, "hashCode", + MethodType.fromMethodDescriptorString("(J)I", loader))); + primitiveHashers.put(float.class, lookup.findStatic(Float.class, "hashCode", + MethodType.fromMethodDescriptorString("(F)I", loader))); + primitiveHashers.put(double.class, lookup.findStatic(Double.class, "hashCode", + MethodType.fromMethodDescriptorString("(D)I", loader))); + primitiveHashers.put(boolean.class, lookup.findStatic(Boolean.class, "hashCode", + MethodType.fromMethodDescriptorString("(Z)I", loader))); + + primitiveToString.put(byte.class, lookup.findStatic(Byte.class, "toString", + MethodType.methodType(String.class, byte.class))); + primitiveToString.put(short.class, lookup.findStatic(Short.class, "toString", + MethodType.methodType(String.class, short.class))); + primitiveToString.put(char.class, lookup.findStatic(Character.class, "toString", + MethodType.methodType(String.class, char.class))); + primitiveToString.put(int.class, lookup.findStatic(Integer.class, "toString", + MethodType.methodType(String.class, int.class))); + primitiveToString.put(long.class, lookup.findStatic(Long.class, "toString", + MethodType.methodType(String.class, long.class))); + primitiveToString.put(float.class, lookup.findStatic(Float.class, "toString", + MethodType.methodType(String.class, float.class))); + primitiveToString.put(double.class, lookup.findStatic(Double.class, "toString", + MethodType.methodType(String.class, double.class))); + primitiveToString.put(boolean.class, lookup.findStatic(Boolean.class, "toString", + MethodType.methodType(String.class, boolean.class))); + } + catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + private static int hashCombiner(int x, int y) { + return x*31 + y; + } + + private static boolean eq(Object a, Object b) { return a == b; } + private static boolean eq(byte a, byte b) { return a == b; } + private static boolean eq(short a, short b) { return a == b; } + private static boolean eq(char a, char b) { return a == b; } + private static boolean eq(int a, int b) { return a == b; } + private static boolean eq(long a, long b) { return a == b; } + private static boolean eq(float a, float b) { return Float.compare(a, b) == 0; } + private static boolean eq(double a, double b) { return Double.compare(a, b) == 0; } + private static boolean eq(boolean a, boolean b) { return a == b; } + + /** Get the method handle for combining two values of a given type */ + private static MethodHandle equalator(Class<?> clazz) { + return (clazz.isPrimitive() + ? primitiveEquals.get(clazz) + : OBJECTS_EQUALS.asType(MethodType.methodType(boolean.class, clazz, clazz))); + } + + /** Get the hasher for a value of a given type */ + private static MethodHandle hasher(Class<?> clazz) { + return (clazz.isPrimitive() + ? primitiveHashers.get(clazz) + : OBJECTS_HASHCODE.asType(MethodType.methodType(int.class, clazz))); + } + + /** Get the stringifier for a value of a given type */ + private static MethodHandle stringifier(Class<?> clazz) { + return (clazz.isPrimitive() + ? primitiveToString.get(clazz) + : OBJECTS_TOSTRING.asType(MethodType.methodType(String.class, clazz))); + } + + /** + * Generates a method handle for the {@code equals} method for a given data class + * @param receiverClass the data class + * @param getters the list of getters + * @return the method handle + */ + private static MethodHandle makeEquals(Class<?> receiverClass, + List<MethodHandle> getters) { + MethodType rr = MethodType.methodType(boolean.class, receiverClass, receiverClass); + MethodType ro = MethodType.methodType(boolean.class, receiverClass, Object.class); + MethodHandle instanceFalse = MethodHandles.dropArguments(FALSE, 0, receiverClass, Object.class); // (RO)Z + MethodHandle instanceTrue = MethodHandles.dropArguments(TRUE, 0, receiverClass, Object.class); // (RO)Z + MethodHandle isSameObject = OBJECT_EQ.asType(ro); // (RO)Z + MethodHandle isInstance = MethodHandles.dropArguments(CLASS_IS_INSTANCE.bindTo(receiverClass), 0, receiverClass); // (RO)Z + MethodHandle accumulator = MethodHandles.dropArguments(TRUE, 0, receiverClass, receiverClass); // (RR)Z + + for (MethodHandle getter : getters) { + MethodHandle equalator = equalator(getter.type().returnType()); // (TT)Z + MethodHandle thisFieldEqual = MethodHandles.filterArguments(equalator, 0, getter, getter); // (RR)Z + accumulator = MethodHandles.guardWithTest(thisFieldEqual, accumulator, instanceFalse.asType(rr)); + } + + return MethodHandles.guardWithTest(isSameObject, + instanceTrue, + MethodHandles.guardWithTest(isInstance, accumulator.asType(ro), instanceFalse)); + } + + /** + * Generates a method handle for the {@code hashCode} method for a given data class + * @param receiverClass the data class + * @param getters the list of getters + * @return the method handle + */ + private static MethodHandle makeHashCode(Class<?> receiverClass, + List<MethodHandle> getters) { + MethodHandle accumulator = MethodHandles.dropArguments(ZERO, 0, receiverClass); // (R)I + + // @@@ Use loop combinator instead? + for (MethodHandle getter : getters) { + MethodHandle hasher = hasher(getter.type().returnType()); // (T)I + MethodHandle hashThisField = MethodHandles.filterArguments(hasher, 0, getter); // (R)I + MethodHandle combineHashes = MethodHandles.filterArguments(HASH_COMBINER, 0, accumulator, hashThisField); // (RR)I + accumulator = MethodHandles.permuteArguments(combineHashes, accumulator.type(), 0, 0); // adapt (R)I to (RR)I + } + + return accumulator; + } + + /** + * Generates a method handle for the {@code toString} method for a given data class + * @param receiverClass the data class + * @param getters the list of getters + * @param names the names + * @return the method handle + */ + private static MethodHandle makeToString(Class<?> receiverClass, + List<MethodHandle> getters, + List<String> names) { + // This is a pretty lousy algorithm; we spread the receiver over N places, + // apply the N getters, apply N toString operations, and concat the result with String.format + // Better to use String.format directly, or delegate to StringConcatFactory + // Also probably want some quoting around String components + + assert getters.size() == names.size(); + + int[] invArgs = new int[getters.size()]; + Arrays.fill(invArgs, 0); + MethodHandle[] filters = new MethodHandle[getters.size()]; + StringBuilder sb = new StringBuilder(); + sb.append(receiverClass.getSimpleName()).append("["); + for (int i=0; i<getters.size(); i++) { + MethodHandle getter = getters.get(i); // (R)T + MethodHandle stringify = stringifier(getter.type().returnType()); // (T)String + MethodHandle stringifyThisField = MethodHandles.filterArguments(stringify, 0, getter); // (R)String + filters[i] = stringifyThisField; + sb.append(names.get(i)).append("=%s"); + if (i != getters.size() - 1) + sb.append(", "); + } + sb.append(']'); + String formatString = sb.toString(); + MethodHandle formatter = MethodHandles.insertArguments(STRING_FORMAT, 0, formatString) + .asCollector(String[].class, getters.size()); // (R*)String + if (getters.size() == 0) { + // Add back extra R + formatter = MethodHandles.dropArguments(formatter, 0, receiverClass); + } + else { + MethodHandle filtered = MethodHandles.filterArguments(formatter, 0, filters); + formatter = MethodHandles.permuteArguments(filtered, MethodType.methodType(String.class, receiverClass), invArgs); + } + + return formatter; + } + + /** + * Bootstrap method to generate the {@link Object#equals(Object)}, + * {@link Object#hashCode()}, and {@link Object#toString()} methods, based + * on a description of the component names and accessor methods, for either + * {@code invokedynamic} call sites or dynamic constant pool entries + * + * @param lookup the lookup + * @param methodName the name of the method to generate, which must be one of + * {@code "equals"}, {@code "hashCode"}, or {@code "toString"} + * @param type a {@link MethodType} corresponding the descriptor type + * for the method, which must correspond to the descriptor + * for the corresponding {@link Object} method, if linking + * an {@code invokedynamic} call site, or the + * constant {@code MethodHandle.class}, if linking a + * dynamic constant + * @param theClass the class hosting the components + * @param names the list of component names, joined into a string separated by ";" + * @param getters method handles for the accessor methods for the components + * @return a call site if invoked by indy, or a method handle + * if invoked by a condy + * @throws IllegalArgumentException if the bootstrap arguments are invalid + * or inconsistent + * @throws Throwable if any exception is thrown during call site construction + */ + public static Object bootstrap(MethodHandles.Lookup lookup, String methodName, TypeDescriptor type, + Class<?> theClass, + String names, + MethodHandle... getters) throws Throwable { + MethodType methodType; + if (type instanceof MethodType) + methodType = (MethodType) type; + else { + methodType = null; + if (!MethodHandle.class.equals(type)) + throw new IllegalArgumentException(type.toString()); + } + List<MethodHandle> getterList = List.of(getters); + MethodHandle handle; + switch (methodName) { + case "equals": + if (methodType != null && !methodType.equals(MethodType.methodType(boolean.class, theClass, Object.class))) + throw new IllegalArgumentException("Bad method type: " + methodType); + handle = makeEquals(theClass, getterList); + return methodType != null ? new ConstantCallSite(handle) : handle; + case "hashCode": + if (methodType != null && !methodType.equals(MethodType.methodType(int.class, theClass))) + throw new IllegalArgumentException("Bad method type: " + methodType); + handle = makeHashCode(theClass, getterList); + return methodType != null ? new ConstantCallSite(handle) : handle; + case "toString": + if (methodType != null && !methodType.equals(MethodType.methodType(String.class, theClass))) + throw new IllegalArgumentException("Bad method type: " + methodType); + List<String> nameList = "".equals(names) ? List.of() : List.of(names.split(";")); + if (nameList.size() != getterList.size()) + throw new IllegalArgumentException("Name list and accessor list do not match"); + handle = makeToString(theClass, getterList, nameList); + return methodType != null ? new ConstantCallSite(handle) : handle; + default: + throw new IllegalArgumentException(methodName); + } + } +}
--- a/src/java.base/share/classes/module-info.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/java.base/share/classes/module-info.java Wed Oct 16 17:02:29 2019 -0400 @@ -132,6 +132,8 @@ // additional qualified exports may be inserted at build time // see make/gensrc/GenModuleInfo.gmk + exports sun.invoke.util to + jdk.compiler; exports com.sun.security.ntlm to java.security.sasl; exports jdk.internal to
--- a/src/java.base/share/classes/sun/reflect/annotation/TypeAnnotation.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/java.base/share/classes/sun/reflect/annotation/TypeAnnotation.java Wed Oct 16 17:02:29 2019 -0400 @@ -90,7 +90,8 @@ METHOD_RETURN, METHOD_RECEIVER, METHOD_FORMAL_PARAMETER, - THROWS; + THROWS, + RECORD_COMPONENT; } public static final class TypeAnnotationTargetInfo {
--- a/src/java.base/share/native/libjava/Class.c Wed Oct 16 17:00:39 2019 -0400 +++ b/src/java.base/share/native/libjava/Class.c Wed Oct 16 17:02:29 2019 -0400 @@ -38,6 +38,10 @@ #include "check_classname.h" #include "java_lang_Class.h" +/* defined in libverify.so/verify.dll (src file common/check_format.c) */ +extern jboolean VerifyClassname(char *utf_name, jboolean arrayAllowed); +extern jboolean VerifyFixClassname(char *utf_name); + #define OBJ "Ljava/lang/Object;" #define CLS "Ljava/lang/Class;" #define CPL "Ljdk/internal/reflect/ConstantPool;" @@ -73,6 +77,8 @@ {"getRawTypeAnnotations", "()" BA, (void *)&JVM_GetClassTypeAnnotations}, {"getNestHost0", "()" CLS, (void *)&JVM_GetNestHost}, {"getNestMembers0", "()[" CLS, (void *)&JVM_GetNestMembers}, + {"getRecordComponents0", "()[" OBJ, (void *)&JVM_GetRecordComponents}, + {"isRecord0", "()Z", (void *)&JVM_IsRecord}, {"linkClass", "(" CLS ")V", (void *)&JVM_LinkClass}, };
--- a/src/java.compiler/share/classes/javax/annotation/processing/RoundEnvironment.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/java.compiler/share/classes/javax/annotation/processing/RoundEnvironment.java Wed Oct 16 17:02:29 2019 -0400 @@ -78,8 +78,8 @@ * The annotation may appear directly or be inherited. Only * package elements, module elements, and type elements <i>included</i> in this * round of annotation processing, or declarations of members, - * constructors, parameters, or type parameters declared within - * those, are returned. Included type elements are {@linkplain + * constructors, parameters, type parameters, or record components + * declared within those, are returned. Included type elements are {@linkplain * #getRootElements root types} and any member types nested within * them. Elements of a package are not considered included simply * because a {@code package-info} file for that package was @@ -133,8 +133,8 @@ * The annotation may appear directly or be inherited. Only * package elements, module elements, and type elements <i>included</i> in this * round of annotation processing, or declarations of members, - * constructors, parameters, or type parameters declared within - * those, are returned. Included type elements are {@linkplain + * constructors, parameters, type parameters, or record components + * declared within those, are returned. Included type elements are {@linkplain * #getRootElements root types} and any member types nested within * them. Elements in a package are not considered included simply * because a {@code package-info} file for that package was
--- a/src/java.compiler/share/classes/javax/lang/model/SourceVersion.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/java.compiler/share/classes/javax/lang/model/SourceVersion.java Wed Oct 16 17:02:29 2019 -0400 @@ -63,6 +63,12 @@ * 14: TBD */ + // TOOD: The textual specs of isIdentifier, isName, and isKeyword + // may or may not need to be explicitly updated for "record" and + // "sealed" depending on how those tokens are formally handled in + // the JLS. If they are treated as restricted keywords, a spec + // update may not be strictly needed. + /** * The original version. *
--- a/src/java.compiler/share/classes/javax/lang/model/element/Element.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/java.compiler/share/classes/javax/lang/model/element/Element.java Wed Oct 16 17:02:29 2019 -0400 @@ -118,6 +118,7 @@ * @see TypeElement#getSimpleName * @see VariableElement#getSimpleName * @see ModuleElement#getSimpleName + * @see RecordComponentElement#getSimpleName * @revised 9 * @spec JPMS */ @@ -148,6 +149,11 @@ * parameter}, {@linkplain ExecutableElement the executable * element} which declares the parameter is returned. * + * <li> If this is a {@linkplain + * RecordComponentElement#getEnclosingElement record component}, + * {@linkplain TypeElement the type} which declares the + * record component is returned. + * * <li> If this is a {@linkplain ModuleElement#getEnclosingElement * module}, {@code null} is returned. * @@ -166,7 +172,7 @@ * * A {@linkplain TypeElement#getEnclosedElements class or * interface} is considered to enclose the fields, methods, - * constructors, and member types that it directly declares. + * constructors, record components, and member types that it directly declares. * * A {@linkplain PackageElement#getEnclosedElements package} * encloses the top-level classes and interfaces within it, but is
--- a/src/java.compiler/share/classes/javax/lang/model/element/ElementKind.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/java.compiler/share/classes/javax/lang/model/element/ElementKind.java Wed Oct 16 17:02:29 2019 -0400 @@ -46,8 +46,12 @@ // Declared types /** An enum type. */ ENUM, - /** A class not described by a more specific kind (like {@code ENUM}). */ + /** + * A class not described by a more specific kind (like {@code + * ENUM} or {@code RECORD}). + */ CLASS, + /** An annotation type. */ ANNOTATION_TYPE, /** @@ -90,6 +94,8 @@ */ OTHER, + // Constants added since initial release + /** * A resource variable. * @since 1.7 @@ -101,17 +107,28 @@ * @since 9 * @spec JPMS */ - MODULE; + MODULE, + /** + * A record type. + * @since amber + */ + RECORD, + + /** + * A record component of a {@code record}. + * @since 14 + */ + RECORD_COMPONENT; /** * Returns {@code true} if this is a kind of class: - * either {@code CLASS} or {@code ENUM}. + * either {@code CLASS} or {@code ENUM} or {@code RECORD}. * * @return {@code true} if this is a kind of class */ public boolean isClass() { - return this == CLASS || this == ENUM; + return this == CLASS || this == ENUM || this == RECORD; } /**
--- a/src/java.compiler/share/classes/javax/lang/model/element/ElementVisitor.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/java.compiler/share/classes/javax/lang/model/element/ElementVisitor.java Wed Oct 16 17:02:29 2019 -0400 @@ -164,4 +164,19 @@ default R visitModule(ModuleElement e, P p) { return visitUnknown(e, p); } + + /** + * Visits a record component element. + * + * @implSpec The default implementation visits a {@code + * RecordComponentElement} by calling {@code visitUnknown(e, p)}. + * + * @param e the element to visit + * @param p a visitor-specified parameter + * @return a visitor-specified result + * @since 14 + */ + default R visitRecordComponent(RecordComponentElement e, P p) { + return visitUnknown(e, p); + } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/java.compiler/share/classes/javax/lang/model/element/RecordComponentElement.java Wed Oct 16 17:02:29 2019 -0400 @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javax.lang.model.element; + +/** + * Represents a record component. + * + * @since 14 + */ + +public interface RecordComponentElement extends Element { + /** + * Returns the enclosing element of this record component. + * + * The enclosing element of a record component is the type + * declaring the record component. + * + * @return the enclosing element of this record component + */ + @Override + Element getEnclosingElement(); + + /** + * Returns the simple name of this record component. + * + * <p>The name of each record component must be distinct from the + * names of all other record components. + * + * @return the simple name of this record component + * + * @jls 6.2 Names and Identifiers + */ + @Override + Name getSimpleName(); + + /** + * Returns the executable element for the accessor associated with the + * given record component. + * + * @return the record component accessor. + */ + ExecutableElement getAccessor(); +}
--- a/src/java.compiler/share/classes/javax/lang/model/element/TypeElement.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/java.compiler/share/classes/javax/lang/model/element/TypeElement.java Wed Oct 16 17:02:29 2019 -0400 @@ -32,7 +32,7 @@ /** * Represents a class or interface program element. Provides access * to information about the type and its members. Note that an enum - * type is a kind of class and an annotation type is a kind of + * type and a record type are kinds of classes and an annotation type is a kind of * interface. * * <p> While a {@code TypeElement} represents a class or interface @@ -82,8 +82,9 @@ TypeMirror asType(); /** - * Returns the fields, methods, constructors, and member types - * that are directly declared in this class or interface. + * Returns the fields, methods, constructors, record components, + * and member types that are directly declared in this class or + * interface. * * This includes any {@linkplain Elements.Origin#MANDATED * mandated} elements such as the (implicit) default constructor @@ -178,6 +179,38 @@ List<? extends TypeParameterElement> getTypeParameters(); /** + * Returns the record components of this type element in + * declaration order. + * + * @implSpec The default implementations of this method returns an + * empty and unmodifiable list. + * + * @return the record components, or an empty list if there are + * none + * + * @since 14 + */ + default List<? extends RecordComponentElement> getRecordComponents() { + return List.of(); + } + + /** + * Returns the permitted subtypes of this type element in + * declaration order. + * + * @implSpec The default implementations of this method returns an + * empty and unmodifiable list. + * + * @return the permitted subtypes, or an empty list + * if there are none + * + * @since amber + */ + default List<? extends TypeMirror> getPermittedSubtypes() { + return List.of(); + } + + /** * Returns the package of a top-level type and returns the * immediately lexically enclosing element for a {@linkplain * NestingKind#isNested nested} type.
--- a/src/java.compiler/share/classes/javax/lang/model/element/package-info.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/java.compiler/share/classes/javax/lang/model/element/package-info.java Wed Oct 16 17:02:29 2019 -0400 @@ -90,8 +90,8 @@ * new RuntimeException();"}. If a program refers to a missing type Xyz, * the returned model must contain no less information than if the * declaration of type Xyz were assumed to be {@code "class Xyz {}"}, - * {@code "interface Xyz {}"}, {@code "enum Xyz {}"}, or {@code - * "@interface Xyz {}"}. If a program refers to a missing type {@code + * {@code "interface Xyz {}"}, {@code "enum Xyz {}"}, {@code + * "@interface Xyz {}"}, or {@code "record Xyz {}"}. If a program refers to a missing type {@code * Xyz<K1, ... ,Kn>}, the returned model must contain no less * information than if the declaration of Xyz were assumed to be * {@code "class Xyz<T1, ... ,Tn> {}"} or {@code "interface Xyz<T1,
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/java.compiler/share/classes/javax/lang/model/util/AbstractElementVisitor14.java Wed Oct 16 17:02:29 2019 -0400 @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javax.lang.model.util; + +import javax.annotation.processing.SupportedSourceVersion; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.RecordComponentElement; +import static javax.lang.model.SourceVersion.*; + + +/** + * A skeletal visitor of program elements with default behavior + * appropriate for the {@link SourceVersion#RELEASE_14 RELEASE_14} + * source version. + * + * <p> <b>WARNING:</b> The {@code ElementVisitor} interface + * implemented by this class may have methods added to it in the + * future to accommodate new, currently unknown, language structures + * added to future versions of the Java™ programming language. + * Therefore, methods whose names begin with {@code "visit"} may be + * added to this class in the future; to avoid incompatibilities, + * classes which extend this class should not declare any instance + * methods with names beginning with {@code "visit"}. + * + * <p>When such a new visit method is added, the default + * implementation in this class will be to call the {@link + * #visitUnknown visitUnknown} method. A new abstract element visitor + * class will also be introduced to correspond to the new language + * level; this visitor will have different default behavior for the + * visit method in question. When the new visitor is introduced, all + * or portions of this visitor may be deprecated. + * + * @param <R> the return type of this visitor's methods. Use {@link + * Void} for visitors that do not need to return results. + * @param <P> the type of the additional parameter to this visitor's + * methods. Use {@code Void} for visitors that do not need an + * additional parameter. + * + * @see AbstractElementVisitor6 + * @see AbstractElementVisitor7 + * @see AbstractElementVisitor8 + * @see AbstractElementVisitor9 + * @since 14 + */ +@SupportedSourceVersion(RELEASE_14) +public abstract class AbstractElementVisitor14<R, P> extends AbstractElementVisitor9<R, P> { + /** + * Constructor for concrete subclasses to call. + */ + protected AbstractElementVisitor14(){ + super(); + } + + /** + * {@inheritDoc} + * + * @implSpec Visits a {@code RecordComponentElement} in a manner defined by a + * subclass. + * + * @param t {@inheritDoc} + * @param p {@inheritDoc} + * @return {@inheritDoc} + */ + @Override + public abstract R visitRecordComponent(RecordComponentElement t, P p); +}
--- a/src/java.compiler/share/classes/javax/lang/model/util/AbstractElementVisitor6.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/java.compiler/share/classes/javax/lang/model/util/AbstractElementVisitor6.java Wed Oct 16 17:02:29 2019 -0400 @@ -66,6 +66,7 @@ * @see AbstractElementVisitor7 * @see AbstractElementVisitor8 * @see AbstractElementVisitor9 + * @see AbstractElementVisitor14 * @since 1.6 */ @SupportedSourceVersion(RELEASE_6) @@ -143,4 +144,22 @@ // Use implementation from interface default method return ElementVisitor.super.visitModule(e, p); } + + /** + * {@inheritDoc} + * + * @implSpec Visits a {@code RecordComponentElement} by calling {@code + * visitUnknown}. + * + * @param e {@inheritDoc} + * @param p {@inheritDoc} + * @return {@inheritDoc} + * + * @since 14 + */ + @Override + public R visitRecordComponent(RecordComponentElement e, P p) { + // Use implementation from interface default method + return ElementVisitor.super.visitRecordComponent(e, p); + } }
--- a/src/java.compiler/share/classes/javax/lang/model/util/AbstractElementVisitor7.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/java.compiler/share/classes/javax/lang/model/util/AbstractElementVisitor7.java Wed Oct 16 17:02:29 2019 -0400 @@ -61,6 +61,7 @@ * @see AbstractElementVisitor6 * @see AbstractElementVisitor8 * @see AbstractElementVisitor9 + * @see AbstractElementVisitor14 * @since 1.7 */ @SupportedSourceVersion(RELEASE_7)
--- a/src/java.compiler/share/classes/javax/lang/model/util/AbstractElementVisitor8.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/java.compiler/share/classes/javax/lang/model/util/AbstractElementVisitor8.java Wed Oct 16 17:02:29 2019 -0400 @@ -61,6 +61,7 @@ * @see AbstractElementVisitor6 * @see AbstractElementVisitor7 * @see AbstractElementVisitor9 + * @see AbstractElementVisitor14 * @since 1.8 */ @SupportedSourceVersion(RELEASE_8)
--- a/src/java.compiler/share/classes/javax/lang/model/util/ElementFilter.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/java.compiler/share/classes/javax/lang/model/util/ElementFilter.java Wed Oct 16 17:02:29 2019 -0400 @@ -83,7 +83,12 @@ Collections.unmodifiableSet(EnumSet.of(ElementKind.CLASS, ElementKind.ENUM, ElementKind.INTERFACE, + ElementKind.RECORD, ElementKind.ANNOTATION_TYPE)); + + private static final Set<ElementKind> RECORD_COMPONENT_KIND = + Set.of(ElementKind.RECORD_COMPONENT); + /** * Returns a list of fields in {@code elements}. * @return a list of fields in {@code elements} @@ -105,6 +110,28 @@ } /** + * Returns a list of record components in {@code elements}. + * @return a list of record components in {@code elements} + * @param elements the elements to filter + * @since 14 + */ + public static List<RecordComponentElement> + recordComponentsIn(Iterable<? extends Element> elements) { + return listFilter(elements, RECORD_COMPONENT_KIND, RecordComponentElement.class); + } + + /** + * Returns a set of record components in {@code elements}. + * @return a set of record components in {@code elements} + * @param elements the elements to filter + * @since 14 + */ + public static Set<RecordComponentElement> + recordComponentsIn(Set<? extends Element> elements) { + return setFilter(elements, RECORD_COMPONENT_KIND, RecordComponentElement.class); + } + + /** * Returns a list of constructors in {@code elements}. * @return a list of constructors in {@code elements} * @param elements the elements to filter
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitor14.java Wed Oct 16 17:02:29 2019 -0400 @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javax.lang.model.util; + +import javax.lang.model.element.*; +import javax.annotation.processing.SupportedSourceVersion; +import static javax.lang.model.SourceVersion.*; +import javax.lang.model.SourceVersion; + +/** + * A visitor of program elements based on their {@linkplain + * ElementKind kind} with default behavior appropriate for the {@link + * SourceVersion#RELEASE_14 RELEASE_14} source version. + * + * For {@linkplain + * Element elements} <code><i>Xyz</i></code> that may have more than one + * kind, the <code>visit<i>Xyz</i></code> methods in this class delegate + * to the <code>visit<i>Xyz</i>As<i>Kind</i></code> method corresponding to the + * first argument's kind. The <code>visit<i>Xyz</i>As<i>Kind</i></code> methods + * call {@link #defaultAction defaultAction}, passing their arguments + * to {@code defaultAction}'s corresponding parameters. + * + * <p> Methods in this class may be overridden subject to their + * general contract. Note that annotating methods in concrete + * subclasses with {@link java.lang.Override @Override} will help + * ensure that methods are overridden as intended. + * + * <p> <b>WARNING:</b> The {@code ElementVisitor} interface + * implemented by this class may have methods added to it or the + * {@code ElementKind} {@code enum} used in this case may have + * constants added to it in the future to accommodate new, currently + * unknown, language structures added to future versions of the + * Java™ programming language. Therefore, methods whose names + * begin with {@code "visit"} may be added to this class in the + * future; to avoid incompatibilities, classes which extend this class + * should not declare any instance methods with names beginning with + * {@code "visit"}. + * + * <p>When such a new visit method is added, the default + * implementation in this class will be to call the {@link + * #visitUnknown visitUnknown} method. A new abstract element kind + * visitor class will also be introduced to correspond to the new + * language level; this visitor will have different default behavior + * for the visit method in question. When the new visitor is + * introduced, all or portions of this visitor may be deprecated. + * + * @param <R> the return type of this visitor's methods. Use {@link + * Void} for visitors that do not need to return results. + * @param <P> the type of the additional parameter to this visitor's + * methods. Use {@code Void} for visitors that do not need an + * additional parameter. + * + * @see ElementKindVisitor6 + * @see ElementKindVisitor7 + * @see ElementKindVisitor8 + * @see ElementKindVisitor9 + * @since 14 + */ +@SupportedSourceVersion(RELEASE_14) +public class ElementKindVisitor14<R, P> extends ElementKindVisitor9<R, P> { + /** + * Constructor for concrete subclasses; uses {@code null} for the + * default value. + */ + protected ElementKindVisitor14() { + super(null); + } + + /** + * Constructor for concrete subclasses; uses the argument for the + * default value. + * + * @param defaultValue the value to assign to {@link #DEFAULT_VALUE} + */ + protected ElementKindVisitor14(R defaultValue) { + super(defaultValue); + } + + /** + * {@inheritDoc} + * + * @implSpec This implementation calls {@code defaultAction}. + * + * @param e the element to visit + * @param p a visitor-specified parameter + * @return the result of {@code defaultAction} + */ + @Override + public R visitRecordComponent(RecordComponentElement e, P p) { + return defaultAction(e, p); + } + + /** + * {@inheritDoc} + * + * @implSpec This implementation calls {@code defaultAction}. + *. + * @param e the element to visit + * @param p a visitor-specified parameter + * @return the result of {@code defaultAction} + */ + @Override + public R visitTypeAsRecord(TypeElement e, P p) { + return defaultAction(e, p); + } +}
--- a/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitor6.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitor6.java Wed Oct 16 17:02:29 2019 -0400 @@ -80,6 +80,7 @@ * @see ElementKindVisitor7 * @see ElementKindVisitor8 * @see ElementKindVisitor9 + * @see ElementKindVisitor14 * @since 1.6 */ @SupportedSourceVersion(RELEASE_6) @@ -154,6 +155,9 @@ case INTERFACE: return visitTypeAsInterface(e, p); + case RECORD: + return visitTypeAsRecord(e, p); + default: throw new AssertionError("Bad kind " + k + " for TypeElement" + e); } @@ -212,6 +216,21 @@ } /** + * Visits a {@code RECORD} type element. + * + * @implSpec This implementation calls {@code visitUnknown}. + *. + * @param e the element to visit + * @param p a visitor-specified parameter + * @return the result of {@code visitUnknown} + * + * @since amber + */ + public R visitTypeAsRecord(TypeElement e, P p) { + return visitUnknown(e, p); + } + + /** * Visits a variable element * * @implSpec This implementation dispatches to the visit method for
--- a/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitor7.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitor7.java Wed Oct 16 17:02:29 2019 -0400 @@ -74,6 +74,7 @@ * @see ElementKindVisitor6 * @see ElementKindVisitor8 * @see ElementKindVisitor9 + * @see ElementKindVisitor14 * @since 1.7 */ @SupportedSourceVersion(RELEASE_7)
--- a/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitor8.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/java.compiler/share/classes/javax/lang/model/util/ElementKindVisitor8.java Wed Oct 16 17:02:29 2019 -0400 @@ -74,6 +74,7 @@ * @see ElementKindVisitor6 * @see ElementKindVisitor7 * @see ElementKindVisitor9 + * @see ElementKindVisitor14 * @since 1.8 */ @SupportedSourceVersion(RELEASE_8)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/java.compiler/share/classes/javax/lang/model/util/ElementScanner14.java Wed Oct 16 17:02:29 2019 -0400 @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javax.lang.model.util; + +import javax.lang.model.element.*; +import javax.annotation.processing.SupportedSourceVersion; +import javax.lang.model.SourceVersion; +import static javax.lang.model.SourceVersion.*; + +/** + * A scanning visitor of program elements with default behavior + * appropriate for the {@link SourceVersion#RELEASE_14 RELEASE_14} + * source version. + * + * The <code>visit<i>Xyz</i></code> methods in this + * class scan their component elements by calling {@code scan} on + * their {@linkplain Element#getEnclosedElements enclosed elements}, + * {@linkplain ExecutableElement#getParameters parameters}, etc., as + * indicated in the individual method specifications. A subclass can + * control the order elements are visited by overriding the + * <code>visit<i>Xyz</i></code> methods. Note that clients of a scanner + * may get the desired behavior be invoking {@code v.scan(e, p)} rather + * than {@code v.visit(e, p)} on the root objects of interest. + * + * <p>When a subclass overrides a <code>visit<i>Xyz</i></code> method, the + * new method can cause the enclosed elements to be scanned in the + * default way by calling <code>super.visit<i>Xyz</i></code>. In this + * fashion, the concrete visitor can control the ordering of traversal + * over the component elements with respect to the additional + * processing; for example, consistently calling + * <code>super.visit<i>Xyz</i></code> at the start of the overridden + * methods will yield a preorder traversal, etc. If the component + * elements should be traversed in some other order, instead of + * calling <code>super.visit<i>Xyz</i></code>, an overriding visit method + * should call {@code scan} with the elements in the desired order. + * + * <p> Methods in this class may be overridden subject to their + * general contract. Note that annotating methods in concrete + * subclasses with {@link java.lang.Override @Override} will help + * ensure that methods are overridden as intended. + * + * <p> <b>WARNING:</b> The {@code ElementVisitor} interface + * implemented by this class may have methods added to it in the + * future to accommodate new, currently unknown, language structures + * added to future versions of the Java™ programming language. + * Therefore, methods whose names begin with {@code "visit"} may be + * added to this class in the future; to avoid incompatibilities, + * classes which extend this class should not declare any instance + * methods with names beginning with {@code "visit"}. + * + * <p>When such a new visit method is added, the default + * implementation in this class will be to call the {@link + * #visitUnknown visitUnknown} method. A new element scanner visitor + * class will also be introduced to correspond to the new language + * level; this visitor will have different default behavior for the + * visit method in question. When the new visitor is introduced, all + * or portions of this visitor may be deprecated. + * + * @param <R> the return type of this visitor's methods. Use {@link + * Void} for visitors that do not need to return results. + * @param <P> the type of the additional parameter to this visitor's + * methods. Use {@code Void} for visitors that do not need an + * additional parameter. + * + * @see ElementScanner6 + * @see ElementScanner7 + * @see ElementScanner8 + * @see ElementScanner9 + * @since 14 + */ +@SupportedSourceVersion(RELEASE_14) +public class ElementScanner14<R, P> extends ElementScanner9<R, P> { + /** + * Constructor for concrete subclasses; uses {@code null} for the + * default value. + */ + protected ElementScanner14(){ + super(null); + } + + /** + * Constructor for concrete subclasses; uses the argument for the + * default value. + * + * @param defaultValue the default value + */ + protected ElementScanner14(R defaultValue){ + super(defaultValue); + } + + /** + * {@inheritDoc} + * + * @implSpec This implementation scans the enclosed elements. + * + * @param e the element to visit + * @param p a visitor-specified parameter + * @return the result of the scan + */ + @Override + public R visitRecordComponent(RecordComponentElement e, P p) { + return scan(e.getEnclosedElements(), p); + } +}
--- a/src/java.compiler/share/classes/javax/lang/model/util/ElementScanner6.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/java.compiler/share/classes/javax/lang/model/util/ElementScanner6.java Wed Oct 16 17:02:29 2019 -0400 @@ -91,6 +91,7 @@ * @see ElementScanner7 * @see ElementScanner8 * @see ElementScanner9 + * @see ElementScanner14 * @since 1.6 */ @SupportedSourceVersion(RELEASE_6)
--- a/src/java.compiler/share/classes/javax/lang/model/util/ElementScanner7.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/java.compiler/share/classes/javax/lang/model/util/ElementScanner7.java Wed Oct 16 17:02:29 2019 -0400 @@ -87,6 +87,7 @@ * @see ElementScanner6 * @see ElementScanner8 * @see ElementScanner9 + * @see ElementScanner14 * @since 1.7 */ @SupportedSourceVersion(RELEASE_7)
--- a/src/java.compiler/share/classes/javax/lang/model/util/ElementScanner8.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/java.compiler/share/classes/javax/lang/model/util/ElementScanner8.java Wed Oct 16 17:02:29 2019 -0400 @@ -87,6 +87,7 @@ * @see ElementScanner6 * @see ElementScanner7 * @see ElementScanner9 + * @see ElementScanner14 * @since 1.8 */ @SupportedSourceVersion(RELEASE_8)
--- a/src/java.compiler/share/classes/javax/lang/model/util/ElementScanner9.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/java.compiler/share/classes/javax/lang/model/util/ElementScanner9.java Wed Oct 16 17:02:29 2019 -0400 @@ -89,6 +89,7 @@ * @see ElementScanner6 * @see ElementScanner7 * @see ElementScanner8 + * @see ElementScanner14 * @since 9 * @spec JPMS */
--- a/src/java.compiler/share/classes/javax/lang/model/util/Elements.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/java.compiler/share/classes/javax/lang/model/util/Elements.java Wed Oct 16 17:02:29 2019 -0400 @@ -31,6 +31,7 @@ import java.util.Map; import java.util.Set; import java.util.LinkedHashSet; +import java.util.Objects; import javax.lang.model.AnnotatedConstruct; import javax.lang.model.element.*; @@ -629,4 +630,38 @@ * @since 1.8 */ boolean isFunctionalInterface(TypeElement type); + + /** + * TODO: needed? @see RecordComponentElement#getAccessor() + * + * Returns the executable element for the getter associated with the given variable element. + * + * @implSpec The default implementation of this method returns + * {@code null}. + * + * @param variableElement the field for which the getter is to be found. + * @return the field's getter; otherwise {@code null} if there is no getter. + * @since amber + */ + default ExecutableElement getterFor(VariableElement variableElement) { + return null; + } + + /** + * Returns the record component for the given accessor. Returns null if the + * given method is not a record component accessor. + * + * @param accessor the method for which the record component should be found. + * @return the record component, or null if the given method is not an record component accessor + */ + default RecordComponentElement recordComponentFor(ExecutableElement accessor) { + if (accessor.getEnclosingElement().getKind() == ElementKind.RECORD) { + for (RecordComponentElement rec : ElementFilter.recordComponentsIn(accessor.getEnclosingElement().getEnclosedElements())) { + if (Objects.equals(rec.getAccessor(), accessor)) { + return rec; + } + } + } + return null; + } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/java.compiler/share/classes/javax/lang/model/util/SimpleElementVisitor14.java Wed Oct 16 17:02:29 2019 -0400 @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package javax.lang.model.util; + +import javax.annotation.processing.SupportedSourceVersion; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.RecordComponentElement; +import static javax.lang.model.SourceVersion.*; + +/** + * A simple visitor of program elements with default behavior + * appropriate for the {@link SourceVersion#RELEASE_14 RELEASE_14} + * source version. + * + * Visit methods corresponding to {@code RELEASE_14} and earlier + * language constructs call {@link #defaultAction defaultAction}, + * passing their arguments to {@code defaultAction}'s corresponding + * parameters. + * + * <p> Methods in this class may be overridden subject to their + * general contract. Note that annotating methods in concrete + * subclasses with {@link java.lang.Override @Override} will help + * ensure that methods are overridden as intended. + * + * <p> <b>WARNING:</b> The {@code ElementVisitor} interface + * implemented by this class may have methods added to it in the + * future to accommodate new, currently unknown, language structures + * added to future versions of the Java™ programming language. + * Therefore, methods whose names begin with {@code "visit"} may be + * added to this class in the future; to avoid incompatibilities, + * classes which extend this class should not declare any instance + * methods with names beginning with {@code "visit"}. + * + * <p>When such a new visit method is added, the default + * implementation in this class will be to call the {@link + * #visitUnknown visitUnknown} method. A new simple element visitor + * class will also be introduced to correspond to the new language + * level; this visitor will have different default behavior for the + * visit method in question. When the new visitor is introduced, all + * or portions of this visitor may be deprecated. + * + * @param <R> the return type of this visitor's methods. Use {@code Void} + * for visitors that do not need to return results. + * @param <P> the type of the additional parameter to this visitor's methods. Use {@code Void} + * for visitors that do not need an additional parameter. + * + * @see SimpleElementVisitor6 + * @see SimpleElementVisitor7 + * @see SimpleElementVisitor8 + * @see SimpleElementVisitor9 + * @since 14 + */ +@SupportedSourceVersion(RELEASE_14) +public class SimpleElementVisitor14<R, P> extends SimpleElementVisitor9<R, P> { + /** + * Constructor for concrete subclasses; uses {@code null} for the + * default value. + */ + protected SimpleElementVisitor14(){ + super(null); + } + + /** + * Constructor for concrete subclasses; uses the argument for the + * default value. + * + * @param defaultValue the value to assign to {@link #DEFAULT_VALUE} + */ + protected SimpleElementVisitor14(R defaultValue){ + super(defaultValue); + } + + /** + * {@inheritDoc} + * + * @implSpec Visits a {@code RecordComponentElement} by calling {@code + * defaultAction}. + * + * @param e the element to visit + * @param p a visitor-specified parameter + * @return {@inheritDoc} + */ + @Override + public R visitRecordComponent(RecordComponentElement e, P p) { + return defaultAction(e, p); + } +}
--- a/src/java.compiler/share/classes/javax/lang/model/util/SimpleElementVisitor6.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/java.compiler/share/classes/javax/lang/model/util/SimpleElementVisitor6.java Wed Oct 16 17:02:29 2019 -0400 @@ -77,6 +77,7 @@ * @see SimpleElementVisitor7 * @see SimpleElementVisitor8 * @see SimpleElementVisitor9 + * @see SimpleElementVisitor14 * @since 1.6 */ @SupportedSourceVersion(RELEASE_6)
--- a/src/java.compiler/share/classes/javax/lang/model/util/SimpleElementVisitor7.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/java.compiler/share/classes/javax/lang/model/util/SimpleElementVisitor7.java Wed Oct 16 17:02:29 2019 -0400 @@ -70,6 +70,7 @@ * @see SimpleElementVisitor6 * @see SimpleElementVisitor8 * @see SimpleElementVisitor9 + * @see SimpleElementVisitor14 * @since 1.7 */ @SupportedSourceVersion(RELEASE_7)
--- a/src/java.compiler/share/classes/javax/lang/model/util/SimpleElementVisitor8.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/java.compiler/share/classes/javax/lang/model/util/SimpleElementVisitor8.java Wed Oct 16 17:02:29 2019 -0400 @@ -69,6 +69,7 @@ * @see SimpleElementVisitor6 * @see SimpleElementVisitor7 * @see SimpleElementVisitor9 + * @see SimpleElementVisitor14 * @since 1.8 */ @SupportedSourceVersion(RELEASE_8)
--- a/src/java.compiler/share/classes/javax/lang/model/util/SimpleElementVisitor9.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/java.compiler/share/classes/javax/lang/model/util/SimpleElementVisitor9.java Wed Oct 16 17:02:29 2019 -0400 @@ -70,6 +70,7 @@ * @see SimpleElementVisitor6 * @see SimpleElementVisitor7 * @see SimpleElementVisitor8 + * @see SimpleElementVisitor14 * @since 9 * @spec JPMS */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.compiler/share/classes/com/sun/source/doctree/AccessorTree.java Wed Oct 16 17:02:29 2019 -0400 @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.source.doctree; + +import java.util.List; + +/** + * + * A tree node for an @getter or @setter block tag. + * + * <p> + * @getter description <br> + * @setter description <br> + * + * @since 1.10 + */ +public interface AccessorTree extends BlockTagTree { + /** + * Returns the description of the {@code @getter} or {@code @setter} tag. + * @return the description associated with this tag + */ + List<? extends DocTree> getDescription(); +}
--- a/src/jdk.compiler/share/classes/com/sun/source/doctree/DocTree.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/source/doctree/DocTree.java Wed Oct 16 17:02:29 2019 -0400 @@ -35,6 +35,19 @@ * Enumerates all kinds of trees. */ enum Kind { + + /** + * Used for instances of {@link AccessorTree} + * representing an embedded getter JavaDoc. + */ + GETTER("getter"), + + /** + * Used for instances of {@link AccessorTree} + * representing an embedded getter JavaDoc. + */ + SETTER("setter"), + /** * Used for instances of {@link AttributeTree} * representing an HTML attribute.
--- a/src/jdk.compiler/share/classes/com/sun/source/doctree/DocTreeVisitor.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/source/doctree/DocTreeVisitor.java Wed Oct 16 17:02:29 2019 -0400 @@ -57,6 +57,14 @@ public interface DocTreeVisitor<R,P> { /** + * Visits an AaccessorTree node. + * @param node the node being visited + * @param p a parameter value + * @return a result value + */ + R visitAccessor(AccessorTree node, P p); + + /** * Visits an AttributeTree node. * @param node the node being visited * @param p a parameter value
--- a/src/jdk.compiler/share/classes/com/sun/source/util/DocTreeFactory.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/source/util/DocTreeFactory.java Wed Oct 16 17:02:29 2019 -0400 @@ -31,6 +31,7 @@ import javax.tools.Diagnostic; import javax.tools.JavaFileObject; +import com.sun.source.doctree.AccessorTree; import com.sun.source.doctree.AttributeTree; import com.sun.source.doctree.AttributeTree.ValueKind; import com.sun.source.doctree.AuthorTree; @@ -39,6 +40,7 @@ import com.sun.source.doctree.DocCommentTree; import com.sun.source.doctree.DocRootTree; import com.sun.source.doctree.DocTree; +import com.sun.source.doctree.DocTree.Kind; import com.sun.source.doctree.DocTypeTree; import com.sun.source.doctree.EndElementTree; import com.sun.source.doctree.EntityTree; @@ -235,6 +237,13 @@ LiteralTree newLiteralTree(TextTree text); /** + * Create a new {@code AccessorTree} object, to represent a {@code @getter} tag. + * @param description the content of the tag + * @return a {@code AccessorTree} object + */ + AccessorTree newAccessorTree(Kind kind, List<? extends DocTree> description); + + /** * Create a new {@code ParamTree} object, to represent a {@code @param } tag. * @param isTypeParameter true if this is a type parameter, and false otherwise * @param name the parameter being described
--- a/src/jdk.compiler/share/classes/com/sun/source/util/DocTreeScanner.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/source/util/DocTreeScanner.java Wed Oct 16 17:02:29 2019 -0400 @@ -489,6 +489,18 @@ } /** + * {@inheritDoc} This implementation returns {@code null}. + * + * @param node {@inheritDoc} + * @param p {@inheritDoc} + * @return the result of scanning + */ + @Override + public R visitAccessor(AccessorTree node, P p) { + return scan(node.getDescription(), p); + } + + /** * {@inheritDoc} This implementation scans the children in left to right order. * * @param node {@inheritDoc}
--- a/src/jdk.compiler/share/classes/com/sun/source/util/SimpleDocTreeVisitor.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/source/util/SimpleDocTreeVisitor.java Wed Oct 16 17:02:29 2019 -0400 @@ -298,6 +298,18 @@ * @return the result of {@code defaultAction} */ @Override + public R visitAccessor(AccessorTree node, P p) { + return defaultAction(node, p); + } + + /** + * {@inheritDoc} This implementation calls {@code defaultAction}. + * + * @param node {@inheritDoc} + * @param p {@inheritDoc} + * @return the result of {@code defaultAction} + */ + @Override public R visitParam(ParamTree node, P p) { return defaultAction(node, p); }
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTrees.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTrees.java Wed Oct 16 17:02:29 2019 -0400 @@ -420,10 +420,10 @@ @Override @DefinedBy(Api.COMPILER_TREE) public Element getElement(DocTreePath path) { - DocTree forTree = path.getLeaf(); - if (forTree instanceof DCReference) - return attributeDocReference(path.getTreePath(), ((DCReference) forTree)); - if (forTree instanceof DCIdentifier) { + DocTree tree = path.getLeaf(); + if (tree instanceof DCReference) + return attributeDocReference(path.getTreePath(), ((DCReference) tree)); + if (tree instanceof DCIdentifier) { if (path.getParentPath().getLeaf() instanceof DCParam) { return attributeParamIdentifier(path.getTreePath(), (DCParam) path.getParentPath().getLeaf()); } @@ -536,7 +536,7 @@ } } - private Symbol attributeParamIdentifier(TreePath path, DCParam ptag) { + private Symbol attributeParamIdentifier(TreePath path, DCParam paramTag) { Symbol javadocSymbol = getElement(path); if (javadocSymbol == null) return null; @@ -544,16 +544,18 @@ List<? extends Symbol> params = List.nil(); if (kind == ElementKind.METHOD || kind == ElementKind.CONSTRUCTOR) { MethodSymbol ee = (MethodSymbol) javadocSymbol; - params = ptag.isTypeParameter() + params = paramTag.isTypeParameter() ? ee.getTypeParameters() : ee.getParameters(); } else if (kind.isClass() || kind.isInterface()) { ClassSymbol te = (ClassSymbol) javadocSymbol; - params = te.getTypeParameters(); + params = paramTag.isTypeParameter() + ? te.getTypeParameters() + : te.getRecordComponents(); } for (Symbol param : params) { - if (param.getSimpleName() == ptag.getName().getName()) { + if (param.getSimpleName() == paramTag.getName().getName()) { return param; } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Accessors.java Wed Oct 16 17:02:29 2019 -0400 @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.sun.tools.javac.code; + +import com.sun.tools.javac.code.Type.MethodType; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.Name; +import com.sun.tools.javac.util.Names; + +import java.util.function.Function; + +public class Accessors { + + public enum Kind { + GET(names -> names.get) { + @Override + public Type accessorType(Symtab syms, Type type) { + return new MethodType(List.nil(), type, List.nil(), syms.methodClass); + } + }, + SET(names -> names.set) { + @Override + public Type accessorType(Symtab syms, Type type) { + return new MethodType(List.of(type), syms.voidType, List.nil(), syms.methodClass); + } + }; + + private final Function<Names, Name> nameFunc; + + Kind(Function<Names, Name> nameFunc) { + this.nameFunc = nameFunc; + } + + public Name name(Names names) { + return nameFunc.apply(names); + } + + public abstract Type accessorType(Symtab syms, Type type); + } + + public static Kind fromName(Name name) { + for (Kind k : Kind.values()) { + if (k.name(name.table.names).equals(name)) { + return k; + } + } + return null; + } +}
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Flags.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Flags.java Wed Oct 16 17:02:29 2019 -0400 @@ -324,12 +324,31 @@ */ public static final long NAME_FILLED = 1L<<58; //ParamSymbols only + /** + * Flag to indicate that a class is a record. The flag is also used to mark fields that are + * part of the state vector of a record. + */ + public static final long RECORD = 1L<<61; + + /** + * Flag to mark a record constructor as a compact one + */ + public static final long COMPACT_RECORD_CONSTRUCTOR = 1L<<51; + + /** + * Flag that marks if a the implementation of a record component, a field, + * was originally declared as a varargs + */ + public static final long ORIGINALLY_VARARGS = 1L<<49; + /** Modifier masks. */ public static final int AccessFlags = PUBLIC | PROTECTED | PRIVATE, LocalClassFlags = FINAL | ABSTRACT | STRICTFP | ENUM | SYNTHETIC, + LocalRecordFlags = LocalClassFlags | STATIC, MemberClassFlags = LocalClassFlags | INTERFACE | AccessFlags, + MemberRecordClassFlags = MemberClassFlags | STATIC, ClassFlags = LocalClassFlags | INTERFACE | PUBLIC | ANNOTATION, InterfaceVarFlags = FINAL | STATIC | PUBLIC, VarFlags = AccessFlags | FINAL | STATIC | @@ -337,10 +356,17 @@ ConstructorFlags = AccessFlags, InterfaceMethodFlags = ABSTRACT | PUBLIC, MethodFlags = AccessFlags | ABSTRACT | STATIC | NATIVE | + SYNCHRONIZED | FINAL | STRICTFP, + RecordMethodFlags = AccessFlags | ABSTRACT | STATIC | SYNCHRONIZED | FINAL | STRICTFP; public static final long - ExtendedStandardFlags = (long)StandardFlags | DEFAULT, - ModifierFlags = ((long)StandardFlags & ~INTERFACE) | DEFAULT, + ExtendedStandardFlags = (long)StandardFlags | DEFAULT, + ExtendedLocalClassFlags = (long)LocalClassFlags, + ExtendedLocalRecordFlags = (long)LocalRecordFlags, + ExtendedMemberClassFlags = (long)MemberClassFlags, + ExtendedMemberRecordClassFlags = (long)MemberRecordClassFlags, + ExtendedClassFlags = (long)ClassFlags, + ModifierFlags = ((long)StandardFlags & ~INTERFACE) | DEFAULT, InterfaceMethodMask = ABSTRACT | PRIVATE | STATIC | PUBLIC | STRICTFP | DEFAULT, AnnotationTypeElementMask = ABSTRACT | PUBLIC, LocalVarFlags = FINAL | PARAMETER, @@ -441,7 +467,8 @@ HAS_RESOURCE(Flags.HAS_RESOURCE), POTENTIALLY_AMBIGUOUS(Flags.POTENTIALLY_AMBIGUOUS), ANONCONSTR_BASED(Flags.ANONCONSTR_BASED), - NAME_FILLED(Flags.NAME_FILLED); + NAME_FILLED(Flags.NAME_FILLED), + RECORD(Flags.RECORD); Flag(long flag) { this.value = flag;
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Kinds.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Kinds.java Wed Oct 16 17:02:29 2019 -0400 @@ -277,6 +277,7 @@ case ANNOTATION_TYPE: case CLASS: + case RECORD: return KindName.CLASS; case INTERFACE:
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Preview.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Preview.java Wed Oct 16 17:02:29 2019 -0400 @@ -168,7 +168,8 @@ if (feature == Feature.SWITCH_EXPRESSION || feature == Feature.SWITCH_MULTIPLE_CASE_LABELS || feature == Feature.SWITCH_RULE || - feature == Feature.TEXT_BLOCKS) + feature == Feature.TEXT_BLOCKS || + feature == Feature.RECORDS) return true; //Note: this is a backdoor which allows to optionally treat all features as 'preview' (for testing). //When real preview features will be added, this method can be implemented to return 'true'
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Source.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Source.java Wed Oct 16 17:02:29 2019 -0400 @@ -199,7 +199,8 @@ SWITCH_MULTIPLE_CASE_LABELS(JDK14, Fragments.FeatureMultipleCaseLabels, DiagKind.PLURAL), SWITCH_RULE(JDK14, Fragments.FeatureSwitchRules, DiagKind.PLURAL), SWITCH_EXPRESSION(JDK14, Fragments.FeatureSwitchExpressions, DiagKind.PLURAL), - TEXT_BLOCKS(JDK14, Fragments.FeatureTextBlocks, DiagKind.PLURAL); + TEXT_BLOCKS(JDK14, Fragments.FeatureTextBlocks, DiagKind.PLURAL), + RECORDS(JDK14); enum DiagKind { NORMAL,
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symbol.java Wed Oct 16 17:02:29 2019 -0400 @@ -42,12 +42,14 @@ import javax.lang.model.element.ModuleElement; import javax.lang.model.element.NestingKind; import javax.lang.model.element.PackageElement; +import javax.lang.model.element.RecordComponentElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeParameterElement; import javax.lang.model.element.VariableElement; import javax.tools.JavaFileManager; import javax.tools.JavaFileObject; +import com.sun.tools.javac.code.Accessors; import com.sun.tools.javac.code.Kinds.Kind; import com.sun.tools.javac.comp.Annotate.AnnotationTypeMetadata; import com.sun.tools.javac.code.Type.*; @@ -61,6 +63,7 @@ import com.sun.tools.javac.tree.JCTree.Tag; import com.sun.tools.javac.util.*; import com.sun.tools.javac.util.DefinedBy.Api; +import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.Name; import static com.sun.tools.javac.code.Flags.*; @@ -370,6 +373,10 @@ return (flags_field & DEPRECATED) != 0; } + public boolean isRecord() { + return (flags_field & RECORD) != 0; + } + public boolean hasDeprecatedAnnotation() { return (flags_field & DEPRECATED_ANNOTATION) != 0; } @@ -402,15 +409,27 @@ return (flags() & INTERFACE) != 0; } + public boolean isAbstract() { + return (flags() & ABSTRACT) != 0; + } + public boolean isPrivate() { return (flags_field & Flags.AccessFlags) == PRIVATE; } + public boolean isPublic() { + return (flags_field & Flags.AccessFlags) == PUBLIC; + } + public boolean isEnum() { return (flags() & ENUM) != 0; } - /** Is this symbol declared (directly or indirectly) local + public boolean isFinal() { + return (flags_field & FINAL) != 0; + } + + /** Is this symbol declared (directly or indirectly) local * to a method or variable initializer? * Also includes fields of inner classes which are in * turn local to a method or variable initializer. @@ -824,7 +843,7 @@ } @Override @DefinedBy(Api.LANGUAGE_MODEL) - public java.util.List<Symbol> getEnclosedElements() { + public List<Symbol> getEnclosedElements() { List<Symbol> list = List.nil(); if (kind == TYP && type.hasTag(TYPEVAR)) { return list; @@ -1260,6 +1279,8 @@ /** the annotation metadata attached to this class */ private AnnotationTypeMetadata annotationTypeMetadata; + private List<RecordComponent> recordComponents = List.nil(); + public ClassSymbol(long flags, Name name, Type type, Symbol owner) { super(TYP, flags, name, type, owner); this.members_field = null; @@ -1328,6 +1349,18 @@ return fullname; } + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public List<Symbol> getEnclosedElements() { + List<Symbol> result = super.getEnclosedElements(); + if (!recordComponents.isEmpty()) { + List<RecordComponent> reversed = recordComponents.reverse(); + for (RecordComponent rc : reversed) { + result = result.prepend(rc); + } + } + return result; + } + public Name flatName() { return flatname; } @@ -1429,6 +1462,8 @@ return ElementKind.INTERFACE; else if ((flags & ENUM) != 0) return ElementKind.ENUM; + else if ((flags & RECORD) != 0) + return ElementKind.RECORD; else return ElementKind.CLASS; } @@ -1440,6 +1475,24 @@ return Flags.asModifierSet(flags & ~DEFAULT); } + public RecordComponent getRecordComponent(VarSymbol field, boolean addIfMissing) { + for (RecordComponent rc : recordComponents) { + if (rc.name == field.name) { + return rc; + } + } + RecordComponent rc = null; + if (addIfMissing) { + recordComponents = recordComponents.append(rc = new RecordComponent(PUBLIC, field.name, field.type, field.owner)); + } + return rc; + } + + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public List<? extends RecordComponent> getRecordComponents() { + return recordComponents; + } + @DefinedBy(Api.LANGUAGE_MODEL) public NestingKind getNestingKind() { apiComplete(); @@ -1453,7 +1506,6 @@ return NestingKind.MEMBER; } - @Override protected <A extends Annotation> Attribute.Compound getAttribute(final Class<A> annoType) { @@ -1469,9 +1521,6 @@ : superType.getAttribute(annoType); } - - - @DefinedBy(Api.LANGUAGE_MODEL) public <R, P> R accept(ElementVisitor<R, P> v, P p) { return v.visitType(this, p); @@ -1552,6 +1601,8 @@ */ public int adr = -1; + public List<Pair<Accessors.Kind, MethodSymbol>> accessors = List.nil(); + /** Construct a variable symbol, given its flags, name, type and owner. */ public VarSymbol(long flags, Name name, Type type, Symbol owner) { @@ -1596,6 +1647,14 @@ return new VarSymbol(flags_field, name, types.memberType(site, this), owner); } + @Override + public Type erasure(Types types) { + if (erasure_field == null) { + erasure_field = types.erasure(type); + } + return erasure_field; + } + @DefinedBy(Api.LANGUAGE_MODEL) public ElementKind getKind() { long flags = flags(); @@ -1677,6 +1736,36 @@ } } + public static class RecordComponent extends VarSymbol implements RecordComponentElement { + + /** + * Construct a record component, given its flags, name, type and owner. + */ + public RecordComponent(long flags, Name name, Type type, Symbol owner) { + super(flags, name, type, owner); + } + + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public ElementKind getKind() { + return ElementKind.RECORD_COMPONENT; + } + + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public ExecutableElement getAccessor() { + for (Pair<Accessors.Kind, MethodSymbol> accessor : accessors) { + if (accessor.fst == Accessors.Kind.GET) { + return accessor.snd; + } + } + throw new AssertionError("record component without accessor"); + } + + @Override @DefinedBy(Api.LANGUAGE_MODEL) + public <R, P> R accept(ElementVisitor<R, P> v, P p) { + return v.visitRecordComponent(this, p); + } + } + public static class ParamSymbol extends VarSymbol { public ParamSymbol(long flags, Name name, Type type, Symbol owner) { super(flags, name, type, owner); @@ -1780,6 +1869,10 @@ ClassFile.CONSTANT_InterfaceMethodref : ClassFile.CONSTANT_Methodref; } + public boolean isDynamic() { + return false; + } + public boolean isHandle() { return false; }
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/SymbolMetadata.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/SymbolMetadata.java Wed Oct 16 17:02:29 2019 -0400 @@ -255,4 +255,36 @@ private boolean isStarted() { return attributes != DECL_NOT_STARTED; } + + private List<Attribute.Compound> removeFromCompoundList(List<Attribute.Compound> l, Attribute.Compound compound) { + ListBuffer<Attribute.Compound> lb = new ListBuffer<>(); + for (Attribute.Compound c : l) { + if (c != compound) { + lb.add(c); + } + } + return lb.toList(); + } + + private List<Attribute.TypeCompound> removeFromTypeCompoundList(List<Attribute.TypeCompound> l, Attribute.TypeCompound compound) { + ListBuffer<Attribute.TypeCompound> lb = new ListBuffer<>(); + for (Attribute.TypeCompound c : l) { + if (c != compound) { + lb.add(c); + } + } + return lb.toList(); + } + + public void remove(Attribute.Compound compound) { + if (attributes.contains(compound)) { + attributes = removeFromCompoundList(attributes, compound); + } else if (type_attributes.contains(compound)) { + type_attributes = removeFromTypeCompoundList(type_attributes, (TypeCompound)compound); + } else if (init_type_attributes.contains(compound)) { + init_type_attributes = removeFromTypeCompoundList(init_type_attributes, (TypeCompound)compound); + } else if (clinit_type_attributes.contains(compound)) { + clinit_type_attributes = removeFromTypeCompoundList(clinit_type_attributes, (TypeCompound)compound); + } + } }
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Symtab.java Wed Oct 16 17:02:29 2019 -0400 @@ -161,6 +161,7 @@ /** Predefined types. */ public final Type objectType; + public final Type objectMethodsType; public final Type objectsType; public final Type classType; public final Type classLoaderType; @@ -214,6 +215,11 @@ public final Type documentedType; public final Type elementTypeType; public final Type functionalInterfaceType; + public final Type typeDescriptorType; + public final Type recordType; + public final Type objectStreamFieldType; + public final Type objectOutputStreamType; + public final Type objectInputStreamType; /** The symbol representing the length field of an array. */ @@ -508,6 +514,7 @@ // Enter predefined classes. All are assumed to be in the java.base module. objectType = enterClass("java.lang.Object"); + objectMethodsType = enterClass("java.lang.invoke.ObjectMethods"); objectsType = enterClass("java.util.Objects"); classType = enterClass("java.lang.Class"); stringType = enterClass("java.lang.String"); @@ -570,6 +577,11 @@ lambdaMetafactory = enterClass("java.lang.invoke.LambdaMetafactory"); stringConcatFactory = enterClass("java.lang.invoke.StringConcatFactory"); functionalInterfaceType = enterClass("java.lang.FunctionalInterface"); + typeDescriptorType = enterClass("java.lang.invoke.TypeDescriptor"); + recordType = enterClass("java.lang.Record"); + objectStreamFieldType = enterClass("java.io.ObjectStreamField"); + objectOutputStreamType = enterClass("java.io.ObjectOutputStream"); + objectInputStreamType = enterClass("java.io.ObjectInputStream"); synthesizeEmptyInterfaceIfMissing(autoCloseableType); synthesizeEmptyInterfaceIfMissing(cloneableType);
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/TypeAnnotations.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/TypeAnnotations.java Wed Oct 16 17:02:29 2019 -0400 @@ -200,7 +200,7 @@ if (e.value.name == names.TYPE) { if (s.kind == TYP) return AnnotationType.DECLARATION; - } else if (e.value.name == names.FIELD) { + } else if (e.value.name == names.FIELD || e.value.name == names.RECORD_COMPONENT) { if (s.kind == VAR && s.owner.kind != MTH) return AnnotationType.DECLARATION; @@ -1266,7 +1266,7 @@ // No type annotations can occur here. } else { // There is nothing else in a variable declaration that needs separation. - Assert.error("Unhandled variable kind"); + Assert.error("Unhandled variable kind: " + tree.sym.getKind()); } scan(tree.mods);
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Types.java Wed Oct 16 17:02:29 2019 -0400 @@ -1479,6 +1479,19 @@ // </editor-fold> + public List<VarSymbol> recordVars(Type t) { + List<VarSymbol> vars = List.nil(); + while (!t.hasTag(NONE)) { + if (t.hasTag(CLASS)) { + for (Symbol s : t.tsym.members().getSymbols(s -> s.kind == VAR && (s.flags() & RECORD) != 0)) { + vars = vars.prepend((VarSymbol)s); + } + } + t = supertype(t); + } + return vars; + } + // <editor-fold defaultstate="collapsed" desc="Contains Type"> public boolean containedBy(Type t, Type s) { switch (t.getTag()) { @@ -5174,7 +5187,7 @@ append('>'); } - private void assembleSig(List<Type> types) { + public void assembleSig(List<Type> types) { for (List<Type> ts = types; ts.nonEmpty(); ts = ts.tail) { assembleSig(ts.head); }
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Annotate.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Annotate.java Wed Oct 16 17:02:29 2019 -0400 @@ -276,7 +276,7 @@ validate(() -> { //validate annotations JavaFileObject prev = log.useSource(localEnv.toplevel.sourcefile); try { - chk.validateAnnotations(annotations, s); + chk.validateAnnotations(annotations, TreeInfo.declarationFor(s, localEnv.tree), s); } finally { log.useSource(prev); }
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java Wed Oct 16 17:02:29 2019 -0400 @@ -1087,12 +1087,11 @@ if (tree.name == names.init && owner.type != syms.objectType) { JCBlock body = tree.body; if (body.stats.isEmpty() || - !TreeInfo.isSelfCall(body.stats.head)) { - body.stats = body.stats. - prepend(typeEnter.SuperCall(make.at(body.pos), - List.nil(), - List.nil(), - false)); + TreeInfo.getConstructorInvocationName(body.stats, names, + (env.enclClass.sym.flags() & RECORD) != 0) == names.empty) { + JCStatement supCall = make.at(body.pos).Exec(make.Apply(List.nil(), + make.Ident(names._super), make.Idents(List.nil()))); + body.stats = body.stats.prepend(supCall); } else if ((env.enclClass.sym.flags() & ENUM) != 0 && (tree.mods.flags & GENERATEDCONSTR) == 0 && TreeInfo.isSuperCall(body.stats.head)) { @@ -1997,6 +1996,11 @@ log.error(tree.pos(), Errors.RetOutsideMeth); } else if (env.info.yieldResult != null) { log.error(tree.pos(), Errors.ReturnOutsideSwitchExpression); + } else if (!env.info.isLambda && + !env.info.isNewClass && + env.enclMethod != null && + TreeInfo.isCanonicalConstructor(env.enclMethod)) { + log.error(tree, Errors.CanonicalCantHaveReturnStatement); } else { // Attribute return expression, if it exists, and check that // it conforms to result type of enclosing method. @@ -2659,8 +2663,10 @@ try { if (needsRecovery && isSerializable(pt())) { localEnv.info.isSerializable = true; - localEnv.info.isLambda = true; + localEnv.info.isSerializableLambda = true; + localEnv.info.isSerializableLambda = true; } + localEnv.info.isLambda = true; List<Type> explicitParamTypes = null; if (that.paramKind == JCLambda.ParameterKind.EXPLICIT) { //attribute lambda parameters @@ -3707,7 +3713,7 @@ } if (env.info.isSerializable) { - chk.checkAccessFromSerializableElement(tree, env.info.isLambda); + chk.checkAccessFromSerializableElement(tree, env.info.isSerializableLambda); } result = checkId(tree, env1.enclClass.sym.type, sym, env, resultInfo); @@ -3849,7 +3855,7 @@ } if (env.info.isSerializable) { - chk.checkAccessFromSerializableElement(tree, env.info.isLambda); + chk.checkAccessFromSerializableElement(tree, env.info.isSerializableLambda); } env.info.selectSuper = selectSuperPrev;
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/AttrContext.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/AttrContext.java Wed Oct 16 17:02:29 2019 -0400 @@ -62,6 +62,10 @@ */ boolean isSerializable = false; + /** Is this a serializable lambda? + */ + boolean isSerializableLambda = false; + /** Is this a lambda environment? */ boolean isLambda = false; @@ -133,12 +137,13 @@ info.yieldResult = yieldResult; info.defaultSuperCallSite = defaultSuperCallSite; info.isSerializable = isSerializable; - info.isLambda = isLambda; + info.isSerializableLambda = isSerializableLambda; info.attributionMode = attributionMode; info.isAnonymousDiamond = isAnonymousDiamond; info.isNewClass = isNewClass; info.preferredTreeForDiagnostics = preferredTreeForDiagnostics; info.visitingServiceImplementation = visitingServiceImplementation; + info.isLambda = isLambda; return info; }
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java Wed Oct 16 17:02:29 2019 -0400 @@ -27,7 +27,9 @@ import java.util.*; import java.util.function.Supplier; - +import java.util.stream.Collectors; + +import javax.lang.model.element.ElementKind; import javax.tools.JavaFileManager; import com.sun.tools.javac.code.*; @@ -116,7 +118,7 @@ names = Names.instance(context); dfltTargetMeta = new Name[] { names.PACKAGE, names.TYPE, - names.FIELD, names.METHOD, names.CONSTRUCTOR, + names.FIELD, names.RECORD_COMPONENT, names.METHOD, names.CONSTRUCTOR, names.ANNOTATION_TYPE, names.LOCAL_VARIABLE, names.PARAMETER}; log = Log.instance(context); rs = Resolve.instance(context); @@ -1159,6 +1161,8 @@ } else { mask = implicit = InterfaceMethodFlags; } + } else if ((sym.owner.flags_field & RECORD) != 0) { + mask = RecordMethodFlags; } else { mask = MethodFlags; } @@ -1169,12 +1173,16 @@ break; case TYP: if (sym.isLocal()) { - mask = LocalClassFlags; + mask = (flags & RECORD) != 0 ? LocalRecordFlags : ExtendedLocalClassFlags; if ((sym.owner.flags_field & STATIC) == 0 && - (flags & ENUM) != 0) + (flags & ENUM) != 0) { log.error(pos, Errors.EnumsMustBeStatic); + } + if ((flags & RECORD) != 0 && (flags & STATIC) == 0) { + log.error(pos, Errors.NestedRecordsMustBeStatic); + } } else if (sym.owner.kind == TYP) { - mask = MemberClassFlags; + mask = (flags & RECORD) != 0 ? ExtendedMemberRecordClassFlags : ExtendedMemberClassFlags; if (sym.owner.owner.kind == PCK || (sym.owner.flags_field & STATIC) != 0) mask |= STATIC; @@ -1183,7 +1191,7 @@ // Nested interfaces and enums are always STATIC (Spec ???) if ((flags & (INTERFACE | ENUM)) != 0 ) implicit = STATIC; } else { - mask = ClassFlags; + mask = ExtendedClassFlags; } // Interfaces are always ABSTRACT if ((flags & INTERFACE) != 0) implicit |= ABSTRACT; @@ -1193,6 +1201,10 @@ mask &= ~(ABSTRACT | FINAL); implicit |= implicitEnumFinalFlag(tree); } + if ((flags & RECORD) != 0) { + // records can't be declared abstract + mask &= ~ABSTRACT; + } // Imply STRICTFP if owner has STRICTFP set. implicit |= sym.owner.flags_field & STRICTFP; break; @@ -2813,9 +2825,9 @@ /** Check the annotations of a symbol. */ - public void validateAnnotations(List<JCAnnotation> annotations, Symbol s) { + public void validateAnnotations(List<JCAnnotation> annotations, JCTree declarationTree, Symbol s) { for (JCAnnotation a : annotations) - validateAnnotation(a, s); + validateAnnotation(a, declarationTree, s); } /** Check the type annotations. @@ -2827,11 +2839,70 @@ /** Check an annotation of a symbol. */ - private void validateAnnotation(JCAnnotation a, Symbol s) { + private void validateAnnotation(JCAnnotation a, JCTree declarationTree, Symbol s) { validateAnnotationTree(a); - - if (a.type.tsym.isAnnotationType() && !annotationApplicable(a, s)) + boolean isRecordMember = s.isRecord() || s.enclClass() != null && s.enclClass().isRecord(); + + boolean isRecordField = isRecordMember && + (s.flags_field & (Flags.PRIVATE | Flags.FINAL | Flags.MANDATED | Flags.RECORD)) != 0 && + declarationTree.hasTag(VARDEF) && + s.owner.kind == TYP; + + if (isRecordField) { + // we are seeing a record field, which had the original annotations, now is the moment, + // before stripping some of them just below, to check if the original annotations + // applied to records at all, first version only cares about declaration annotations + // we will add type annotations later on + Name[] targets = getTargetNames(a); + boolean appliesToRecords = false; + for (Name target : targets) { + appliesToRecords = + target == names.FIELD || + target == names.PARAMETER || + target == names.METHOD || + target == names.TYPE_USE || + target == names.RECORD_COMPONENT; + if (appliesToRecords) { + break; + } + } + if (!appliesToRecords) { log.error(a.pos(), Errors.AnnotationTypeNotApplicable); + } else { + ClassSymbol recordClass = (ClassSymbol) s.owner; + RecordComponent rc = recordClass.getRecordComponent((VarSymbol)s, false); + rc.appendAttributes(s.getRawAttributes().stream().filter(anno -> + Arrays.stream(getTargetNames(anno.type.tsym)).anyMatch(name -> name == names.RECORD_COMPONENT) + ).collect(List.collector())); + rc.appendUniqueTypeAttributes(s.getRawTypeAttributes()); + // to get all the type annotations applied to the type + rc.type = s.type; + } + } + + //System.out.println("at Check.validateAnnotation: flags: " + Flags.toString(s.flags_field) + ", declaration tree " + declarationTree); + + if (a.type.tsym.isAnnotationType() && !annotationApplicable(a, s)) { + // debug + //System.out.println("at Check.validateAnnotation: flags: " + Flags.toString(s.flags_field) + ", declaration tree " + declarationTree); + if (isRecordMember && (s.flags_field & Flags.MANDATED) != 0) { + JCModifiers modifiers = TreeInfo.getModifiers(declarationTree); + // lets first remove the annotation from the modifier + if (modifiers != null) { + ListBuffer<JCAnnotation> newAnnotations = new ListBuffer<>(); + for (JCAnnotation anno : modifiers.annotations) { + if (anno != a) { + newAnnotations.add(anno); + } + } + modifiers.annotations = newAnnotations.toList(); + } + // now lets remove it from the symbol + s.getMetadata().remove(a.attribute); + } else { + log.error(a.pos(), Errors.AnnotationTypeNotApplicable); + } + } if (a.annotationType.type.tsym == syms.functionalInterfaceType.tsym) { if (s.kind != TYP) { @@ -2990,6 +3061,7 @@ targets.add(names.ANNOTATION_TYPE); targets.add(names.CONSTRUCTOR); targets.add(names.FIELD); + targets.add(names.RECORD_COMPONENT); targets.add(names.LOCAL_VARIABLE); targets.add(names.METHOD); targets.add(names.PACKAGE); @@ -3081,24 +3153,36 @@ } /** Is the annotation applicable to the symbol? */ - boolean annotationApplicable(JCAnnotation a, Symbol s) { - Attribute.Array arr = getAttributeTargetAttribute(a.annotationType.type.tsym); + Name[] getTargetNames(JCAnnotation a) { + return getTargetNames(a.annotationType.type.tsym); + } + + public Name[] getTargetNames(TypeSymbol annoSym) { + Attribute.Array arr = getAttributeTargetAttribute(annoSym); Name[] targets; - if (arr == null) { - targets = defaultTargetMetaInfo(a, s); + targets = defaultTargetMetaInfo(); } else { // TODO: can we optimize this? targets = new Name[arr.values.length]; for (int i=0; i<arr.values.length; ++i) { Attribute app = arr.values[i]; if (!(app instanceof Attribute.Enum)) { - return true; // recovery + return new Name[0]; } Attribute.Enum e = (Attribute.Enum) app; targets[i] = e.value.name; } } + return targets; + } + + boolean annotationApplicable(JCAnnotation a, Symbol s) { + Name[] targets = getTargetNames(a); + if (targets.length == 0) { + // recovery + return true; + } for (Name target : targets) { if (target == names.TYPE) { if (s.kind == TYP) @@ -3106,12 +3190,16 @@ } else if (target == names.FIELD) { if (s.kind == VAR && s.owner.kind != MTH) return true; + } else if (target == names.RECORD_COMPONENT) { + if (s.getKind() == ElementKind.RECORD_COMPONENT) { + return true; + } } else if (target == names.METHOD) { if (s.kind == MTH && !s.isConstructor()) return true; } else if (target == names.PARAMETER) { - if (s.kind == VAR && s.owner.kind == MTH && - (s.flags() & PARAMETER) != 0) { + if (s.kind == VAR && + (s.owner.kind == MTH && (s.flags() & PARAMETER) != 0)) { return true; } } else if (target == names.CONSTRUCTOR) { @@ -3158,8 +3246,8 @@ return (Attribute.Array) atValue; } - private final Name[] dfltTargetMeta; - private Name[] defaultTargetMetaInfo(JCAnnotation a, Symbol s) { + public final Name[] dfltTargetMeta; + private Name[] defaultTargetMetaInfo() { return dfltTargetMeta; }
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Enter.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Enter.java Wed Oct 16 17:02:29 2019 -0400 @@ -400,6 +400,9 @@ PackageSymbol packge = (PackageSymbol)owner; for (Symbol q = packge; q != null && q.kind == PCK; q = q.owner) q.flags_field |= EXISTS; + if ((tree.mods.flags & Flags.RECORD) != 0) { + tree.mods.flags &= ~Flags.STATIC; + } c = syms.enterClass(env.toplevel.modle, tree.name, packge); packge.members().enterIfAbsent(c); if ((tree.mods.flags & PUBLIC) != 0 && !classNameMatchesFileName(c, env)) {
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Flow.java Wed Oct 16 17:02:29 2019 -0400 @@ -30,6 +30,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Set; +import java.util.stream.Collectors; import com.sun.source.tree.LambdaExpressionTree.BodyKind; import com.sun.tools.javac.code.*; @@ -525,6 +526,7 @@ try { alive = Liveness.ALIVE; scanStat(tree.body); + tree.completesNormally = alive != Liveness.DEAD; if (alive == Liveness.ALIVE && !tree.sym.type.getReturnType().hasTag(VOID)) log.error(TreeInfo.diagEndPos(tree.body), Errors.MissingRetStmt); @@ -1757,17 +1759,21 @@ /** Check that trackable variable is initialized. */ - void checkInit(DiagnosticPosition pos, VarSymbol sym) { - checkInit(pos, sym, Errors.VarMightNotHaveBeenInitialized(sym)); + boolean checkInit(DiagnosticPosition pos, VarSymbol sym, boolean compactConstructor) { + return checkInit(pos, sym, Errors.VarMightNotHaveBeenInitialized(sym), compactConstructor); } - void checkInit(DiagnosticPosition pos, VarSymbol sym, Error errkey) { + boolean checkInit(DiagnosticPosition pos, VarSymbol sym, Error errkey, boolean compactConstructor) { if ((sym.adr >= firstadr || sym.owner.kind != TYP) && trackable(sym) && !inits.isMember(sym.adr)) { + if (sym.owner.kind != TYP || !compactConstructor || !uninits.isMember(sym.adr)) { log.error(pos, errkey); + } inits.incl(sym.adr); + return false; } + return true; } /** Utility method to reset several Bits instances. @@ -1985,6 +1991,7 @@ // leave caught unchanged. scan(tree.body); + boolean isCompactConstructor = (tree.sym.flags() & Flags.COMPACT_RECORD_CONSTRUCTOR) != 0; if (isInitialConstructor) { boolean isSynthesized = (tree.sym.flags() & GENERATEDCONSTR) != 0; @@ -1994,11 +2001,17 @@ if (var.owner == classDef.sym) { // choose the diagnostic position based on whether // the ctor is default(synthesized) or not - if (isSynthesized) { + if (isSynthesized && !isCompactConstructor) { checkInit(TreeInfo.diagnosticPositionFor(var, vardecl), - var, Errors.VarNotInitializedInDefaultConstructor(var)); + var, Errors.VarNotInitializedInDefaultConstructor(var), isCompactConstructor); } else { - checkInit(TreeInfo.diagEndPos(tree.body), var); + boolean wasInitialized = checkInit(TreeInfo.diagEndPos(tree.body), var, isCompactConstructor && tree.completesNormally); + if (!wasInitialized && var.owner.kind == TYP && isCompactConstructor && uninits.isMember(var.adr) && tree.completesNormally) { + /* this way we indicate Lower that it should generate an initialization for this field + * in the compact constructor + */ + var.flags_field |= COMPACT_RECORD_CONSTRUCTOR; + } } } } @@ -2015,7 +2028,7 @@ Assert.check(exit instanceof AssignPendingExit); inits.assign(((AssignPendingExit) exit).exit_inits); for (int i = firstadr; i < nextadr; i++) { - checkInit(exit.tree.pos(), vardecls[i].sym); + checkInit(exit.tree.pos(), vardecls[i].sym, isCompactConstructor); } } } @@ -2557,7 +2570,7 @@ super.visitSelect(tree); if (TreeInfo.isThisQualifier(tree.selected) && tree.sym.kind == VAR) { - checkInit(tree.pos(), (VarSymbol)tree.sym); + checkInit(tree.pos(), (VarSymbol)tree.sym, false); } } @@ -2618,7 +2631,7 @@ public void visitIdent(JCIdent tree) { if (tree.sym.kind == VAR) { - checkInit(tree.pos(), (VarSymbol)tree.sym); + checkInit(tree.pos(), (VarSymbol)tree.sym, false); referenced(tree.sym); } }
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Lower.java Wed Oct 16 17:02:29 2019 -0400 @@ -28,13 +28,16 @@ import java.util.*; import java.util.Map.Entry; import java.util.function.Function; +import java.util.stream.Collectors; import java.util.stream.Stream; import com.sun.source.tree.CaseTree.CaseKind; import com.sun.tools.javac.code.*; import com.sun.tools.javac.code.Kinds.KindSelector; import com.sun.tools.javac.code.Scope.WriteableScope; +import com.sun.tools.javac.comp.Resolve.MethodResolutionContext; import com.sun.tools.javac.jvm.*; +import com.sun.tools.javac.jvm.PoolConstant.LoadableConstant; import com.sun.tools.javac.main.Option.PkgInfo; import com.sun.tools.javac.resources.CompilerProperties.Fragments; import com.sun.tools.javac.tree.*; @@ -802,6 +805,21 @@ return rs.resolveInternalMethod(pos, attrEnv, qual, name, args, List.nil()); } + private Symbol findMethodOrFailSilently( + DiagnosticPosition pos, + Env<AttrContext> env, + Type site, + Name name, + List<Type> argtypes, + List<Type> typeargtypes) { + MethodResolutionContext resolveContext = rs.new MethodResolutionContext(); + resolveContext.internalResolution = true; + resolveContext.silentFail = true; + Symbol sym = rs.resolveQualifiedMethod(resolveContext, pos, env, site.tsym, + site, name, argtypes, typeargtypes); + return sym; + } + /** Anon inner classes are used as access constructor tags. * accessConstructorTag will use an existing anon class if one is available, * and synthethise a class (with makeEmptyClass) if one is not available. @@ -2188,6 +2206,10 @@ (types.supertype(currentClass.type).tsym.flags() & ENUM) == 0) visitEnumDef(tree); + if ((tree.mods.flags & RECORD) != 0) { + visitRecordDef(tree); + } + // If this is a nested class, define a this$n field for // it and add to proxies. JCVariableDecl otdef = null; @@ -2257,6 +2279,54 @@ result = make_at(tree.pos()).Block(SYNTHETIC, List.nil()); } + List<JCTree> accessors(JCClassDecl tree) { + ListBuffer<JCTree> buffer = new ListBuffer<>(); + tree.defs.stream() + .filter(t -> t.hasTag(VARDEF)) + .map(t -> (JCVariableDecl)t) + .filter(vd -> vd.sym.accessors.nonEmpty()) + .forEach(vd -> { + for (Pair<Accessors.Kind, MethodSymbol> accessor : vd.sym.accessors) { + MethodSymbol accessorSym = accessor.snd; + if ((accessorSym.flags() & Flags.MANDATED) != 0) { + make_at(tree.pos()); + switch (accessor.fst) { + case GET: + buffer.add(make.MethodDef(accessorSym, make.Block(0, + List.of(make.Return(make.Ident(vd.sym)))))); + break; + case SET: + buffer.add(make.MethodDef(accessorSym, make.Block(0, + List.of(make.Exec( + make.Assign(make.Ident(vd.sym), make.Ident(accessorSym.params.head)) + .setType(vd.sym.type)))))); + break; + default: + Assert.error("Cannot get here!"); + } + } + } + }); + return buffer.toList(); + } + + /* this method looks for explicit accessors to add them to the corresponding field + */ + void findUserDefinedAccessors(JCClassDecl tree) { + tree.defs.stream() + .filter(t -> t.hasTag(VARDEF)) + .map(t -> (JCVariableDecl)t) + .filter(vd -> (vd.sym.accessors.isEmpty() && !vd.sym.isStatic())) + .forEach(vd -> { + MethodSymbol msym = lookupMethod(tree.pos(), + vd.name, + tree.sym.type, + List.nil()); + Assert.check(msym != null, "there has to be a user defined accessor"); + vd.sym.accessors = List.of(new Pair<>(Accessors.Kind.GET, msym)); + }); + } + /** Translate an enum class. */ private void visitEnumDef(JCClassDecl tree) { make_at(tree.pos()); @@ -2412,6 +2482,169 @@ prepend(makeLit(syms.stringType, var.name.toString())); } + /** Translate a record. */ + private void visitRecordDef(JCClassDecl tree) { + make_at(tree.pos()); + List<VarSymbol> vars = types.recordVars(tree.type); + MethodHandleSymbol[] getterMethHandles = new MethodHandleSymbol[vars.size()]; + // for the extractor we use the user provided getter, for the rest we access the field directly + MethodHandleSymbol[] getterMethHandlesForExtractor = new MethodHandleSymbol[vars.size()]; + int index = 0; + for (VarSymbol var : vars) { + if (var.owner != tree.sym) { + var = new VarSymbol(var.flags_field, var.name, var.type, tree.sym); + } + getterMethHandles[index] = var.asMethodHandle(true); + if (!var.accessors.isEmpty()) { + getterMethHandlesForExtractor[index] = getterMethHandles[index]; + } else { + MethodSymbol msym = lookupMethod(tree, var.name, tree.sym.type, List.nil()); + getterMethHandlesForExtractor[index] = msym.asHandle(); + } + index++; + } + + tree.defs = tree.defs.appendList(accessors(tree)); + tree.defs = tree.defs.appendList(List.of( + generateRecordMethod(tree, names.toString, vars, getterMethHandles), + generateRecordMethod(tree, names.hashCode, vars, getterMethHandles), + generateRecordMethod(tree, names.equals, vars, getterMethHandles) + )); + findUserDefinedAccessors(tree); + } + + JCTree generateRecordMethod(JCClassDecl tree, Name name, List<VarSymbol> vars, MethodHandleSymbol[] getterMethHandles) { + make_at(tree.pos()); + boolean isEquals = name == names.equals; + MethodSymbol msym = lookupMethod(tree.pos(), + name, + tree.sym.type, + isEquals ? List.of(syms.objectType) : List.nil()); + if ((msym.flags() & RECORD) != 0) { + Name bootstrapName = names.bootstrap; + LoadableConstant[] staticArgsValues = new LoadableConstant[2 + getterMethHandles.length]; + staticArgsValues[0] = (ClassType)tree.sym.type; + String concatNames = vars.stream() + .map(v -> v.name) + .collect(Collectors.joining(";", "", "")); + staticArgsValues[1] = LoadableConstant.String(concatNames); + int index = 2; + for (MethodHandleSymbol mho : getterMethHandles) { + staticArgsValues[index] = mho; + index++; + } + + List<Type> staticArgTypes = List.of(syms.classType, + syms.stringType, + new ArrayType(syms.methodHandleType, syms.arrayClass)); + + JCFieldAccess qualifier = makeIndyQualifier(syms.objectMethodsType, tree, msym, + List.of(syms.methodHandleLookupType, + syms.stringType, + syms.typeDescriptorType).appendList(staticArgTypes), + staticArgsValues, bootstrapName, name, false); + + VarSymbol _this = new VarSymbol(SYNTHETIC, names._this, tree.sym.type, tree.sym); + + JCMethodInvocation proxyCall; + if (!isEquals) { + proxyCall = make.Apply(List.nil(), qualifier, List.of(make.Ident(_this))); + } else { + VarSymbol o = msym.params.head; + o.adr = 0; + proxyCall = make.Apply(List.nil(), qualifier, List.of(make.Ident(_this), make.Ident(o))); + } + proxyCall.type = qualifier.type; + return make.MethodDef(msym, make.Block(0, List.of(make.Return(proxyCall)))); + } else { + return make.Block(SYNTHETIC, List.nil()); + } + } + + private String argsTypeSig(List<Type> typeList) { + LowerSignatureGenerator sg = new LowerSignatureGenerator(); + sg.assembleSig(typeList); + return sg.toString(); + } + + /** + * Signature Generation + */ + private class LowerSignatureGenerator extends Types.SignatureGenerator { + + /** + * An output buffer for type signatures. + */ + StringBuilder sb = new StringBuilder(); + + LowerSignatureGenerator() { + super(types); + } + + @Override + protected void append(char ch) { + sb.append(ch); + } + + @Override + protected void append(byte[] ba) { + sb.append(new String(ba)); + } + + @Override + protected void append(Name name) { + sb.append(name.toString()); + } + + @Override + public String toString() { + return sb.toString(); + } + } + + /** + * Creates an indy qualifier, helpful to be part of an indy invocation + * @param site the site + * @param tree a class declaration tree + * @param msym the method symbol + * @param staticArgTypes the static argument types + * @param staticArgValues the static argument values + * @param bootstrapName the bootstrap name to look for + * @param argName normally bootstraps receives a method name as second argument, if you want that name + * to be different to that of the bootstrap name pass a different name here + * @param isStatic is it static or not + * @return a field access tree + */ + JCFieldAccess makeIndyQualifier( + Type site, + JCClassDecl tree, + MethodSymbol msym, + List<Type> staticArgTypes, + LoadableConstant[] staticArgValues, + Name bootstrapName, + Name argName, + boolean isStatic) { + Symbol bsm = rs.resolveInternalMethod(tree.pos(), attrEnv, site, + bootstrapName, staticArgTypes, List.nil()); + + MethodType indyType = msym.type.asMethodType(); + indyType = new MethodType( + isStatic ? List.nil() : indyType.argtypes.prepend(tree.sym.type), + indyType.restype, + indyType.thrown, + syms.methodClass + ); + DynamicMethodSymbol dynSym = new DynamicMethodSymbol(argName, + syms.noSymbol, + ((MethodSymbol)bsm).asHandle(), + indyType, + staticArgValues); + JCFieldAccess qualifier = make.Select(make.QualIdent(site.tsym), argName); + qualifier.sym = dynSym; + qualifier.type = msym.type.asMethodType().restype; + return qualifier; + } + public void visitMethodDef(JCMethodDecl tree) { if (tree.name == names.init && (currentClass.flags_field&ENUM) != 0) { // Add "String $enum$name, int $enum$ordinal" to the beginning of the @@ -2539,6 +2772,27 @@ lambdaTranslationMap = prevLambdaTranslationMap; } } + if (tree.name == names.init && (tree.sym.flags_field & Flags.COMPACT_RECORD_CONSTRUCTOR) != 0) { + // lets find out if there is any field waiting to be initialized + ListBuffer<VarSymbol> fields = new ListBuffer<>(); + for (Symbol sym : currentClass.getEnclosedElements()) { + if (sym.kind == Kinds.Kind.VAR && ((sym.flags() & RECORD) != 0)) + fields.append((VarSymbol) sym); + } + for (VarSymbol field: fields) { + if ((field.flags_field & Flags.COMPACT_RECORD_CONSTRUCTOR) != 0) { + VarSymbol param = tree.params.stream().filter(p -> p.name == field.name).findFirst().get().sym; + make.at(tree.pos); + tree.body.stats = tree.body.stats.append( + make.Exec( + make.Assign( + make.Select(make.This(field.owner.erasure(types)), field), + make.Ident(param)).setType(field.erasure(types)))); + // we don't need the flag at the field anymore + field.flags_field &= ~Flags.COMPACT_RECORD_CONSTRUCTOR; + } + } + } result = tree; } //where
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/MemberEnter.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/MemberEnter.java Wed Oct 16 17:02:29 2019 -0400 @@ -296,7 +296,8 @@ v.setLazyConstValue(initEnv(tree, initEnv), attr, tree); } } - if (chk.checkUnique(tree.pos(), v, enclScope)) { + if ((v.flags_field & (HYPOTHETICAL | RECORD)) != (HYPOTHETICAL | RECORD) && + chk.checkUnique(tree.pos(), v, enclScope)) { chk.checkTransparentVar(tree.pos(), v, enclScope); enclScope.enter(v); } else if (v.owner.kind == MTH) {
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Resolve.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Resolve.java Wed Oct 16 17:02:29 2019 -0400 @@ -2664,7 +2664,7 @@ List<Type> typeargtypes) { return resolveQualifiedMethod(new MethodResolutionContext(), pos, env, location, site, name, argtypes, typeargtypes); } - private Symbol resolveQualifiedMethod(MethodResolutionContext resolveContext, + public Symbol resolveQualifiedMethod(MethodResolutionContext resolveContext, DiagnosticPosition pos, Env<AttrContext> env, Symbol location, Type site, Name name, List<Type> argtypes, List<Type> typeargtypes) { @@ -2678,7 +2678,7 @@ @Override Symbol access(Env<AttrContext> env, DiagnosticPosition pos, Symbol location, Symbol sym) { if (sym.kind.isResolutionError()) { - sym = super.access(env, pos, location, sym); + sym = resolveContext.silentFail ? sym : super.access(env, pos, location, sym); } else { MethodSymbol msym = (MethodSymbol)sym; if ((msym.flags() & SIGNATURE_POLYMORPHIC) != 0) { @@ -4793,7 +4793,7 @@ * can be nested - this means that when each overload resolution routine should * work within the resolution context it created. */ - class MethodResolutionContext { + public class MethodResolutionContext { private List<Candidate> candidates = List.nil(); @@ -4801,7 +4801,9 @@ MethodCheck methodCheck = resolveMethodCheck; - private boolean internalResolution = false; + public boolean internalResolution = false; + // in case of failure, don't report the error + public boolean silentFail = false; private DeferredAttr.AttrMode attrMode = DeferredAttr.AttrMode.SPECULATIVE; void addInapplicableCandidate(Symbol sym, JCDiagnostic details) {
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TypeEnter.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TypeEnter.java Wed Oct 16 17:02:29 2019 -0400 @@ -25,9 +25,11 @@ package com.sun.tools.javac.comp; +import java.util.ArrayList; import java.util.HashSet; import java.util.Set; import java.util.function.BiConsumer; +import java.util.stream.Collectors; import javax.tools.JavaFileObject; @@ -55,6 +57,9 @@ import static com.sun.tools.javac.code.TypeTag.CLASS; import static com.sun.tools.javac.code.TypeTag.ERROR; import com.sun.tools.javac.resources.CompilerProperties.Fragments; + +import static com.sun.tools.javac.code.TypeTag.*; +import static com.sun.tools.javac.code.TypeTag.BOT; import static com.sun.tools.javac.tree.JCTree.Tag.*; import com.sun.tools.javac.util.Dependencies.CompletionCause; @@ -678,6 +683,9 @@ if (tree.extending != null) { extending = clearTypeParams(tree.extending); supertype = attr.attribBase(extending, baseEnv, true, false, true); + if (supertype == syms.recordType) { + log.error(tree, Errors.InvalidSupertypeRecord); + } } else { extending = null; supertype = ((tree.mods.flags & Flags.ENUM) != 0) @@ -685,7 +693,7 @@ true, false, false) : (sym.fullname == names.java_lang_Object) ? Type.noType - : syms.objectType; + : sym.isRecord() ? syms.recordType : syms.objectType; } ct.supertype_field = modelMissingTypes(baseEnv, supertype, extending, false); @@ -801,7 +809,7 @@ private final class HeaderPhase extends AbstractHeaderPhase { public HeaderPhase() { - super(CompletionCause.HEADER_PHASE, new MembersPhase()); + super(CompletionCause.HEADER_PHASE, new RecordPhase()); } @Override @@ -851,12 +859,10 @@ } } - /** Enter member fields and methods of a class - */ - private final class MembersPhase extends Phase { + private abstract class AbstractMembersPhase extends Phase { - public MembersPhase() { - super(CompletionCause.MEMBERS_PHASE, null); + public AbstractMembersPhase(CompletionCause completionCause, Phase next) { + super(completionCause, next); } private boolean completing; @@ -880,50 +886,86 @@ completing = prevCompleting; } } + } + + private final class RecordPhase extends AbstractMembersPhase { + + public RecordPhase() { + super(CompletionCause.RECORD_PHASE, new MembersPhase()); + } + + @Override + protected void runPhase(Env<AttrContext> env) { + JCClassDecl tree = env.enclClass; + ClassSymbol sym = tree.sym; + if ((sym.flags_field & RECORD) != 0) { + List<JCVariableDecl> fields = TreeInfo.recordFields(tree); + memberEnter.memberEnter(fields, env); + for (JCVariableDecl field : fields) { + sym.getRecordComponent(field.sym, true); + } + } + } + } + + /** Enter member fields and methods of a class + */ + private final class MembersPhase extends AbstractMembersPhase { + + public MembersPhase() { + super(CompletionCause.MEMBERS_PHASE, null); + } @Override protected void runPhase(Env<AttrContext> env) { JCClassDecl tree = env.enclClass; ClassSymbol sym = tree.sym; ClassType ct = (ClassType)sym.type; + boolean defaultConstructorGenerated = false; // Add default constructor if needed. if ((sym.flags() & INTERFACE) == 0 && !TreeInfo.hasConstructors(tree.defs)) { - List<Type> argtypes = List.nil(); - List<Type> typarams = List.nil(); - List<Type> thrown = List.nil(); - long ctorFlags = 0; - boolean based = false; - boolean addConstructor = true; - JCNewClass nc = null; + DefaultConstructorHelper helper = new BasicConstructorHelper(sym); if (sym.name.isEmpty()) { - nc = (JCNewClass)env.next.tree; + JCNewClass nc = (JCNewClass)env.next.tree; if (nc.constructor != null) { - addConstructor = nc.constructor.kind != ERR; - Type superConstrType = types.memberType(sym.type, - nc.constructor); - argtypes = superConstrType.getParameterTypes(); - typarams = superConstrType.getTypeArguments(); - ctorFlags = nc.constructor.flags() & VARARGS; - if (nc.encl != null) { - argtypes = argtypes.prepend(nc.encl.type); - based = true; + if (nc.constructor.kind != ERR) { + helper = new AnonClassConstructorHelper(sym, (MethodSymbol)nc.constructor, nc.encl); + } else { + helper = null; } - thrown = superConstrType.getThrownTypes(); + } + } else if ((sym.flags() & RECORD) != 0) { + helper = new RecordConstructorHelper(sym, TreeInfo.recordFields(tree)); + } + if (helper != null) { + JCTree constrDef = defaultConstructor(make.at(tree.pos), helper); + tree.defs = tree.defs.prepend(constrDef); + defaultConstructorGenerated = true; + } + } else { + if ((sym.flags() & RECORD) != 0) { + // there are constructors but they could be incomplete + for (JCTree def : tree.defs) { + if (TreeInfo.isConstructor(def)) { + Name constructorInvocationName = + TreeInfo.getConstructorInvocationName(((JCMethodDecl)def).body.stats, names, true); + if (constructorInvocationName == names.empty || + constructorInvocationName == names._super) { + JCMethodDecl methDecl = (JCMethodDecl)def; + if ((methDecl.mods.flags & Flags.COMPACT_RECORD_CONSTRUCTOR) != 0) { + if (constructorInvocationName == names.empty) { + JCStatement supCall = make.at(methDecl.body.pos).Exec(make.Apply(List.nil(), + make.Ident(names._super), List.nil())); + methDecl.body.stats = methDecl.body.stats.prepend(supCall); + } + } + } + } } } - if (addConstructor) { - MethodSymbol basedConstructor = nc != null ? - (MethodSymbol)nc.constructor : null; - JCTree constrDef = DefaultConstructor(make.at(tree.pos), sym, - basedConstructor, - typarams, argtypes, thrown, - ctorFlags, based); - tree.defs = tree.defs.prepend(constrDef); - } } - // enter symbols for 'this' into current scope. VarSymbol thisSym = new VarSymbol(FINAL | HASINIT, names._this, sym.type, sym); @@ -945,7 +987,7 @@ } } - finishClass(tree, env); + finishClass(tree, env, defaultConstructorGenerated); if (allowTypeAnnos) { typeAnnotations.organizeTypeAnnotationsSignatures(env, (JCClassDecl)env.tree); @@ -955,13 +997,26 @@ /** Enter members for a class. */ - void finishClass(JCClassDecl tree, Env<AttrContext> env) { + void finishClass(JCClassDecl tree, Env<AttrContext> env, boolean defaultConstructorGenerated) { if ((tree.mods.flags & Flags.ENUM) != 0 && !tree.sym.type.hasTag(ERROR) && (types.supertype(tree.sym.type).tsym.flags() & Flags.ENUM) == 0) { addEnumMembers(tree, env); } - memberEnter.memberEnter(tree.defs, env); + boolean isRecord = (tree.sym.flags_field & RECORD) != 0; + List<JCTree> defsToEnter = isRecord ? + tree.defs.diff(List.convert(JCTree.class, TreeInfo.recordFields(tree))) : tree.defs; + memberEnter.memberEnter(defsToEnter, env); + if (isRecord) { + checkForSerializationMembers(tree, env); + } + List<JCTree> defsBeforeAddingNewMembers = tree.defs; + if (isRecord) { + addRecordMembersIfNeeded(tree, env, defaultConstructorGenerated); + addAccessorsIfNeeded(tree, env); + } + // now we need to enter any additional mandated member that could have been added in the previous step + memberEnter.memberEnter(tree.defs.diff(List.convert(JCTree.class, defsBeforeAddingNewMembers)), env); if (tree.sym.isAnnotationType()) { Assert.check(tree.sym.isCompleted()); @@ -969,6 +1024,65 @@ } } + /** Add the accessors for fields to the symbol table. + */ + private void addAccessorsIfNeeded(JCClassDecl tree, Env<AttrContext> env) { + tree.defs.stream() + .filter(t -> t.hasTag(VARDEF)) + .map(t -> (JCVariableDecl)t) + .filter(vd -> vd.accessors != null && vd.accessors.nonEmpty()) + .forEach(vd -> addAccessors(vd, env)); + } + + private void addAccessors(JCVariableDecl tree, Env<AttrContext> env) { + for (Pair<Accessors.Kind, Name> accessor : tree.accessors) { + Type accessorType = accessor.fst.accessorType(syms, tree.sym.type); + MethodSymbol implSym = lookupMethod(env.enclClass.sym, accessor.snd, accessorType.getParameterTypes()); + if (implSym == null || (implSym.flags_field & MANDATED) != 0) { + JCMethodDecl getter = make.at(tree.pos).MethodDef(make.Modifiers(Flags.PUBLIC | Flags.MANDATED, tree.mods.annotations), + accessor.snd, + make.Type(accessorType.getReturnType()), + List.nil(), + accessorType.getParameterTypes().stream() + .map(ptype -> make.Param(tree.name, tree.sym.type, env.enclClass.sym)) + .collect(List.collector()), + List.nil(), // thrown + null, + null); + memberEnter.memberEnter(getter, env); + RecordComponent rec = ((ClassSymbol) tree.sym.owner).getRecordComponent(tree.sym, false); + rec.accessors = rec.accessors.prepend(new Pair<>(accessor.fst, getter.sym)); + tree.sym.accessors = tree.sym.accessors.prepend(new Pair<>(accessor.fst, getter.sym)); + } else if (implSym != null) { + if ((implSym.flags() & Flags.PUBLIC) == 0) { + log.error(TreeInfo.declarationFor(implSym, env.enclClass), Errors.MethodMustBePublic(implSym.name)); + } + if (!types.isSameType(implSym.type.getReturnType(), tree.sym.type)) { + log.error(TreeInfo.declarationFor(implSym, env.enclClass), Errors.AccessorReturnTypeDoesntMatch(tree.sym.type, implSym.type.getReturnType())); + } + if (implSym.type.asMethodType().thrown.stream().anyMatch(exc -> !isUnchecked(exc))) { + log.error(TreeInfo.declarationFor(implSym, env.enclClass), Errors.MethodCantThrowCheckedException); + } + } + } + } + + /** Is exc an exception symbol that need not be declared? + */ + boolean isUnchecked(ClassSymbol exc) { + return exc.kind == ERR || + exc.isSubClass(syms.errorType.tsym, types) || + exc.isSubClass(syms.runtimeExceptionType.tsym, types); + } + + /** Is exc an exception type that need not be declared? + */ + boolean isUnchecked(Type exc) { + return (exc.hasTag(TYPEVAR)) ? isUnchecked(types.supertype(exc)) : + (exc.hasTag(CLASS)) ? isUnchecked((ClassSymbol)exc.tsym) : + exc.hasTag(BOT); + } + /** Add the implicit members for an enum type * to the symbol table. */ @@ -1003,136 +1117,359 @@ memberEnter.memberEnter(valueOf, env); } + private void checkForSerializationMembers(JCClassDecl tree, Env<AttrContext> env) { + // non-static void writeObject(java.io.ObjectOutputStream) {} + MethodSymbol ms = lookupMethod(tree.sym, names.writeObject, List.of(syms.objectOutputStreamType)); + if (ms != null) { + errorOnSerializationMember(tree, names.writeObject, ms, syms.voidType, false); + } + // non-static void readObjectNoData() {} + ms = lookupMethod(tree.sym, names.readObjectNoData, List.nil()); + if (ms != null) { + errorOnSerializationMember(tree, names.readObjectNoData, ms, syms.voidType, false); + } + // non-static void readObject(java.io.ObjectInputStream stream) {} + ms = lookupMethod(tree.sym, names.readObject, List.of(syms.objectInputStreamType)); + if (ms != null) { + errorOnSerializationMember(tree, names.readObject, ms, syms.voidType, false); + } + Type objectStreamFieldArr = new ArrayType(syms.objectStreamFieldType, syms.arrayClass); + Symbol fieldSym = lookupField(tree.sym, names.serialPersistentFields, objectStreamFieldArr); + if (fieldSym != null) { + errorOnSerializationMember(tree, names.serialPersistentFields, fieldSym, objectStreamFieldArr, true); + } + } + + private void errorOnSerializationMember(JCClassDecl tree, + Name name, Symbol sym, Type expectedType, boolean shouldBeStatic) { + Type typeOrReturnType = sym.kind == MTH ? sym.type.asMethodType().getReturnType() : sym.type; + if (sym.isStatic() == shouldBeStatic && (typeOrReturnType == expectedType || types.isSameType(typeOrReturnType, expectedType))) { + for (JCTree def : tree.defs) { + Symbol sym2 = TreeInfo.symbolFor(def); + if (sym2 == sym) { + log.error(def, Errors.IllegalRecordMember(name)); + return; + } + } + log.error(tree, Errors.IllegalRecordMember(name)); + } + } + + /** Add the implicit members for a record + * to the symbol table. + */ + private void addRecordMembersIfNeeded(JCClassDecl tree, Env<AttrContext> env, boolean defaultConstructorGenerated) { + if (!defaultConstructorGenerated) { + // let's check if there is a constructor with exactly the same arguments as the record components + List<Type> recordComponentTypes = TreeInfo.recordFields(tree).map(vd -> vd.sym.type); + List<Type> erasedTypes = types.erasure(recordComponentTypes); + JCMethodDecl canonicalDecl = null; + for (JCTree def : tree.defs) { + if (TreeInfo.isConstructor(def)) { + JCMethodDecl mdecl = (JCMethodDecl)def; + if (types.isSameTypes(mdecl.sym.type.getParameterTypes(), erasedTypes)) { + canonicalDecl = mdecl; + break; + } + } + } + if (canonicalDecl != null && !types.isSameTypes(erasedTypes, recordComponentTypes)) { + // error we found a constructor with the same erasure as the canonical constructor + log.error(canonicalDecl, Errors.ConstructorWithSameErasureAsCanonical); + } + MethodSymbol canonicalInit = canonicalDecl == null ? + null : + canonicalDecl.sym; + if (canonicalInit == null) { + RecordConstructorHelper helper = new RecordConstructorHelper(tree.sym, TreeInfo.recordFields(tree)); + JCTree constrDef = defaultConstructor(make.at(tree.pos), helper); + tree.defs = tree.defs.prepend(constrDef); + defaultConstructorGenerated = true; + } else { + /* there is an explicit constructor that match the canonical constructor by type + let's check that the match is also by name + */ + List<Name> recordComponentNames = TreeInfo.recordFields(tree).map(vd -> vd.sym.name); + List<Name> initParamNames = canonicalInit.params.map(p -> p.name); + if (!initParamNames.equals(recordComponentNames)) { + log.error(canonicalDecl, Errors.CanonicalWithNameMismatch); + } + if (!canonicalInit.isPublic()) { + log.error(canonicalDecl, Errors.CanonicalConstructorMustBePublic); + } + if (canonicalInit.type.asMethodType().thrown.stream().anyMatch(exc -> !isUnchecked(exc))) { + log.error(canonicalDecl, Errors.MethodCantThrowCheckedException); + } + // let's use the RECORD flag to mark it as the canonical constructor + canonicalInit.flags_field |= Flags.RECORD; + } + } + + if (lookupMethod(tree.sym, names.toString, List.nil()) == null) { + // public String toString() { return ???; } + JCMethodDecl toString = make. + MethodDef(make.Modifiers(Flags.PUBLIC | Flags.RECORD | Flags.MANDATED), + names.toString, + make.Type(syms.stringType), + List.nil(), + List.nil(), + List.nil(), // thrown + null, + null); + memberEnter.memberEnter(toString, env); + } + + if (lookupMethod(tree.sym, names.hashCode, List.nil()) == null) { + // public int hashCode() { return ???; } + JCMethodDecl hashCode = make. + MethodDef(make.Modifiers(Flags.PUBLIC | Flags.RECORD | Flags.FINAL | Flags.MANDATED), + names.hashCode, + make.Type(syms.intType), + List.nil(), + List.nil(), + List.nil(), // thrown + null, + null); + memberEnter.memberEnter(hashCode, env); + } + + if (lookupMethod(tree.sym, names.equals, List.of(syms.objectType)) == null) { + // public boolean equals(Object o) { return ???; } + JCMethodDecl equals = make. + MethodDef(make.Modifiers(Flags.PUBLIC | Flags.RECORD | Flags.FINAL | Flags.MANDATED), + names.equals, + make.Type(syms.booleanType), + List.nil(), + List.of(make.VarDef(make.Modifiers(Flags.PARAMETER), + names.fromString("o"), + make.Type(syms.objectType), null)), + List.nil(), // thrown + null, + null); + memberEnter.memberEnter(equals, env); + } + + // lets remove a temporary flag used to mark if the record component was initially declared as a varargs + List<JCVariableDecl> recordFields = TreeInfo.recordFields(tree); + for (JCVariableDecl field: recordFields) { + field.mods.flags &= ~Flags.ORIGINALLY_VARARGS; + field.sym.flags_field &= ~Flags.ORIGINALLY_VARARGS; + } + } + + } + + private MethodSymbol lookupMethod(TypeSymbol tsym, Name name, List<Type> argtypes) { + for (Symbol s : tsym.members().getSymbolsByName(name, s -> s.kind == MTH)) { + if (types.isSameTypes(s.type.getParameterTypes(), argtypes)) { + return (MethodSymbol) s; + } + } + return null; + } + + private Symbol lookupField(TypeSymbol tsym, Name name, Type type) { + for (Symbol s : tsym.members().getSymbolsByName(name, s -> s.kind == VAR)) { + if (types.isSameType(s.type, type)) { + return s; + } + } + return null; } /* *************************************************************************** * tree building ****************************************************************************/ - /** Generate default constructor for given class. For classes different - * from java.lang.Object, this is: - * - * c(argtype_0 x_0, ..., argtype_n x_n) throws thrown { - * super(x_0, ..., x_n) - * } - * - * or, if based == true: - * - * c(argtype_0 x_0, ..., argtype_n x_n) throws thrown { - * x_0.super(x_1, ..., x_n) - * } - * - * @param make The tree factory. - * @param c The class owning the default constructor. - * @param argtypes The parameter types of the constructor. - * @param thrown The thrown exceptions of the constructor. - * @param based Is first parameter a this$n? - */ - JCTree DefaultConstructor(TreeMaker make, - ClassSymbol c, - MethodSymbol baseInit, - List<Type> typarams, - List<Type> argtypes, - List<Type> thrown, - long flags, - boolean based) { - JCTree result; - if ((c.flags() & ENUM) != 0 && - (types.supertype(c.type).tsym == syms.enumSym)) { + interface DefaultConstructorHelper { + Type constructorType(); + MethodSymbol constructorSymbol(); + Type enclosingType(); + TypeSymbol owner(); + List<Name> superArgs(); + default JCMethodDecl finalAdjustment(JCMethodDecl md) { return md; } + } + + class BasicConstructorHelper implements DefaultConstructorHelper { + + TypeSymbol owner; + Type constructorType; + MethodSymbol constructorSymbol; + + BasicConstructorHelper(TypeSymbol owner) { + this.owner = owner; + } + + @Override + public Type constructorType() { + if (constructorType == null) { + constructorType = new MethodType(List.nil(), syms.voidType, List.nil(), syms.methodClass); + } + return constructorType; + } + + @Override + public MethodSymbol constructorSymbol() { + if (constructorSymbol == null) { + long flags; + if ((owner().flags() & ENUM) != 0 && + (types.supertype(owner().type).tsym == syms.enumSym)) { // constructors of true enums are private - flags = (flags & ~AccessFlags) | PRIVATE | GENERATEDCONSTR; - } else - flags |= (c.flags() & AccessFlags) | GENERATEDCONSTR; - if (c.name.isEmpty()) { - flags |= ANONCONSTR; + flags = PRIVATE | GENERATEDCONSTR; + } else if (owner().isRecord()) { + // record constructors are public + flags = PUBLIC | GENERATEDCONSTR; + } else { + flags = (owner().flags() & AccessFlags) | GENERATEDCONSTR; } - if (based) { - flags |= ANONCONSTR_BASED; + constructorSymbol = new MethodSymbol(flags, names.init, + constructorType(), owner()); + } + return constructorSymbol; } - Type mType = new MethodType(argtypes, null, thrown, c); - Type initType = typarams.nonEmpty() ? - new ForAll(typarams, mType) : - mType; - MethodSymbol init = new MethodSymbol(flags, names.init, - initType, c); - init.params = createDefaultConstructorParams(make, baseInit, init, - argtypes, based); - List<JCVariableDecl> params = make.Params(argtypes, init); - List<JCStatement> stats = List.nil(); - if (c.type != syms.objectType) { - stats = stats.prepend(SuperCall(make, typarams, params, based)); + + @Override + public Type enclosingType() { + return Type.noType; + } + + @Override + public TypeSymbol owner() { + return owner; } - result = make.MethodDef(init, make.Block(0, stats)); - return result; + + @Override + public List<Name> superArgs() { + return List.nil(); + } + } + + class AnonClassConstructorHelper extends BasicConstructorHelper { + + MethodSymbol constr; + Type encl; + boolean based = false; + + AnonClassConstructorHelper(TypeSymbol owner, MethodSymbol constr, JCExpression encl) { + super(owner); + this.constr = constr; + this.encl = encl != null ? encl.type : Type.noType; } - private List<VarSymbol> createDefaultConstructorParams( - TreeMaker make, - MethodSymbol baseInit, - MethodSymbol init, - List<Type> argtypes, - boolean based) { - List<VarSymbol> initParams = null; - List<Type> argTypesList = argtypes; - if (based) { - /* In this case argtypes will have an extra type, compared to baseInit, - * corresponding to the type of the enclosing instance i.e.: - * - * Inner i = outer.new Inner(1){} - * - * in the above example argtypes will be (Outer, int) and baseInit - * will have parameter's types (int). So in this case we have to add - * first the extra type in argtypes and then get the names of the - * parameters from baseInit. - */ - initParams = List.nil(); - VarSymbol param = new VarSymbol(PARAMETER, make.paramName(0), argtypes.head, init); - initParams = initParams.append(param); - argTypesList = argTypesList.tail; + @Override + public Type constructorType() { + if (constructorType == null) { + Type ctype = types.memberType(owner.type, constr); + if (!enclosingType().hasTag(NONE)) { + ctype = types.createMethodTypeWithParameters(ctype, ctype.getParameterTypes().prepend(enclosingType())); + based = true; + } + constructorType = ctype; + } + return constructorType; } - if (baseInit != null && baseInit.params != null && - baseInit.params.nonEmpty() && argTypesList.nonEmpty()) { - initParams = (initParams == null) ? List.nil() : initParams; - List<VarSymbol> baseInitParams = baseInit.params; - while (baseInitParams.nonEmpty() && argTypesList.nonEmpty()) { - VarSymbol param = new VarSymbol(baseInitParams.head.flags() | PARAMETER, - baseInitParams.head.name, argTypesList.head, init); - initParams = initParams.append(param); - baseInitParams = baseInitParams.tail; - argTypesList = argTypesList.tail; + + @Override + public MethodSymbol constructorSymbol() { + MethodSymbol csym = super.constructorSymbol(); + csym.flags_field |= ANONCONSTR | (constr.flags() & VARARGS); + csym.flags_field |= based ? ANONCONSTR_BASED : 0; + ListBuffer<VarSymbol> params = new ListBuffer<>(); + List<Type> argtypes = constructorType().getParameterTypes(); + if (!enclosingType().hasTag(NONE)) { + argtypes = argtypes.tail; + params = params.prepend(new VarSymbol(PARAMETER, make.paramName(0), enclosingType(), csym)); + } + if (constr.params != null) { + for (VarSymbol p : constr.params) { + params.add(new VarSymbol(PARAMETER | p.flags(), p.name, argtypes.head, csym)); + argtypes = argtypes.tail; + } } + csym.params = params.toList(); + return csym; } - return initParams; + + @Override + public Type enclosingType() { + return encl; + } + + @Override + public List<Name> superArgs() { + List<JCVariableDecl> params = make.Params(constructorType().getParameterTypes(), constructorSymbol()); + if (!enclosingType().hasTag(NONE)) { + params = params.tail; + } + return params.map(vd -> vd.name); + } } - /** Generate call to superclass constructor. This is: - * - * super(id_0, ..., id_n) - * - * or, if based == true - * - * id_0.super(id_1,...,id_n) - * - * where id_0, ..., id_n are the names of the given parameters. - * - * @param make The tree factory - * @param params The parameters that need to be passed to super - * @param typarams The type parameters that need to be passed to super - * @param based Is first parameter a this$n? - */ - JCExpressionStatement SuperCall(TreeMaker make, - List<Type> typarams, - List<JCVariableDecl> params, - boolean based) { - JCExpression meth; - if (based) { - meth = make.Select(make.Ident(params.head), names._super); - params = params.tail; + class RecordConstructorHelper extends BasicConstructorHelper { + + List<VarSymbol> recordFieldSymbols; + List<JCVariableDecl> recordFieldDecls; + + RecordConstructorHelper(TypeSymbol owner, List<JCVariableDecl> recordFieldDecls) { + super(owner); + this.recordFieldDecls = recordFieldDecls; + this.recordFieldSymbols = recordFieldDecls.map(vd -> vd.sym); + } + + @Override + public Type constructorType() { + if (constructorType == null) { + List<Type> argtypes = recordFieldSymbols.map(v -> v.type); + constructorType = new MethodType(argtypes, syms.voidType, List.nil(), syms.methodClass); + } + return constructorType; + } + + @Override + public MethodSymbol constructorSymbol() { + MethodSymbol csym = super.constructorSymbol(); + // if we have to generate a default constructor for records we will treat it as the compact one + // to trigger field initialization later on + csym.flags_field |= Flags.COMPACT_RECORD_CONSTRUCTOR; + ListBuffer<VarSymbol> params = new ListBuffer<>(); + for (VarSymbol p : recordFieldSymbols) { + params.add(new VarSymbol(MANDATED | PARAMETER | RECORD | ((p.flags_field & Flags.ORIGINALLY_VARARGS) != 0 ? Flags.VARARGS : 0), p.name, p.type, csym)); + } + csym.params = params.toList(); + csym.flags_field |= RECORD | PUBLIC; + return csym; + } + + @Override + public JCMethodDecl finalAdjustment(JCMethodDecl md) { + List<JCVariableDecl> tmpRecordFieldDecls = recordFieldDecls; + for (JCVariableDecl arg : md.params) { + arg.mods.annotations = tmpRecordFieldDecls.head.mods.annotations; + arg.vartype = tmpRecordFieldDecls.head.vartype; + tmpRecordFieldDecls = tmpRecordFieldDecls.tail; + } + return md; + } + } + + JCTree defaultConstructor(TreeMaker make, DefaultConstructorHelper helper) { + Type initType = helper.constructorType(); + MethodSymbol initSym = helper.constructorSymbol(); + ListBuffer<JCStatement> stats = new ListBuffer<>(); + if (helper.owner().type != syms.objectType) { + JCExpression meth; + if (!helper.enclosingType().hasTag(NONE)) { + meth = make.Select(make.Ident(initSym.params.head), names._super); } else { meth = make.Ident(names._super); } - List<JCExpression> typeargs = typarams.nonEmpty() ? make.Types(typarams) : null; - return make.Exec(make.Apply(typeargs, meth, make.Idents(params))); + List<JCExpression> typeargs = initType.getTypeArguments().nonEmpty() ? + make.Types(initType.getTypeArguments()) : null; + JCStatement superCall = make.Exec(make.Apply(typeargs, meth, helper.superArgs().map(make::Ident))); + stats.add(superCall); + } + JCMethodDecl result = make.MethodDef(initSym, make.Block(0, stats.toList())); + return helper.finalAdjustment(result); } /**
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassReader.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassReader.java Wed Oct 16 17:02:29 2019 -0400 @@ -105,6 +105,10 @@ */ boolean allowModules; + /** Switch: allow records + */ + boolean allowRecords; + /** Lint option: warn about classfile issues */ boolean lintClassfile; @@ -264,6 +268,7 @@ Source source = Source.instance(context); preview = Preview.instance(context); allowModules = Feature.MODULES.allowedInSource(source); + allowRecords = Feature.RECORDS.allowedInSource(source); saveParameterNames = options.isSet(PARAMETERS); @@ -1184,6 +1189,19 @@ } } }, + + new AttributeReader(names.Record, V57, CLASS_ATTRIBUTE) { + @Override + protected boolean accepts(AttributeKind kind) { + return super.accepts(kind) && allowRecords; + } + protected void read(Symbol sym, int attrLen) { + if (sym.kind == TYP) { + sym.flags_field |= RECORD; + } + bp = bp + attrLen; + } + } }; for (AttributeReader r: readers)
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassWriter.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/ClassWriter.java Wed Oct 16 17:02:29 2019 -0400 @@ -49,6 +49,7 @@ import com.sun.tools.javac.resources.CompilerProperties.Errors; import com.sun.tools.javac.resources.CompilerProperties.Fragments; import com.sun.tools.javac.util.*; +import com.sun.tools.javac.util.List; import static com.sun.tools.javac.code.Flags.*; import static com.sun.tools.javac.code.Kinds.Kind.*; @@ -345,8 +346,11 @@ /** Write member (field or method) attributes; * return number of attributes written. */ - int writeMemberAttrs(Symbol sym) { - int acount = writeFlagAttrs(sym.flags()); + int writeMemberAttrs(Symbol sym, boolean isRecordComponent) { + int acount = 0; + if (!isRecordComponent) { + acount = writeFlagAttrs(sym.flags()); + } long flags = sym.flags(); if ((flags & (SYNTHETIC | BRIDGE)) != SYNTHETIC && (flags & ANONCONSTR) == 0 && @@ -403,9 +407,9 @@ return 0; } - private void writeParamAnnotations(List<VarSymbol> params, RetentionPolicy retention) { + databuf.appendByte(params.length()); for (VarSymbol s : params) { ListBuffer<Attribute.Compound> buf = new ListBuffer<>(); for (Attribute.Compound a : s.getRawAttributes()) @@ -427,11 +431,11 @@ /** Write method parameter annotations; * return number of attributes written. */ - int writeParameterAttrs(MethodSymbol m) { + int writeParameterAttrs(List<VarSymbol> vars) { boolean hasVisible = false; boolean hasInvisible = false; - if (m.params != null) { - for (VarSymbol s : m.params) { + if (vars != null) { + for (VarSymbol s : vars) { for (Attribute.Compound a : s.getRawAttributes()) { switch (types.getRetention(a)) { case SOURCE: break; @@ -446,13 +450,13 @@ int attrCount = 0; if (hasVisible) { int attrIndex = writeAttr(names.RuntimeVisibleParameterAnnotations); - writeParamAnnotations(m, RetentionPolicy.RUNTIME); + writeParamAnnotations(vars, RetentionPolicy.RUNTIME); endAttr(attrIndex); attrCount++; } if (hasInvisible) { int attrIndex = writeAttr(names.RuntimeInvisibleParameterAnnotations); - writeParamAnnotations(m, RetentionPolicy.CLASS); + writeParamAnnotations(vars, RetentionPolicy.CLASS); endAttr(attrIndex); attrCount++; } @@ -831,6 +835,23 @@ endAttr(alenIdx); } + int writeRecordAttribute(ClassSymbol csym) { + int alenIdx = writeAttr(names.Record); + Scope s = csym.members(); + databuf.appendChar(csym.getRecordComponents().size()); + for (VarSymbol v: csym.getRecordComponents()) { + //databuf.appendChar(poolWriter.putMember(v.accessors.head.snd)); + databuf.appendChar(poolWriter.putName(v.name)); + databuf.appendChar(poolWriter.putDescriptor(v)); + int acountIdx = beginAttrs(); + int acount = 0; + acount += writeMemberAttrs(v, true); + endAttrs(acountIdx, acount); + } + endAttr(alenIdx); + return 1; + } + /** * Write NestMembers attribute (if needed) */ @@ -920,7 +941,7 @@ endAttr(alenIdx); acount++; } - acount += writeMemberAttrs(v); + acount += writeMemberAttrs(v, false); endAttrs(acountIdx, acount); } @@ -960,13 +981,13 @@ endAttr(alenIdx); acount++; } - if (options.isSet(PARAMETERS) && target.hasMethodParameters()) { + if (target.hasMethodParameters() && (options.isSet(PARAMETERS) || m.isConstructor() && m.isRecord())) { if (!m.isLambdaMethod()) // Per JDK-8138729, do not emit parameters table for lambda bodies. acount += writeMethodParametersAttr(m); } - acount += writeMemberAttrs(m); + acount += writeMemberAttrs(m, false); if (!m.isLambdaMethod()) - acount += writeParameterAttrs(m); + acount += writeParameterAttrs(m.params); endAttrs(acountIdx, acount); } @@ -1600,6 +1621,10 @@ } } + if (c.isRecord()) { + acount += writeRecordAttribute(c); + } + if (!poolWriter.bootstrapMethods.isEmpty()) { writeBootstrapMethods(); acount++;
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/Gen.java Wed Oct 16 17:02:29 2019 -0400 @@ -31,6 +31,7 @@ import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; import com.sun.tools.javac.util.List; import com.sun.tools.javac.code.*; +import com.sun.tools.javac.code.Attribute.Compound; import com.sun.tools.javac.code.Attribute.TypeCompound; import com.sun.tools.javac.code.Symbol.VarSymbol; import com.sun.tools.javac.comp.*;
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/PoolConstant.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/jvm/PoolConstant.java Wed Oct 16 17:02:29 2019 -0400 @@ -35,6 +35,8 @@ import java.util.Objects; import java.util.stream.Stream; +import com.sun.tools.javac.code.Symbol; + /** * This interface models all javac entities that can be used to represent constant pool entries. * A pool constant entity must (i) be associated with a constant pool entry tag and have a function
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/model/JavacElements.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/model/JavacElements.java Wed Oct 16 17:02:29 2019 -0400 @@ -45,6 +45,7 @@ import com.sun.source.util.JavacTask; import com.sun.tools.javac.api.JavacTaskImpl; import com.sun.tools.javac.code.*; +import com.sun.tools.javac.code.Accessors.Kind; import com.sun.tools.javac.code.Attribute.Compound; import com.sun.tools.javac.code.Directive.ExportsDirective; import com.sun.tools.javac.code.Directive.ExportsFlag; @@ -462,6 +463,8 @@ Symbol sym = cast(Symbol.class, e); if ((sym.flags() & Flags.GENERATEDCONSTR) != 0) return Origin.MANDATED; + if ((sym.flags() & Flags.MANDATED) != 0) + return Origin.MANDATED; //TypeElement.getEnclosedElements does not return synthetic elements, //and most synthetic elements are not read from the classfile anyway: return Origin.EXPLICIT; @@ -800,4 +803,18 @@ public void newRound() { resultCache.clear(); } + + @Override + public ExecutableElement getterFor(VariableElement variableElement) { + return accessorFor(Kind.GET, variableElement); + } + + private ExecutableElement accessorFor(Accessors.Kind kind, VariableElement variableElement) { + for (Pair<Accessors.Kind, MethodSymbol> accessor : ((VarSymbol)variableElement).accessors) { + if (accessor.fst == kind) { + return accessor.snd; + } + } + return null; + } }
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/DocCommentParser.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/DocCommentParser.java Wed Oct 16 17:02:29 2019 -0400 @@ -30,6 +30,7 @@ import java.util.Map; import com.sun.source.doctree.AttributeTree.ValueKind; +import com.sun.source.doctree.DocTree; import com.sun.tools.javac.parser.DocCommentParser.TagParser.Kind; import com.sun.tools.javac.parser.Tokens.Comment; import com.sun.tools.javac.parser.Tokens.TokenKind; @@ -1322,6 +1323,12 @@ } }, + // {@getter text} + new AccessorParser(DCTree.Kind.GETTER), + + // {@getter text} + new AccessorParser(DCTree.Kind.SETTER), + // @param parameter-name description new TagParser(Kind.BLOCK, DCTree.Kind.PARAM) { public DCTree parse(int pos) throws ParseException { @@ -1527,4 +1534,14 @@ } + class AccessorParser extends TagParser { + AccessorParser(DocTree.Kind kind) { + super(Kind.BLOCK, kind, true); + } + + public DCTree parse(int pos) throws ParseException { + List<DCTree> desc = blockContent(); + return m.at(pos).newAccessorTree(treeKind, desc); + } + } }
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java Wed Oct 16 17:02:29 2019 -0400 @@ -101,11 +101,13 @@ private Preview preview; /** The name table. */ - private Names names; + protected Names names; /** End position mappings container */ protected final AbstractEndPosTable endPosTable; + private final boolean debug; + // Because of javac's limited lookahead, some contexts are ambiguous in // the presence of type annotations even though they are not ambiguous // in the absence of type annotations. Consider this code: @@ -184,6 +186,12 @@ endPosTable = newEndPosTable(keepEndPositions); this.allowYieldStatement = (!preview.isPreview(Feature.SWITCH_EXPRESSION) || preview.isEnabled()) && Feature.SWITCH_EXPRESSION.allowedInSource(source); + this.allowRecords = true; // to speed up testing for now + /* + (!preview.isPreview(Feature.RECORDS) || preview.isEnabled()) && + Feature.RECORDS.allowedInSource(source); + */ + debug = fac.options.isSet("debug"); } protected AbstractEndPosTable newEndPosTable(boolean keepEndPositions) { @@ -217,6 +225,10 @@ */ boolean allowYieldStatement; + /** Switch: are records allowed in this source level? + */ + boolean allowRecords; + /** The type of the method receiver, as specified by a first "this" parameter. */ JCVariableDecl receiverParam; @@ -1785,7 +1797,7 @@ JCExpression lambdaExpressionOrStatement(boolean hasParens, boolean explicitParams, int pos) { List<JCVariableDecl> params = explicitParams ? - formalParameters(true) : + formalParameters(true, false) : implicitParameters(hasParens); if (explicitParams) { LambdaClassifier lambdaClassifier = new LambdaClassifier(); @@ -2400,7 +2412,7 @@ JCClassDecl body = null; if (token.kind == LBRACE) { int pos = token.pos; - List<JCTree> defs = classOrInterfaceBody(names.empty, false); + List<JCTree> defs = classInterfaceOrRecordBody(names.empty, false, false); JCModifiers mods = F.at(Position.NOPOS).Modifiers(0); body = toP(F.at(pos).AnonymousClassDef(mods, defs)); } @@ -2535,6 +2547,7 @@ @SuppressWarnings("fallthrough") List<JCStatement> blockStatement() { //todo: skip to anchor on error(?) + Comment dc; int pos = token.pos; switch (token.kind) { case RBRACE: case CASE: case DEFAULT: case EOF: @@ -2546,30 +2559,30 @@ return List.of(parseSimpleStatement()); case MONKEYS_AT: case FINAL: { - Comment dc = token.comment(CommentStyle.JAVADOC); + dc = token.comment(CommentStyle.JAVADOC); JCModifiers mods = modifiersOpt(); if (token.kind == INTERFACE || token.kind == CLASS || token.kind == ENUM) { - return List.of(classOrInterfaceOrEnumDeclaration(mods, dc)); + return List.of(classOrRecordOrInterfaceOrEnumDeclaration(mods, dc)); } else { JCExpression t = parseType(true); return localVariableDeclarations(mods, t); } } case ABSTRACT: case STRICTFP: { - Comment dc = token.comment(CommentStyle.JAVADOC); + dc = token.comment(CommentStyle.JAVADOC); JCModifiers mods = modifiersOpt(); - return List.of(classOrInterfaceOrEnumDeclaration(mods, dc)); + return List.of(classOrRecordOrInterfaceOrEnumDeclaration(mods, dc)); } case INTERFACE: case CLASS: - Comment dc = token.comment(CommentStyle.JAVADOC); - return List.of(classOrInterfaceOrEnumDeclaration(modifiersOpt(), dc)); + dc = token.comment(CommentStyle.JAVADOC); + return List.of(classOrRecordOrInterfaceOrEnumDeclaration(modifiersOpt(), dc)); case ENUM: log.error(DiagnosticFlag.SYNTAX, token.pos, Errors.LocalEnum); dc = token.comment(CommentStyle.JAVADOC); - return List.of(classOrInterfaceOrEnumDeclaration(modifiersOpt(), dc)); + return List.of(classOrRecordOrInterfaceOrEnumDeclaration(modifiersOpt(), dc)); case IDENTIFIER: if (token.name() == names.yield && allowYieldStatement) { Token next = S.token(1); @@ -2616,7 +2629,14 @@ //else intentional fall-through } - default: + } + if (isRecordToken() && + (peekToken(TokenKind.IDENTIFIER, TokenKind.LPAREN) || + peekToken(TokenKind.IDENTIFIER, TokenKind.LT))) { + JCModifiers mods = modifiersOpt(); + dc = token.comment(CommentStyle.JAVADOC); + return List.of(recordDeclaration(mods, dc)); + } else { Token prevToken = token; JCExpression t = term(EXPR | TYPE); if (token.kind == COLON && t.hasTag(IDENT)) { @@ -3303,16 +3323,27 @@ log.warning(pos, Warnings.RestrictedTypeNotAllowedPreview(name, Source.JDK13)); } } + if (name == names.record) { + if (allowRecords) { + return true; + } else if (shouldWarn) { + log.warning(pos, Warnings.RestrictedTypeNotAllowedPreview(name, Source.JDK14)); + } + } return false; } + boolean isRestrictedRecordTypeName(Name name) { + return allowRecords && name == names.record; + } + /** VariableDeclaratorId = Ident BracketsOpt */ JCVariableDecl variableDeclaratorId(JCModifiers mods, JCExpression type) { - return variableDeclaratorId(mods, type, false); + return variableDeclaratorId(mods, type, false, false); } //where - JCVariableDecl variableDeclaratorId(JCModifiers mods, JCExpression type, boolean lambdaParameter) { + JCVariableDecl variableDeclaratorId(JCModifiers mods, JCExpression type, boolean lambdaParameter, boolean recordComponent) { int pos = token.pos; Name name; if (lambdaParameter && token.kind == UNDERSCORE) { @@ -3358,7 +3389,15 @@ log.error(token.pos, Errors.VarargsAndOldArraySyntax); } type = bracketsOpt(type); - return toP(F.at(pos).VarDef(mods, name, type, null)); + List<Pair<Accessors.Kind, Name>> accessors = null; + if (recordComponent) { + if (forbiddenRecordComponentNames.contains(name.toString())) { + log.error(pos, Errors.IllegalRecordComponentName(name)); + } + accessors = List.of(new Pair<>(Accessors.Kind.GET, name)); + } + + return toP(F.at(pos).VarDef(mods, name, type, null, accessors)); } /** Resources = Resource { ";" Resources } @@ -3627,7 +3666,7 @@ nextToken(); return toP(F.at(pos).Skip()); } else { - return classOrInterfaceOrEnumDeclaration(modifiersOpt(mods), docComment); + return classOrRecordOrInterfaceOrEnumDeclaration(modifiersOpt(mods), docComment); } } @@ -3636,9 +3675,11 @@ * @param mods Any modifiers starting the class or interface declaration * @param dc The documentation comment for the class, or null. */ - protected JCStatement classOrInterfaceOrEnumDeclaration(JCModifiers mods, Comment dc) { + protected JCStatement classOrRecordOrInterfaceOrEnumDeclaration(JCModifiers mods, Comment dc) { if (token.kind == CLASS) { return classDeclaration(mods, dc); + } if (isRecordToken()) { + return recordDeclaration(mods, dc); } else if (token.kind == INTERFACE) { return interfaceDeclaration(mods, dc); } else if (token.kind == ENUM) { @@ -3656,8 +3697,12 @@ if (parseModuleInfo) { erroneousTree = syntaxError(pos, errs, Errors.ExpectedModuleOrOpen); } else { + if (allowRecords) { + erroneousTree = syntaxError(pos, errs, Errors.Expected4(CLASS, INTERFACE, ENUM, RECORD)); + } else { erroneousTree = syntaxError(pos, errs, Errors.Expected3(CLASS, INTERFACE, ENUM)); } + } return toP(F.Exec(erroneousTree)); } } @@ -3684,22 +3729,129 @@ nextToken(); implementing = typeList(); } - List<JCTree> defs = classOrInterfaceBody(name, false); + List<JCTree> defs = classInterfaceOrRecordBody(name, false, false); JCClassDecl result = toP(F.at(pos).ClassDef( mods, name, typarams, extending, implementing, defs)); attach(result, dc); return result; } + protected JCClassDecl recordDeclaration(JCModifiers mods, Comment dc) { + int pos = token.pos; + if ((mods.flags & Flags.ABSTRACT) != 0) { + log.error(mods.pos, Errors.RecordCantBeAbstract); + } + nextToken(); + mods.flags |= Flags.RECORD | Flags.STATIC | Flags.FINAL; + Name name = typeName(); + + List<JCTypeParameter> typarams = typeParametersOpt(); + + List<JCVariableDecl> headerFields = formalParameters(false, true); + + List<JCExpression> implementing = List.nil(); + if (token.kind == IMPLEMENTS) { + nextToken(); + implementing = typeList(); + } + List<JCTree> defs = List.nil(); + defs = classInterfaceOrRecordBody(name, false, true); + java.util.List<JCVariableDecl> fields = new ArrayList<>(); + Set<Name> seenNames = new HashSet<>(); + for (JCVariableDecl field : headerFields) { + if (seenNames.add(field.name)) { + fields.add(field); + } else { + log.error(field.pos(), Errors.RecordCantDeclareDuplicateFields); + } + } + for (JCTree def : defs) { + if (def.hasTag(METHODDEF)) { + JCMethodDecl methDef = (JCMethodDecl) def; + if (methDef.name == names.init && methDef.params.isEmpty()) { + if ((methDef.mods.flags & Flags.PUBLIC) == 0) { + log.error(methDef, Errors.MethodMustBePublic(names.init)); + } + ListBuffer<JCVariableDecl> tmpParams = new ListBuffer<>(); + for (JCVariableDecl param : fields) { + tmpParams.add(F.at(param).VarDef(F.Modifiers(Flags.PARAMETER | param.mods.flags & Flags.VARARGS), param.name, param.vartype, null)); + } + methDef.params = tmpParams.toList(); + } + } + } + for (int i = fields.size() - 1; i >= 0; i--) { + JCVariableDecl field = fields.get(i); + if ((field.mods.flags & Flags.VARARGS) != 0) { + if ((field.mods.flags & Flags.VARARGS) != 0) { + field.mods.flags = field.mods.flags & ~Flags.VARARGS | Flags.ORIGINALLY_VARARGS; + field = F.at(field).VarDef(field.mods, field.name, field.vartype, null, field.accessors); + } + } + defs = defs.prepend(field); + } + JCClassDecl result = toP(F.at(pos).ClassDef(mods, name, typarams, null, implementing, defs)); + attach(result, dc); + return result; + } + Name typeName() { int pos = token.pos; Name name = ident(); if (isRestrictedTypeName(name, pos, true)) { reportSyntaxError(pos, Errors.RestrictedTypeNotAllowed(name, name == names.var ? Source.JDK10 : Source.JDK13)); } + + if (isRestrictedRecordTypeName(name)) { + reportSyntaxError(pos, Errors.RecordNotAllowed(name)); + } return name; } + List<JCVariableDecl> headerFields() { + ListBuffer<JCVariableDecl> fields = new ListBuffer<>(); + if (token.kind == LPAREN) { + nextToken(); + // check for empty record + if (token.kind == RPAREN) { + nextToken(); + return List.nil(); + } + fields.add(headerField()); + while (token.kind == COMMA) { + nextToken(); + fields.add(headerField()); + } + accept(RPAREN); + } else { + accept(LPAREN); + } + return fields.toList(); + } + + static final Set<String> forbiddenRecordComponentNames = Set.of( + "clone", "finalize", "getClass", "hashCode", + "notify", "notifyAll", "readObjectNoData", + "readResolve", "serialPersistentFields", + "serialVersionUID", "toString", "wait", + "writeReplace"); + + JCVariableDecl headerField() { + JCModifiers mods = modifiersOpt(); + if (mods.flags != 0) { + log.error(mods.pos, Errors.RecordCantDeclareFieldModifiers); + } + mods.flags |= Flags.RECORD | Flags.FINAL | Flags.PRIVATE | Flags.MANDATED; + JCExpression type = parseType(); + int pos = token.pos; + Name id = ident(); + if (forbiddenRecordComponentNames.contains(id.toString())) { + log.error(pos, Errors.IllegalRecordComponentName(id)); + } + List<Pair<Accessors.Kind, Name>> accessors = List.of(new Pair<>(Accessors.Kind.GET, id)); + return toP(F.at(pos).VarDef(mods, id, type, null, accessors)); + } + /** InterfaceDeclaration = INTERFACE Ident TypeParametersOpt * [EXTENDS TypeList] InterfaceBody * @param mods The modifiers starting the interface declaration @@ -3718,7 +3870,7 @@ nextToken(); extending = typeList(); } - List<JCTree> defs = classOrInterfaceBody(name, true); + List<JCTree> defs = classInterfaceOrRecordBody(name, true, false); JCClassDecl result = toP(F.at(pos).ClassDef( mods, name, typarams, null, extending, defs)); attach(result, dc); @@ -3809,8 +3961,8 @@ hasStructuralErrors = true; } wasError = false; - defs.appendList(classOrInterfaceBodyDeclaration(enumName, - false)); + defs.appendList(classOrInterfaceOrRecordBodyDeclaration(enumName, + false, false)); if (token.pos <= endPosTable.errorEndPos) { // error recovery skip(false, true, true, false); @@ -3863,7 +4015,7 @@ JCClassDecl body = null; if (token.kind == LBRACE) { JCModifiers mods1 = F.at(Position.NOPOS).Modifiers(Flags.ENUM); - List<JCTree> defs = classOrInterfaceBody(names.empty, false); + List<JCTree> defs = classInterfaceOrRecordBody(names.empty, false, false); body = toP(F.at(identPos).AnonymousClassDef(mods1, defs)); } if (args.isEmpty() && body == null) @@ -3893,7 +4045,7 @@ /** ClassBody = "{" {ClassBodyDeclaration} "}" * InterfaceBody = "{" {InterfaceBodyDeclaration} "}" */ - List<JCTree> classOrInterfaceBody(Name className, boolean isInterface) { + List<JCTree> classInterfaceOrRecordBody(Name className, boolean isInterface, boolean isRecord) { accept(LBRACE); if (token.pos <= endPosTable.errorEndPos) { // error recovery @@ -3903,7 +4055,7 @@ } ListBuffer<JCTree> defs = new ListBuffer<>(); while (token.kind != RBRACE && token.kind != EOF) { - defs.appendList(classOrInterfaceBodyDeclaration(className, isInterface)); + defs.appendList(classOrInterfaceOrRecordBodyDeclaration(className, isInterface, isRecord)); if (token.pos <= endPosTable.errorEndPos) { // error recovery skip(false, true, true, false); @@ -3942,7 +4094,7 @@ * ) * */ - protected List<JCTree> classOrInterfaceBodyDeclaration(Name className, boolean isInterface) { + protected List<JCTree> classOrInterfaceOrRecordBodyDeclaration(Name className, boolean isInterface, boolean isRecord) { if (token.kind == SEMI) { nextToken(); return List.nil(); @@ -3950,10 +4102,14 @@ Comment dc = token.comment(CommentStyle.JAVADOC); int pos = token.pos; JCModifiers mods = modifiersOpt(); + if (debug) { + System.out.println("read flags " + Flags.toString(mods.flags)); + } if (token.kind == CLASS || + isRecordToken() || token.kind == INTERFACE || token.kind == ENUM) { - return List.of(classOrInterfaceOrEnumDeclaration(mods, dc)); + return List.of(classOrRecordOrInterfaceOrEnumDeclaration(mods, dc)); } else if (token.kind == LBRACE && (mods.flags & Flags.StandardFlags & ~Flags.STATIC) == 0 && mods.annotations.isEmpty()) { @@ -3990,22 +4146,27 @@ // method returns types are un-annotated types type = unannotatedType(false); } - if (token.kind == LPAREN && !isInterface && type.hasTag(IDENT)) { + if ((token.kind == LPAREN && !isInterface || + isRecord && token.kind == LBRACE) && type.hasTag(IDENT)) { if (isInterface || tk.name() != className) log.error(DiagnosticFlag.SYNTAX, pos, Errors.InvalidMethDeclRetTypeReq); else if (annosAfterParams.nonEmpty()) illegal(annosAfterParams.head.pos); + if (isRecord && token.kind == LBRACE) { + mods.flags |= Flags.COMPACT_RECORD_CONSTRUCTOR; + } return List.of(methodDeclaratorRest( pos, mods, null, names.init, typarams, - isInterface, true, dc)); + isInterface, true, isRecord, dc)); } else { pos = token.pos; Name name = ident(); if (token.kind == LPAREN) { return List.of(methodDeclaratorRest( pos, mods, type, name, typarams, - isInterface, isVoid, dc)); + isInterface, isVoid, false, dc)); } else if (!isVoid && typarams.isEmpty()) { + if (!isRecord || (isRecord && (mods.flags & Flags.STATIC) != 0)) { List<JCTree> defs = variableDeclaratorsRest(pos, mods, type, name, isInterface, dc, new ListBuffer<JCTree>(), false).toList(); @@ -4013,6 +4174,10 @@ storeEnd(defs.last(), S.prevToken().endPos); return defs; } else { + nextToken(); + return List.of(syntaxError(pos, null, Errors.RecordFieldsMustBeInHeader)); + } + } else { pos = token.pos; List<JCTree> err; if (isVoid || typarams.nonEmpty()) { @@ -4031,6 +4196,10 @@ } } + boolean isRecordToken() { + return allowRecords && token.kind == IDENTIFIER && token.name() == names.record; + } + /** MethodDeclaratorRest = * FormalParameters BracketsOpt [THROWS TypeList] ( MethodBody | [DEFAULT AnnotationValue] ";") * VoidMethodDeclaratorRest = @@ -4044,6 +4213,7 @@ Name name, List<JCTypeParameter> typarams, boolean isInterface, boolean isVoid, + boolean isRecord, Comment dc) { if (isInterface) { if ((mods.flags & Flags.STATIC) != 0) { @@ -4057,13 +4227,16 @@ try { this.receiverParam = null; // Parsing formalParameters sets the receiverParam, if present - List<JCVariableDecl> params = formalParameters(); + List<JCVariableDecl> params = List.nil(); + List<JCExpression> thrown = List.nil(); + if (!isRecord || name != names.init || token.kind == LPAREN) { + params = formalParameters(); if (!isVoid) type = bracketsOpt(type); - List<JCExpression> thrown = List.nil(); if (token.kind == THROWS) { nextToken(); thrown = qualidentList(true); } + } JCBlock body = null; JCExpression defaultValue; if (token.kind == LBRACE) { @@ -4174,15 +4347,15 @@ * FormalParameterListNovarargs = [ FormalParameterListNovarargs , ] FormalParameter */ List<JCVariableDecl> formalParameters() { - return formalParameters(false); + return formalParameters(false, false); } - List<JCVariableDecl> formalParameters(boolean lambdaParameters) { + List<JCVariableDecl> formalParameters(boolean lambdaParameters, boolean recordComponents) { ListBuffer<JCVariableDecl> params = new ListBuffer<>(); JCVariableDecl lastParam; accept(LPAREN); if (token.kind != RPAREN) { - this.allowThisIdent = !lambdaParameters; - lastParam = formalParameter(lambdaParameters); + this.allowThisIdent = !lambdaParameters && !recordComponents; + lastParam = formalParameter(lambdaParameters, recordComponents); if (lastParam.nameexpr != null) { this.receiverParam = lastParam; } else { @@ -4194,7 +4367,7 @@ log.error(DiagnosticFlag.SYNTAX, lastParam, Errors.VarargsMustBeLast); } nextToken(); - params.append(lastParam = formalParameter(lambdaParameters)); + params.append(lastParam = formalParameter(lambdaParameters, recordComponents)); } } if (token.kind == RPAREN) { @@ -4306,10 +4479,17 @@ * LastFormalParameter = { FINAL | '@' Annotation } Type '...' Ident | FormalParameter */ protected JCVariableDecl formalParameter() { - return formalParameter(false); + return formalParameter(false, false); } - protected JCVariableDecl formalParameter(boolean lambdaParameter) { - JCModifiers mods = optFinal(Flags.PARAMETER); + + protected JCVariableDecl formalParameter(boolean lambdaParameter, boolean recordComponent) { + JCModifiers mods = !recordComponent ? optFinal(Flags.PARAMETER) : modifiersOpt(); + if (recordComponent && mods.flags != 0) { + log.error(mods.pos, Errors.RecordCantDeclareFieldModifiers); + } + if (recordComponent) { + mods.flags |= Flags.RECORD | Flags.FINAL | Flags.PRIVATE | Flags.MANDATED; + } // need to distinguish between vararg annos and array annos // look at typeAnnotationsPushedBack comment this.permitTypeAnnotationsPushBack = true; @@ -4330,12 +4510,12 @@ } typeAnnotationsPushedBack = List.nil(); } - return variableDeclaratorId(mods, type, lambdaParameter); + return variableDeclaratorId(mods, type, lambdaParameter, recordComponent); } protected JCVariableDecl implicitParameter() { JCModifiers mods = F.at(token.pos).Modifiers(Flags.PARAMETER); - return variableDeclaratorId(mods, null, true); + return variableDeclaratorId(mods, null, true, false); } /* ---------- auxiliary methods -------------- */
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/Tokens.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/Tokens.java Wed Oct 16 17:02:29 2019 -0400 @@ -84,7 +84,7 @@ key = new TokenKind[maxKey+1]; for (int i = 0; i <= maxKey; i++) key[i] = TokenKind.IDENTIFIER; for (TokenKind t : TokenKind.values()) { - if (t.name != null) + if (t.name != null && !t.reserved()) key[tokenName[t.ordinal()].getIndex()] = t; } } @@ -226,6 +226,7 @@ GTGTEQ(">>="), GTGTGTEQ(">>>="), MONKEYS_AT("@"), + RECORD("record", Tag.RESERVED), CUSTOM; public final String name; @@ -276,6 +277,10 @@ } } + public boolean reserved() { + return tag == Tag.RESERVED; + } + public String getKind() { return "Token"; } @@ -315,7 +320,8 @@ DEFAULT, NAMED, STRING, - NUMERIC + NUMERIC, + RESERVED; } /** The token kind */
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java Wed Oct 16 17:02:29 2019 -0400 @@ -974,7 +974,7 @@ * Leave class public for external testing purposes. */ public static class ComputeAnnotationSet extends - ElementScanner9<Set<TypeElement>, Set<TypeElement>> { + ElementScanner14<Set<TypeElement>, Set<TypeElement>> { final Elements elements; public ComputeAnnotationSet(Elements elements) {
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacRoundEnvironment.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacRoundEnvironment.java Wed Oct 16 17:02:29 2019 -0400 @@ -109,9 +109,9 @@ /** * Returns the elements annotated with the given annotation type. * Only type elements <i>included</i> in this round of annotation - * processing, or declarations of members, parameters, or type - * parameters declared within those, are returned. Included type - * elements are {@linkplain #getRootElements specified + * processing, or declarations of members, parameters, type + * parameters, or record components declared within those, are returned. + * Included type elements are {@linkplain #getRootElements specified * types} and any types nested within them. * * @param a annotation type being requested @@ -123,7 +123,7 @@ throwIfNotAnnotation(a); Set<Element> result = Collections.emptySet(); - ElementScanner9<Set<Element>, TypeElement> scanner = + ElementScanner14<Set<Element>, TypeElement> scanner = new AnnotationSetScanner(result); for (Element element : rootElements) @@ -144,7 +144,7 @@ } Set<Element> result = Collections.emptySet(); - ElementScanner9<Set<Element>, Set<TypeElement>> scanner = + ElementScanner14<Set<Element>, Set<TypeElement>> scanner = new AnnotationSetMultiScanner(result); for (Element element : rootElements) @@ -224,7 +224,7 @@ } private static abstract class ElementScanningIncludingTypeParameters<R, P> - extends ElementScanner9<R, P> { + extends ElementScanner14<R, P> { protected ElementScanningIncludingTypeParameters(R defaultValue) { super(defaultValue);
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/PrintingProcessor.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/PrintingProcessor.java Wed Oct 16 17:02:29 2019 -0400 @@ -39,6 +39,8 @@ import java.io.Writer; import java.util.*; import java.util.stream.Collectors; +import java.util.stream.Stream; + import com.sun.tools.javac.util.DefinedBy; import com.sun.tools.javac.util.DefinedBy.Api; @@ -89,7 +91,7 @@ * Used for the -Xprint option and called by Elements.printElements */ public static class PrintingElementVisitor - extends SimpleElementVisitor9<PrintingElementVisitor, Boolean> { + extends SimpleElementVisitor14<PrintingElementVisitor, Boolean> { int indentation; // Indentation level; final PrintWriter writer; final Elements elementUtils; @@ -111,6 +113,13 @@ } @Override @DefinedBy(Api.LANGUAGE_MODEL) + public PrintingElementVisitor visitRecordComponent(RecordComponentElement e, Boolean p) { + // Do nothing; printing of component information done by + // printing the record type itself + return this; + } + + @Override @DefinedBy(Api.LANGUAGE_MODEL) public PrintingElementVisitor visitExecutable(ExecutableElement e, Boolean p) { ElementKind kind = e.getKind(); @@ -123,7 +132,7 @@ enclosing != null && NestingKind.ANONYMOUS == // Use an anonymous class to determine anonymity! - (new SimpleElementVisitor9<NestingKind, Void>() { + (new SimpleElementVisitor14<NestingKind, Void>() { @Override @DefinedBy(Api.LANGUAGE_MODEL) public NestingKind visitType(TypeElement e, Void p) { return e.getNestingKind(); @@ -216,6 +225,16 @@ printFormalTypeParameters(e, false); + if (kind == RECORD) { + // Print out record components + writer.print("("); + writer.print(e.getRecordComponents() + .stream() + .map(recordDes -> recordDes.asType().toString() + " " + recordDes.getSimpleName()) + .collect(Collectors.joining(", "))); + writer.print(")"); + } + // Print superclass information if informative if (kind == CLASS) { TypeMirror supertype = e.getSuperclass(); @@ -255,7 +274,13 @@ for(Element element : enclosedElements) this.visit(element); } else { - for(Element element : e.getEnclosedElements()) + for(Element element : + (kind != RECORD ? + e.getEnclosedElements() : + e.getEnclosedElements() + .stream() + .filter(elt -> elementUtils.getOrigin(elt) == Elements.Origin.EXPLICIT ) + .collect(Collectors.toList()) ) ) this.visit(element); } @@ -448,6 +473,10 @@ modifiers.remove(Modifier.ABSTRACT); break; + case RECORD: + modifiers.remove(Modifier.FINAL); + break; + case METHOD: case FIELD: Element enclosingElement = e.getEnclosingElement();
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/compiler.properties Wed Oct 16 17:02:29 2019 -0400 @@ -2159,6 +2159,10 @@ compiler.err.expected3=\ {0}, {1}, or {2} expected +# 0: token, 1: token, 2: token, 3: token +compiler.err.expected4=\ + {0}, {1}, {2}, or {3} expected + compiler.err.premature.eof=\ reached end of file while parsing @@ -3400,6 +3404,92 @@ compiler.err.switch.mixing.case.types=\ different case kinds used in the switch +### +# errors related to records + +compiler.err.record.cant.be.abstract=\ + records cannot be abstract + +compiler.err.record.cant.declare.duplicate.fields=\ + records cannot declare fields with the same name + +compiler.err.record.cant.declare.field.modifiers=\ + records cannot declare field modifiers + +compiler.err.record.can.only.declare.methods.as.members=\ + records can only declare methods as members + +# 0: fragment +compiler.err.cant.extend.record=\ + Illegal ''extends'' clause for record\n\ + {0} + +compiler.misc.bad.record.super=\ + A record must extend class AbstractRecord or an ''abstract'' record + +# 0: type, 1: name, 2: type, 3: name +compiler.misc.super.field.mismatch=\ + Superclass field declaration mismatch\n\ + expected: {0} {1}\n\ + found: {2} {3} + +compiler.misc.bad.super.fields=\ + A record cannot have both an explicit constructor, and an implicit superclass header. + +compiler.err.record.fields.must.be.in.header=\ + instance fields in a record must be declared in the header + +compiler.err.local.record=\ + records must not be local + +compiler.err.nested.records.must.be.static=\ + nested records must always be static + +# 0: name +compiler.err.duplicate.argument.to.super=\ + duplicate argument {0}, arguments passed to the super of a record must be unique + +# 0: name +compiler.err.record.not.allowed=\ + ''{0}'' not allowed here\n\ + as of release 14, ''{0}'' is a restricted type name and cannot be used for type declarations + +# 0: name +compiler.err.method.must.be.public=\ + method: {0}(), must be public + +compiler.err.canonical.constructor.must.be.public=\ + canonical constructor must be public + +compiler.err.canonical.with.name.mismatch=\ + constructor with same signature as canonical does not match by parameter names + +compiler.err.constructor.with.same.erasure.as.canonical=\ + constructor with same erasure as canonical constructor + +# 0: type, 1: type +compiler.err.accessor.return.type.doesnt.match=\ + type returned by the accessor is not the same as the type of the corresponding record component\n\ + required: {0}\n\ + found: {1}\n\ + +# 0: name +compiler.err.illegal.record.component.name=\ + Illegal record component name: {0} + +# 0: name +compiler.err.illegal.record.member=\ + Illegal record member: {0} + +compiler.err.invalid.supertype.record=\ + no class can explicitly extend java.lang.Record + +compiler.err.method.cant.throw.checked.exception=\ + method cannot throw checked exception + +compiler.err.canonical.cant.have.return.statement=\ + canonical constructor can not have return statements + ############################################ # messages previouly at javac.properties
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DCTree.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DCTree.java Wed Oct 16 17:02:29 2019 -0400 @@ -575,6 +575,32 @@ } } + public static class DCAccessor extends DCInlineTag implements AccessorTree { + public final Kind kind; + public final List<? extends DocTree> description; + + DCAccessor(Kind kind, List<? extends DocTree> description) { + Assert.check(kind == Kind.GETTER || kind == Kind.SETTER); + this.kind = kind; + this.description = description; + } + + @Override @DefinedBy(Api.COMPILER_TREE) + public Kind getKind() { + return kind; + } + + @Override @DefinedBy(Api.COMPILER_TREE) + public <R, D> R accept(DocTreeVisitor<R, D> v, D d) { + return v.visitAccessor(this, d); + } + + @Override @DefinedBy(Api.COMPILER_TREE) + public List<? extends DocTree> getDescription() { + return description; + } + } + public static class DCParam extends DCBlockTag implements ParamTree { public final boolean isTypeParameter; public final DCIdentifier name;
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocPretty.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocPretty.java Wed Oct 16 17:02:29 2019 -0400 @@ -129,6 +129,19 @@ } } + @Override + public Void visitAccessor(AccessorTree node, Void aVoid) { + try { + print("{"); + printTagName(node); + print(node.getDescription()); + print("}"); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + return null; + } + @Override @DefinedBy(Api.COMPILER_TREE) public Void visitAttribute(AttributeTree node, Void p) { try {
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocTreeMaker.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/DocTreeMaker.java Wed Oct 16 17:02:29 2019 -0400 @@ -55,6 +55,7 @@ import com.sun.tools.javac.parser.ReferenceParser; import com.sun.tools.javac.parser.Tokens.Comment; import com.sun.tools.javac.parser.Tokens.Comment.CommentStyle; +import com.sun.tools.javac.tree.DCTree.DCAccessor; import com.sun.tools.javac.tree.DCTree.DCAttribute; import com.sun.tools.javac.tree.DCTree.DCAuthor; import com.sun.tools.javac.tree.DCTree.DCComment; @@ -361,6 +362,13 @@ } @Override @DefinedBy(Api.COMPILER_TREE) + public DCAccessor newAccessorTree(Kind kind, List<? extends DocTree> desc) { + DCAccessor tree = new DCAccessor(kind, desc); + tree.pos = pos; + return tree; + } + + @Override @DefinedBy(Api.COMPILER_TREE) public DCParam newParamTree(boolean isTypeParameter, IdentifierTree name, List<? extends DocTree> description) { DCParam tree = new DCParam(isTypeParameter, (DCIdentifier) name, cast(description)); tree.pos = pos;
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/JCTree.java Wed Oct 16 17:02:29 2019 -0400 @@ -848,6 +848,9 @@ public JCExpression defaultValue; /** method symbol */ public MethodSymbol sym; + /** does this method completes normally */ + public boolean completesNormally; + protected JCMethodDecl(JCModifiers mods, Name name, JCExpression restype, @@ -932,23 +935,27 @@ public VarSymbol sym; /** explicit start pos */ public int startPos = Position.NOPOS; + /** accessors */ + public List<Pair<Accessors.Kind, Name>> accessors; protected JCVariableDecl(JCModifiers mods, Name name, JCExpression vartype, JCExpression init, - VarSymbol sym) { + VarSymbol sym, + List<Pair<Accessors.Kind, Name>> accessors) { this.mods = mods; this.name = name; this.vartype = vartype; this.init = init; this.sym = sym; + this.accessors = accessors; } protected JCVariableDecl(JCModifiers mods, JCExpression nameexpr, JCExpression vartype) { - this(mods, null, vartype, null, null); + this(mods, null, vartype, null, null, null); this.nameexpr = nameexpr; if (nameexpr.hasTag(Tag.IDENT)) { this.name = ((JCIdent)nameexpr).name;
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeInfo.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeInfo.java Wed Oct 16 17:02:29 2019 -0400 @@ -82,6 +82,10 @@ } } + public static boolean isCanonicalConstructor(JCTree tree) { + return isConstructor(tree) && ((JCMethodDecl)tree).sym.isRecord(); + } + public static boolean isReceiverParam(JCTree tree) { if (tree.hasTag(VARDEF)) { return ((JCVariableDecl)tree).nameexpr != null; @@ -98,6 +102,25 @@ return false; } + /** Is there a constructor invocation in the given list of trees? + */ + public static Name getConstructorInvocationName(List<? extends JCTree> trees, Names names, boolean isRecord) { + for (JCTree tree : trees) { + if (tree.hasTag(EXEC)) { + JCExpressionStatement stat = (JCExpressionStatement)tree; + if (stat.expr.hasTag(APPLY)) { + JCMethodInvocation apply = (JCMethodInvocation)stat.expr; + Name methName = TreeInfo.name(apply.meth); + if (methName == names._this || + methName == names._super) { + return methName; + } + } + } + } + return names.empty; + } + public static boolean isMultiCatch(JCCatch catchClause) { return catchClause.param.vartype.hasTag(TYPEUNION); } @@ -190,6 +213,23 @@ } } + public static List<JCVariableDecl> recordFields(JCClassDecl tree) { + return tree.defs.stream() + .filter(t -> t.hasTag(VARDEF)) + .map(t -> (JCVariableDecl)t) + .filter(vd -> (vd.getModifiers().flags & (Flags.RECORD)) == RECORD) + .collect(List.collector()); + } + + public static List<Type> recordFieldTypes(JCClassDecl tree) { + return tree.defs.stream() + .filter(t -> t.hasTag(VARDEF)) + .map(t -> (JCVariableDecl)t) + .filter(vd -> (vd.getModifiers().flags & (Flags.RECORD)) == RECORD) + .map(vd -> vd.type) + .collect(List.collector()); + } + /** Is this a constructor whose first (non-synthetic) statement is not * of the form this(...)? */ @@ -886,6 +926,24 @@ } } + /** If this tree has a modifiers field, return it otherwise return null + */ + public static JCModifiers getModifiers(JCTree tree) { + tree = skipParens(tree); + switch (tree.getTag()) { + case VARDEF: + return ((JCVariableDecl) tree).mods; + case METHODDEF: + return ((JCMethodDecl) tree).mods; + case CLASSDEF: + return ((JCClassDecl) tree).mods; + case MODULEDEF: + return ((JCModuleDecl) tree).mods; + default: + return null; + } + } + /** Return true if this is a nonstatic selection. */ public static boolean nonstaticSelect(JCTree tree) { tree = skipParens(tree);
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeMaker.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/TreeMaker.java Wed Oct 16 17:02:29 2019 -0400 @@ -211,7 +211,11 @@ } public JCVariableDecl VarDef(JCModifiers mods, Name name, JCExpression vartype, JCExpression init) { - JCVariableDecl tree = new JCVariableDecl(mods, name, vartype, init, null); + return VarDef(mods, name, vartype, init, null); + } + + public JCVariableDecl VarDef(JCModifiers mods, Name name, JCExpression vartype, JCExpression init, List<Pair<Accessors.Kind, Name>> accessors) { + JCVariableDecl tree = new JCVariableDecl(mods, name, vartype, init, null, accessors); tree.pos = pos; return tree; } @@ -840,7 +844,7 @@ v.name, Type(v.type), init, - v).setPos(pos).setType(v.type); + v, null).setPos(pos).setType(v.type); } /** Create annotation trees from annotations.
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Dependencies.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Dependencies.java Wed Oct 16 17:02:29 2019 -0400 @@ -91,6 +91,7 @@ HIERARCHY_PHASE, IMPORTS_PHASE, MEMBER_ENTER, + RECORD_PHASE, MEMBERS_PHASE, OTHER; }
--- a/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Names.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/util/Names.java Wed Oct 16 17:02:29 2019 -0400 @@ -70,6 +70,8 @@ public final Name uses; public final Name open; public final Name with; + public final Name get; + public final Name set; public final Name yield; // field and method names @@ -83,6 +85,7 @@ public final Name deserializeLambda; public final Name desiredAssertionStatus; public final Name equals; + public final Name oldEquals; public final Name error; public final Name finalize; public final Name forRemoval; @@ -100,6 +103,8 @@ public final Name value; public final Name valueOf; public final Name values; + public final Name readResolve; + public final Name readObject; // class names public final Name java_io_Serializable; @@ -141,6 +146,7 @@ public final Name ModuleResolution; public final Name NestHost; public final Name NestMembers; + public final Name Record; public final Name RuntimeInvisibleAnnotations; public final Name RuntimeInvisibleParameterAnnotations; public final Name RuntimeInvisibleTypeAnnotations; @@ -168,6 +174,7 @@ public final Name TYPE; public final Name TYPE_PARAMETER; public final Name TYPE_USE; + public final Name RECORD_COMPONENT; // members of java.lang.annotation.RetentionPolicy public final Name CLASS; @@ -191,6 +198,21 @@ public final Name makeConcat; public final Name makeConcatWithConstants; + // record related + // members of java.lang.invoke.ObjectMethods + public final Name bootstrap; + + public final Name record; + public final Name where; + public final Name non; + public final Name ofLazyProjection; + + // serialization members, used by records too + public final Name serialPersistentFields; + public final Name writeObject; + public final Name writeReplace; + public final Name readObjectNoData; + public final Name.Table table; public Names(Context context) { @@ -220,6 +242,8 @@ uses = fromString("uses"); open = fromString("open"); with = fromString("with"); + get = fromString("get"); + set = fromString("set"); yield = fromString("yield"); // field and method names @@ -233,6 +257,7 @@ deserializeLambda = fromString("$deserializeLambda$"); desiredAssertionStatus = fromString("desiredAssertionStatus"); equals = fromString("equals"); + oldEquals = fromString("oldEquals"); error = fromString("<error>"); finalize = fromString("finalize"); forRemoval = fromString("forRemoval"); @@ -250,6 +275,8 @@ value = fromString("value"); valueOf = fromString("valueOf"); values = fromString("values"); + readResolve = fromString("readResolve"); + readObject = fromString("readObject"); dollarThis = fromString("$this"); // class names @@ -292,6 +319,7 @@ ModuleResolution = fromString("ModuleResolution"); NestHost = fromString("NestHost"); NestMembers = fromString("NestMembers"); + Record = fromString("Record"); RuntimeInvisibleAnnotations = fromString("RuntimeInvisibleAnnotations"); RuntimeInvisibleParameterAnnotations = fromString("RuntimeInvisibleParameterAnnotations"); RuntimeInvisibleTypeAnnotations = fromString("RuntimeInvisibleTypeAnnotations"); @@ -319,6 +347,7 @@ TYPE = fromString("TYPE"); TYPE_PARAMETER = fromString("TYPE_PARAMETER"); TYPE_USE = fromString("TYPE_USE"); + RECORD_COMPONENT = fromString("RECORD_COMPONENT"); // members of java.lang.annotation.RetentionPolicy CLASS = fromString("CLASS"); @@ -340,6 +369,17 @@ // string concat makeConcat = fromString("makeConcat"); makeConcatWithConstants = fromString("makeConcatWithConstants"); + + bootstrap = fromString("bootstrap"); + record = fromString("record"); + where = fromString("where"); + non = fromString("non"); + ofLazyProjection = fromString("ofLazyProjection"); + + serialPersistentFields = fromString("serialPersistentFields"); + writeObject = fromString("writeObject"); + writeReplace = fromString("writeReplace"); + readObjectNoData = fromString("readObjectNoData"); } protected Name.Table createTable(Options options) {
--- a/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/PubapiVisitor.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.compiler/share/classes/com/sun/tools/sjavac/comp/PubapiVisitor.java Wed Oct 16 17:02:29 2019 -0400 @@ -36,7 +36,7 @@ import javax.lang.model.element.TypeParameterElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.ElementScanner9; +import javax.lang.model.util.ElementScanner14; import com.sun.tools.javac.code.Symbol.ClassSymbol; import com.sun.tools.javac.util.DefinedBy; @@ -56,7 +56,7 @@ * This code and its internal interfaces are subject to change or * deletion without notice.</b> */ -public class PubapiVisitor extends ElementScanner9<Void, Void> { +public class PubapiVisitor extends ElementScanner14<Void, Void> { private PubApi collectedApi = new PubApi();
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/AbstractIndexWriter.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/AbstractIndexWriter.java Wed Oct 16 17:02:29 2019 -0400 @@ -38,7 +38,7 @@ import javax.lang.model.element.ModuleElement; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; -import javax.lang.model.util.SimpleElementVisitor9; +import javax.lang.model.util.SimpleElementVisitor14; import com.sun.source.doctree.DocTree; import jdk.javadoc.internal.doclets.formats.html.markup.Entity; @@ -180,7 +180,7 @@ protected void addDescription(Content dl, Element element) { SearchIndexItem si = new SearchIndexItem(); - new SimpleElementVisitor9<Void, Void>() { + new SimpleElementVisitor14<Void, Void>() { @Override public Void visitModule(ModuleElement e, Void p) {
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/ClassWriterImpl.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/ClassWriterImpl.java Wed Oct 16 17:02:29 2019 -0400 @@ -33,6 +33,7 @@ import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; import javax.lang.model.element.ModuleElement; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; @@ -294,9 +295,9 @@ * Get the class hierarchy tree for the given class. * * @param type the class to print the hierarchy for - * @return a content tree for class inheritence + * @return a content tree for class inheritance */ - private Content getClassInheritenceTree(TypeMirror type) { + private Content getClassInheritanceTree(TypeMirror type) { TypeMirror sup; HtmlTree classTree = null; do { @@ -347,19 +348,20 @@ if (!utils.isClass(typeElement)) { return; } - classContentTree.add(getClassInheritenceTree(typeElement.asType())); + classContentTree.add(getClassInheritanceTree(typeElement.asType())); } /** * {@inheritDoc} */ @Override - public void addTypeParamInfo(Content classInfoTree) { - if (!utils.getTypeParamTrees(typeElement).isEmpty()) { - Content typeParam = (new ParamTaglet()).getTagletOutput(typeElement, + public void addParamInfo(Content classInfoTree) { + if (utils.hasBlockTag(typeElement, DocTree.Kind.PARAM)) { + Content paramInfo = (new ParamTaglet()).getTagletOutput(typeElement, getTagletWriterInstance(false)); - Content dl = HtmlTree.DL(typeParam); - classInfoTree.add(dl); + if (!paramInfo.isEmpty()) { + classInfoTree.add(HtmlTree.DL(paramInfo)); + } } }
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/Contents.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/Contents.java Wed Oct 16 17:02:29 2019 -0400 @@ -155,6 +155,7 @@ public final Content propertyLabel; public final Content propertyDetailsLabel; public final Content propertySummaryLabel; + public final Content record; public final Content seeLabel; public final Content serializedForm; public final Content servicesLabel; @@ -282,6 +283,7 @@ propertyLabel = getContent("doclet.Property"); propertyDetailsLabel = getContent("doclet.Property_Detail"); propertySummaryLabel = getContent("doclet.Property_Summary"); + record = getContent("doclet.Record"); seeLabel = getContent("doclet.See"); serializedForm = getContent("doclet.Serialized_Form"); servicesLabel = getContent("doclet.Services");
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDoclet.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDoclet.java Wed Oct 16 17:02:29 2019 -0400 @@ -236,22 +236,21 @@ * {@inheritDoc} */ @Override // defined by AbstractDoclet - protected void generateClassFiles(SortedSet<TypeElement> arr, ClassTree classtree) + protected void generateClassFiles(SortedSet<TypeElement> typeElems, ClassTree classTree) throws DocletException { - List<TypeElement> list = new ArrayList<>(arr); - for (TypeElement klass : list) { - if (utils.hasHiddenTag(klass) || - !(configuration.isGeneratedDoc(klass) && utils.isIncluded(klass))) { + for (TypeElement te : typeElems) { + if (utils.hasHiddenTag(te) || + !(configuration.isGeneratedDoc(te) && utils.isIncluded(te))) { continue; } - if (utils.isAnnotationType(klass)) { + if (utils.isAnnotationType(te)) { AbstractBuilder annotationTypeBuilder = configuration.getBuilderFactory() - .getAnnotationTypeBuilder(klass); + .getAnnotationTypeBuilder(te); annotationTypeBuilder.build(); } else { AbstractBuilder classBuilder = - configuration.getBuilderFactory().getClassBuilder(klass, classtree); + configuration.getBuilderFactory().getClassBuilder(te, classTree); classBuilder.build(); } }
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDocletWriter.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDocletWriter.java Wed Oct 16 17:02:29 2019 -0400 @@ -51,7 +51,7 @@ import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.SimpleAnnotationValueVisitor9; -import javax.lang.model.util.SimpleElementVisitor9; +import javax.lang.model.util.SimpleElementVisitor14; import javax.lang.model.util.SimpleTypeVisitor9; import com.sun.source.doctree.AttributeTree; @@ -1512,7 +1512,8 @@ @Override public Boolean visitLink(LinkTree node, Content c) { // we need to pass the DocTreeImpl here, so ignore node - result.add(seeTagToContent(element, tag)); + Content content = seeTagToContent(element, tag); + result.add(content); return false; } @@ -1666,7 +1667,7 @@ return text; } - DocPath redirectPathFromRoot = new SimpleElementVisitor9<DocPath, Void>() { + DocPath redirectPathFromRoot = new SimpleElementVisitor14<DocPath, Void>() { @Override public DocPath visitType(TypeElement e, Void p) { return docPaths.forPackage(utils.containingPackage(e));
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/PackageWriterImpl.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/PackageWriterImpl.java Wed Oct 16 17:02:29 2019 -0400 @@ -214,6 +214,15 @@ * {@inheritDoc} */ @Override + public void addRecordSummary(SortedSet<TypeElement> records, Content summaryContentTree) { + TableHeader tableHeader= new TableHeader(contents.record, contents.descriptionLabel); + addClassesSummary(records, resources.recordSummary, tableHeader, summaryContentTree); + } + + /** + * {@inheritDoc} + */ + @Override public void addExceptionSummary(SortedSet<TypeElement> exceptions, Content summaryContentTree) { TableHeader tableHeader= new TableHeader(contents.exception, contents.descriptionLabel); addClassesSummary(exceptions, resources.exceptionSummary, tableHeader, summaryContentTree);
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/TagletWriterImpl.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/TagletWriterImpl.java Wed Oct 16 17:02:29 2019 -0400 @@ -28,13 +28,20 @@ import java.util.List; import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.ModuleElement; +import javax.lang.model.element.Name; +import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.SimpleElementVisitor9; +import javax.lang.model.util.SimpleElementVisitor14; import com.sun.source.doctree.DocTree; +import com.sun.source.doctree.DocTree.Kind; import com.sun.source.doctree.IndexTree; +import com.sun.source.doctree.ParamTree; import com.sun.source.doctree.SystemPropertyTree; import jdk.javadoc.internal.doclets.formats.html.markup.ContentBuilder; import jdk.javadoc.internal.doclets.formats.html.markup.HtmlStyle; @@ -193,12 +200,15 @@ public Content paramTagOutput(Element element, DocTree paramTag, String paramName) { ContentBuilder body = new ContentBuilder(); CommentHelper ch = utils.getCommentHelper(element); - body.add(HtmlTree.CODE(new RawHtml(paramName))); + // define id attributes for state components so that generated descriptions may refer to them + boolean defineID = (element.getKind() == ElementKind.RECORD) + && (paramTag instanceof ParamTree) && !((ParamTree) paramTag).isTypeParameter(); + Content nameTree = new StringContent(paramName); + body.add(HtmlTree.CODE(defineID ? HtmlTree.A_ID("param-" + paramName, nameTree) : nameTree)); body.add(" - "); List<? extends DocTree> description = ch.getDescription(configuration, paramTag); body.add(htmlWriter.commentTagsToContent(paramTag, element, description, false, inSummary)); - HtmlTree result = HtmlTree.DD(body); - return result; + return HtmlTree.DD(body); } /** @@ -272,6 +282,26 @@ } + public Content accessorTagOutput(Element holder, List<? extends DocTree> tags) { + if (!tags.isEmpty()) { + //Todo: check that there's only one tag + DocTree.Kind kind = tags.get(0).getKind(); + ExecutableElement accessor = utils.findAccessorFor((VariableElement)holder, kind); + //add reference to getter/setter + Content body = htmlWriter.getDocLink(LinkInfoImpl.Kind.SEE_TAG, (TypeElement)holder.getEnclosingElement(), + accessor, accessor.getSimpleName() + utils.makeSignature(accessor, true), false, false); + ContentBuilder result = new ContentBuilder(); + String key = kind == Kind.GETTER ? + "doclet.getter" : "doclet.setter"; + result.add(HtmlTree.DT(HtmlTree.SPAN(HtmlStyle.seeLabel, + new StringContent(resources.getText(key))))); + result.add(HtmlTree.DD(body)); + return result; + } else { + return new ContentBuilder(); + } + } + private void appendSeparatorIfNotEmpty(ContentBuilder body) { if (!body.isEmpty()) { body.add(", "); @@ -427,7 +457,7 @@ si.setDescription(desc); si.setUrl(htmlWriter.path.getPath() + "#" + anchorName); DocPaths docPaths = configuration.docPaths; - new SimpleElementVisitor9<Void, Void>() { + new SimpleElementVisitor14<Void, Void>() { @Override public Void visitVariable(VariableElement e, Void p) { TypeElement te = utils.getEnclosingTypeElement(e);
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/BaseConfiguration.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/BaseConfiguration.java Wed Oct 16 17:02:29 2019 -0400 @@ -26,13 +26,14 @@ package jdk.javadoc.internal.doclets.toolkit; import java.io.*; +import java.lang.ref.*; import java.util.*; import javax.lang.model.element.Element; import javax.lang.model.element.ModuleElement; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; -import javax.lang.model.util.SimpleElementVisitor9; +import javax.lang.model.util.SimpleElementVisitor14; import javax.tools.JavaFileManager; import javax.tools.JavaFileObject; @@ -1235,7 +1236,7 @@ : docEnv.getSpecifiedElements(); for (Element e : inset) { - new SimpleElementVisitor9<Void, Void>() { + new SimpleElementVisitor14<Void, Void>() { @Override @DefinedBy(Api.LANGUAGE_MODEL) public Void visitModule(ModuleElement e, Void p) {
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/ClassWriter.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/ClassWriter.java Wed Oct 16 17:02:29 2019 -0400 @@ -73,11 +73,11 @@ public Content getClassInfoTreeHeader(); /** - * Add the type parameter information. + * Add the type parameter and state component information. * * @param classInfoTree content tree to which the documentation will be added */ - public void addTypeParamInfo(Content classInfoTree); + public void addParamInfo(Content classInfoTree); /** * Add all super interfaces if this is an interface.
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/CommentUtils.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/CommentUtils.java Wed Oct 16 17:02:29 2019 -0400 @@ -38,20 +38,26 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Name; import javax.lang.model.element.PackageElement; +import javax.lang.model.element.RecordComponentElement; +import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.util.Elements; import javax.tools.FileObject; import javax.tools.JavaFileObject; import javax.tools.SimpleJavaFileObject; +import com.sun.source.doctree.AttributeTree; import com.sun.source.doctree.DocCommentTree; import com.sun.source.doctree.DocTree; import com.sun.source.doctree.IdentifierTree; +import com.sun.source.doctree.ParamTree; import com.sun.source.doctree.ReferenceTree; import com.sun.source.doctree.TextTree; import com.sun.source.util.DocTreeFactory; @@ -65,6 +71,7 @@ public class CommentUtils { final BaseConfiguration configuration; + final Utils utils; final Resources resources; final DocTreeFactory treeFactory; final HashMap<Element, DocCommentDuo> dcTreesMap = new HashMap<>(); @@ -73,6 +80,7 @@ protected CommentUtils(BaseConfiguration configuration) { this.configuration = configuration; + utils = configuration.utils; resources = configuration.getResources(); trees = configuration.docEnv.getDocTrees(); treeFactory = trees.getDocTreeFactory(); @@ -107,17 +115,17 @@ return treeFactory.newSeeTree(list); } - public DocTree makeTextTree(String content) { - TextTree text = treeFactory.newTextTree(content); - return (DocTree) text; + public TextTree makeTextTree(String content) { + return treeFactory.newTextTree(content); } - public void setEnumValuesTree(Element e) { - Utils utils = configuration.utils; - String klassName = utils.getSimpleName(utils.getEnclosingTypeElement(e)); + public TextTree makeTextTreeForResource(String key) { + return treeFactory.newTextTree(resources.getText(key)); + } + public void setEnumValuesTree(ExecutableElement ee) { List<DocTree> fullBody = new ArrayList<>(); - fullBody.add(treeFactory.newTextTree(resources.getText("doclet.enum_values_doc.fullbody", klassName))); + fullBody.add(treeFactory.newTextTree(resources.getText("doclet.enum_values_doc.fullbody"))); List<DocTree> descriptions = new ArrayList<>(); descriptions.add(treeFactory.newTextTree(resources.getText("doclet.enum_values_doc.return"))); @@ -125,11 +133,10 @@ List<DocTree> tags = new ArrayList<>(); tags.add(treeFactory.newReturnTree(descriptions)); DocCommentTree docTree = treeFactory.newDocCommentTree(fullBody, tags); - dcTreesMap.put(e, new DocCommentDuo(null, docTree)); + dcTreesMap.put(ee, new DocCommentDuo(null, docTree)); } - public void setEnumValueOfTree(Element e) { - + public void setEnumValueOfTree(ExecutableElement ee) { List<DocTree> fullBody = new ArrayList<>(); fullBody.add(treeFactory.newTextTree(resources.getText("doclet.enum_valueof_doc.fullbody"))); @@ -137,7 +144,6 @@ List<DocTree> paramDescs = new ArrayList<>(); paramDescs.add(treeFactory.newTextTree(resources.getText("doclet.enum_valueof_doc.param_name"))); - ExecutableElement ee = (ExecutableElement) e; java.util.List<? extends VariableElement> parameters = ee.getParameters(); VariableElement param = parameters.get(0); IdentifierTree id = treeFactory.newIdentifierTree(elementUtils.getName(param.getSimpleName().toString())); @@ -161,7 +167,230 @@ DocCommentTree docTree = treeFactory.newDocCommentTree(fullBody, tags); - dcTreesMap.put(e, new DocCommentDuo(null, docTree)); + dcTreesMap.put(ee, new DocCommentDuo(null, docTree)); + } + + /** + * Generates the description for the canonical constructor for a record. + * @param ee the constructor + */ + public void setRecordConstructorTree(ExecutableElement ee) { + TypeElement te = utils.getEnclosingTypeElement(ee); + + List<DocTree> fullBody = + makeDescriptionWithName("doclet.record_constructor_doc.fullbody", te.getSimpleName()); + + List<DocTree> tags = new ArrayList<>(); + java.util.List<? extends VariableElement> parameters = ee.getParameters(); + for (VariableElement param : ee.getParameters()) { + Name name = param.getSimpleName(); + IdentifierTree id = treeFactory.newIdentifierTree(name); + tags.add(treeFactory.newParamTree(false, id, + makeDescriptionWithComponent("doclet.record_constructor_doc.param_name", te, name))); + } + + DocCommentTree docTree = treeFactory.newDocCommentTree(fullBody, tags); + dcTreesMap.put(ee, new DocCommentDuo(null, docTree)); + } + + /** + * Generates the description for the standard {@code equals} method for a record. + * @param ee the {@code equals} method + */ + public void setRecordEqualsTree(ExecutableElement ee) { + List<DocTree> fullBody = new ArrayList<>(); + add(fullBody, "doclet.record_equals_doc.fullbody.head"); + fullBody.add(treeFactory.newTextTree(" ")); + + List<? extends RecordComponentElement> comps = ((TypeElement) ee.getEnclosingElement()).getRecordComponents(); + boolean hasPrimitiveComponents = + comps.stream().anyMatch(e -> e.asType().getKind().isPrimitive()); + boolean hasReferenceComponents = + comps.stream().anyMatch(e -> !e.asType().getKind().isPrimitive()); + if (hasPrimitiveComponents && hasReferenceComponents) { + add(fullBody, "doclet.record_equals_doc.fullbody.tail.both"); + } else if (hasPrimitiveComponents) { + add(fullBody, "doclet.record_equals_doc.fullbody.tail.primitive"); + } else if (hasReferenceComponents) { + add(fullBody, "doclet.record_equals_doc.fullbody.tail.reference"); + } + Name paramName = ee.getParameters().get(0).getSimpleName(); + IdentifierTree id = treeFactory.newIdentifierTree(paramName); + List<DocTree> paramDesc = + makeDescriptionWithName("doclet.record_equals_doc.param_name", paramName); + DocTree paramTree = treeFactory.newParamTree(false, id, paramDesc); + + DocTree returnTree = treeFactory.newReturnTree( + makeDescriptionWithName("doclet.record_equals_doc.return", paramName)); + + TreePath treePath = utils.getTreePath(ee.getEnclosingElement()); + DocCommentTree docTree = treeFactory.newDocCommentTree(fullBody, List.of(paramTree, returnTree)); + dcTreesMap.put(ee, new DocCommentDuo(treePath, docTree)); + } + + private void add(List<DocTree> contents, String resourceKey) { + // Special case to allow '{@link ...}' to appear in the string. + // A less general case would be to detect literal use of Object.equals + // A more general case would be to allow access to DocCommentParser somehow + String body = resources.getText(resourceKey); + Pattern p = Pattern.compile("\\{@link (\\S*)(.*)}"); + Matcher m = p.matcher(body); + int start = 0; + while (m.find(start)) { + if (m.start() > start) { + contents.add(treeFactory.newTextTree(body.substring(start, m.start()))); + } + ReferenceTree refTree = treeFactory.newReferenceTree(m.group(1)); + List<DocTree> descr = List.of(treeFactory.newTextTree(m.group(2).trim())) ; + contents.add(treeFactory.newLinkTree(refTree, descr)); + start = m.end(); + } + if (start < body.length()) { + contents.add(treeFactory.newTextTree(body.substring(start))); + } + } + + /** + * Generates the description for the standard {@code hashCode} method for a record. + * @param ee the {@code hashCode} method + */ + public void setRecordHashCodeTree(ExecutableElement ee) { + List<DocTree> fullBody = List.of(makeTextTreeForResource("doclet.record_hashCode_doc.fullbody")); + + DocTree returnTree = treeFactory.newReturnTree( + List.of(makeTextTreeForResource("doclet.record_hashCode_doc.return"))); + + DocCommentTree docTree = treeFactory.newDocCommentTree(fullBody, List.of(returnTree)); + dcTreesMap.put(ee, new DocCommentDuo(null, docTree)); + } + + /** + * Generates the description for the standard {@code toString} method for a record. + * @param ee the {@code toString} method + */ + public void setRecordToStringTree(ExecutableElement ee) { + List<DocTree> fullBody = List.of( + treeFactory.newTextTree(resources.getText("doclet.record_toString_doc.fullbody"))); + + DocTree returnTree = treeFactory.newReturnTree(List.of( + treeFactory.newTextTree(resources.getText("doclet.record_toString_doc.return")))); + + DocCommentTree docTree = treeFactory.newDocCommentTree(fullBody, List.of(returnTree)); + dcTreesMap.put(ee, new DocCommentDuo(null, docTree)); + } + + /** + * Generates the description for the accessor method for a state component of a record. + * @param ee the accessor method + */ + public void setRecordAccessorTree(ExecutableElement ee) { + TypeElement te = utils.getEnclosingTypeElement(ee); + + List<DocTree> fullBody = + makeDescriptionWithComponent("doclet.record_accessor_doc.fullbody", te, ee.getSimpleName()); + + DocTree returnTree = treeFactory.newReturnTree( + makeDescriptionWithComponent("doclet.record_accessor_doc.return", te, ee.getSimpleName())); + + DocCommentTree docTree = treeFactory.newDocCommentTree(fullBody, List.of(returnTree)); + dcTreesMap.put(ee, new DocCommentDuo(null, docTree)); + } + + /** + * Generates the description for the field for a state component of a record. + * @param ve the field + */ + public void setRecordFieldTree(VariableElement ve) { + TypeElement te = utils.getEnclosingTypeElement(ve); + + List<DocTree> fullBody = + makeDescriptionWithComponent("doclet.record_field_doc.fullbody", te, ve.getSimpleName()); + + DocCommentTree docTree = treeFactory.newDocCommentTree(fullBody, List.of()); + dcTreesMap.put(ve, new DocCommentDuo(null, docTree)); + } + + /** + * Creates a description that contains a reference to a state component of a record. + * The description is looked up as a resource, and should contain {@code {0}} where the + * reference to the component is to be inserted. The reference will be a link if the + * doc comment for the record has a {@code @param} tag for the component. + * @param key the resource key for the description + * @param elem the record element + * @param component the name of the component + * @return the description + */ + private List<DocTree> makeDescriptionWithComponent(String key, TypeElement elem, Name component) { + List<DocTree> result = new ArrayList<>(); + String text = resources.getText(key); + int index = text.indexOf("{0}"); + result.add(treeFactory.newTextTree(text.substring(0, index))); + Name A = elementUtils.getName("a"); + Name CODE = elementUtils.getName("code"); + Name HREF = elementUtils.getName("href"); + List<DocTree> code = List.of( + treeFactory.newStartElementTree(CODE, List.of(), false), + treeFactory.newTextTree(component.toString()), + treeFactory.newEndElementTree(CODE)); + if (hasParamForComponent(elem, component)) { + DocTree href = treeFactory.newAttributeTree(HREF, + AttributeTree.ValueKind.DOUBLE, + List.of(treeFactory.newTextTree("#param-" + component))); + result.add(treeFactory.newStartElementTree(A, List.of(href), false)); + result.addAll(code); + result.add(treeFactory.newEndElementTree(A)); + } else { + result.addAll(code); + } + result.add(treeFactory.newTextTree(text.substring(index + 3))); + return result; + } + + /** + * Returns whether or not the doc comment for a record contains an {@code @param}} + * for a state component of the record. + * @param elem the record element + * @param component the name of the component + * @return whether or not there is a {@code @param}} for the component + */ + private boolean hasParamForComponent(TypeElement elem, Name component) { + DocCommentTree elemComment = utils.getDocCommentTree(elem); + if (elemComment == null) { + return false; + } + + for (DocTree t : elemComment.getBlockTags()) { + if (t instanceof ParamTree && ((ParamTree) t).getName().getName() == component) { + return true; + } + } + + return false; + } + + /** + * Creates a description that contains the simple name of a program element + * The description is looked up as a resource, and should contain {@code {0}} where the + * name is to be inserted. T + * @param key the resource key for the description + * @param name the name + * @return the description + */ + private List<DocTree> makeDescriptionWithName(String key, Name name) { + String text = resources.getText(key); + int index = text.indexOf("{0}"); + if (index == -1) { + return List.of(treeFactory.newTextTree(text)); + } else { + Name CODE = elementUtils.getName("code"); + return List.of( + treeFactory.newTextTree(text.substring(0, index)), + treeFactory.newStartElementTree(CODE, List.of(), false), + treeFactory.newTextTree(name.toString()), + treeFactory.newEndElementTree(CODE), + treeFactory.newTextTree(text.substring(index + 3)) + ); + } } /* @@ -215,7 +444,7 @@ } public void setDocCommentTree(Element element, List<? extends DocTree> fullBody, - List<? extends DocTree> blockTags, Utils utils) { + List<? extends DocTree> blockTags) { DocCommentTree docTree = treeFactory.newDocCommentTree(fullBody, blockTags); dcTreesMap.put(element, new DocCommentDuo(null, docTree)); // A method having null comment (no comment) that might need to be replaced
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/PackageSummaryWriter.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/PackageSummaryWriter.java Wed Oct 16 17:02:29 2019 -0400 @@ -95,6 +95,15 @@ Content summaryContentTree); /** + * Adds the table of records to the documentation tree. + * + * @param records the records to document. + * @param summaryContentTree the content tree to which the summaries will be added + */ + public abstract void addRecordSummary(SortedSet<TypeElement> records, + Content summaryContentTree); + + /** * Adds the table of exceptions to the documentation tree. * * @param exceptions the exceptions to document.
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/Resources.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/Resources.java Wed Oct 16 17:02:29 2019 -0400 @@ -49,6 +49,7 @@ public final String exceptionSummary; public final String interfaceSummary; public final String packageSummary; + public final String recordSummary; protected ResourceBundle commonBundle; protected ResourceBundle docletBundle; @@ -76,6 +77,7 @@ this.exceptionSummary = getText("doclet.Exception_Summary"); this.interfaceSummary = getText("doclet.Interface_Summary"); this.packageSummary = getText("doclet.Package_Summary"); + this.recordSummary = getText("doclet.Record_Summary"); } /**
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/builders/ClassBuilder.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/builders/ClassBuilder.java Wed Oct 16 17:02:29 2019 -0400 @@ -25,11 +25,26 @@ package jdk.javadoc.internal.doclets.toolkit.builders; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Name; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Elements; +import com.sun.source.tree.MethodTree; +import com.sun.source.tree.Tree; import jdk.javadoc.internal.doclets.formats.html.markup.ContentBuilder; import jdk.javadoc.internal.doclets.toolkit.ClassWriter; +import jdk.javadoc.internal.doclets.toolkit.CommentUtils; import jdk.javadoc.internal.doclets.toolkit.Content; import jdk.javadoc.internal.doclets.toolkit.DocFilesHandler; import jdk.javadoc.internal.doclets.toolkit.DocletException; @@ -70,6 +85,11 @@ private final boolean isEnum; /** + * Keep track of whether or not this typeElement is an record. + */ + private final boolean isRecord; + + /** * The content tree for the class documentation. */ private Content contentTree; @@ -91,13 +111,21 @@ if (utils.isInterface(typeElement)) { isInterface = true; isEnum = false; + isRecord = false; } else if (utils.isEnum(typeElement)) { isInterface = false; isEnum = true; - utils.setEnumDocumentation(typeElement); + isRecord = false; + setEnumDocumentation(typeElement); + } else if (utils.isRecord(typeElement)) { + isInterface = false; + isEnum = false; + isRecord = true; + setRecordDocumentation(typeElement); } else { isInterface = false; isEnum = false; + isRecord = false; } } @@ -133,6 +161,8 @@ key = "doclet.Interface"; } else if (isEnum) { key = "doclet.Enum"; + } else if (isRecord) { + key = "doclet.Record"; } else { key = "doclet.Class"; } @@ -168,7 +198,7 @@ */ protected void buildClassInfo(Content classContentTree) throws DocletException { Content classInfoTree = new ContentBuilder(); - buildTypeParamInfo(classInfoTree); + buildParamInfo(classInfoTree); buildSuperInterfacesInfo(classInfoTree); buildImplementedInterfacesInfo(classInfoTree); buildSubClassInfo(classInfoTree); @@ -185,12 +215,12 @@ } /** - * Build the type parameters of this class. + * Build the type parameters and state components of this class. * * @param classInfoTree the content tree to which the documentation will be added */ - protected void buildTypeParamInfo(Content classInfoTree) { - writer.addTypeParamInfo(classInfoTree); + protected void buildParamInfo(Content classInfoTree) { + writer.addParamInfo(classInfoTree); } /** @@ -392,4 +422,95 @@ protected void buildMethodDetails(Content memberDetailsTree) throws DocletException { builderFactory.getMethodBuilder(writer).build(memberDetailsTree); } + + /** + * The documentation for values() and valueOf() in Enums are set by the + * doclet only iff the user or overridden methods are missing. + * @param elem the enum element + */ + private void setEnumDocumentation(TypeElement elem) { + CommentUtils cmtUtils = configuration.cmtUtils; + for (ExecutableElement ee : utils.getMethods(elem)) { + if (!utils.getFullBody(ee).isEmpty()) // ignore if already set + continue; + Name name = ee.getSimpleName(); + if (name.contentEquals("values") && ee.getParameters().isEmpty()) { + utils.removeCommentHelper(ee); // purge previous entry + cmtUtils.setEnumValuesTree(ee); + } else if (name.contentEquals("valueOf") && ee.getParameters().size() == 1) { + // TODO: check parameter type + utils.removeCommentHelper(ee); // purge previous entry + cmtUtils.setEnumValueOfTree(ee); + } + } + } + + /** + * Sets the documentation as needed for the mandated parts of a record type. + * This includes the canonical constructor, methods like {@code equals}, + * {@code hashCode}, {@code toString}, the accessor methods, and the underlying + * field. + * @param elem the record element + */ + + private void setRecordDocumentation(TypeElement elem) { + CommentUtils cmtUtils = configuration.cmtUtils; + Set<Name> componentNames = elem.getRecordComponents().stream() + .map(Element::getSimpleName) + .collect(Collectors.toSet()); + + for (ExecutableElement ee : utils.getConstructors(elem)) { + if (utils.isCanonicalRecordConstructor(ee)) { + if (utils.getFullBody(ee).isEmpty()) { + utils.removeCommentHelper(ee); // purge previous entry + cmtUtils.setRecordConstructorTree(ee); + } + // only one canonical constructor; no longer need to keep looking + break; + } + } + + for (VariableElement ve : utils.getFields(elem)) { + // The fields for the record component cannot be declared by the + // user and so cannot have any pre-existing comment. + Name name = ve.getSimpleName(); + if (componentNames.contains(name)) { + utils.removeCommentHelper(ve); // purge previous entry + cmtUtils.setRecordFieldTree(ve); + } + } + + TypeMirror objectType = utils.elementUtils.getTypeElement("java.lang.Object").asType(); + + for (ExecutableElement ee : utils.getMethods(elem)) { + if (!utils.getFullBody(ee).isEmpty()) { + continue; + } + + Name name = ee.getSimpleName(); + List<? extends VariableElement> params = ee.getParameters(); + if (name.contentEquals("equals")) { + if (params.size() == 1 && utils.typeUtils.isSameType(params.get(0).asType(), objectType)) { + utils.removeCommentHelper(ee); // purge previous entry + cmtUtils.setRecordEqualsTree(ee); + } + } else if (name.contentEquals("hashCode")) { + if (params.isEmpty()) { + utils.removeCommentHelper(ee); // purge previous entry + cmtUtils.setRecordHashCodeTree(ee); + } + } else if (name.contentEquals("toString")) { + if (params.isEmpty()) { + utils.removeCommentHelper(ee); // purge previous entry + cmtUtils.setRecordToStringTree(ee); + } + } else if (componentNames.contains(name)) { + if (params.isEmpty()) { + utils.removeCommentHelper(ee); // purge previous entry + cmtUtils.setRecordAccessorTree(ee); + } + } + } + + } }
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/builders/MemberSummaryBuilder.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/builders/MemberSummaryBuilder.java Wed Oct 16 17:02:29 2019 -0400 @@ -411,7 +411,7 @@ blockTags.add(cmtutils.makeSeeTree(sb.toString(), setter)); } } - cmtutils.setDocCommentTree(member, fullBody, blockTags, utils); + cmtutils.setDocCommentTree(member, fullBody, blockTags); } /**
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/builders/PackageSummaryBuilder.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/builders/PackageSummaryBuilder.java Wed Oct 16 17:02:29 2019 -0400 @@ -159,6 +159,7 @@ buildInterfaceSummary(summaryContentTree); buildClassSummary(summaryContentTree); buildEnumSummary(summaryContentTree); + buildRecordSummary(summaryContentTree); buildExceptionSummary(summaryContentTree); buildErrorSummary(summaryContentTree); buildAnnotationTypeSummary(summaryContentTree); @@ -215,6 +216,22 @@ } /** + * Build the summary for the records in this package. + * + * @param summaryContentTree the summary tree to which the record summary will + * be added + */ + protected void buildRecordSummary(Content summaryContentTree) { + SortedSet<TypeElement> rlist = utils.isSpecified(packageElement) + ? utils.getTypeElementsAsSortedSet(utils.getRecords(packageElement)) + : configuration.typeElementCatalog.records(packageElement); + SortedSet<TypeElement> records = utils.filterOutPrivateClasses(rlist, configuration.javafx); + if (!records.isEmpty()) { + packageWriter.addRecordSummary(records, summaryContentTree); + } + } + + /** * Build the summary for the exceptions in this package. * * @param summaryContentTree the summary tree to which the exception summary will
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/builders/SerializedFormBuilder.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/builders/SerializedFormBuilder.java Wed Oct 16 17:02:29 2019 -0400 @@ -600,10 +600,10 @@ } /** - * Return true if any of the given typeElements have a @serialinclude tag. + * Return true if any of the given typeElements have a {@code @serial include} tag. * * @param classes the typeElements to check. - * @return true if any of the given typeElements have a @serialinclude tag. + * @return true if any of the given typeElements have a {@code @serial include} tag. */ private boolean serialClassFoundToDocument(SortedSet<TypeElement> classes) { for (TypeElement aClass : classes) {
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets.properties Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/resources/doclets.properties Wed Oct 16 17:02:29 2019 -0400 @@ -93,14 +93,19 @@ doclet.Default=Default: doclet.Parameters=Parameters: doclet.TypeParameters=Type Parameters: +doclet.RecordComponents=Record Components: doclet.Parameters_warn=@param argument "{0}" is not a parameter name. doclet.Parameters_dup_warn=Parameter "{0}" is documented more than once. -doclet.Type_Parameters_warn=@param argument "{0}" is not a type parameter name. -doclet.Type_Parameters_dup_warn=Type parameter "{0}" is documented more than once. +doclet.TypeParameters_warn=@param argument "{0}" is not the name of a type parameter. +doclet.TypeParameters_dup_warn=Type parameter "{0}" is documented more than once. +doclet.RecordComponents_warn=@param argument "{0}" is not the name of a record component. +doclet.RecordComponents_dup_warn=Record component "{0}" is documented more than once. doclet.Returns=Returns: doclet.Return_tag_on_void_method=@return tag cannot be used in method with void return type. doclet.See_Also=See Also: doclet.See=See: +doclet.getter=Getter: +doclet.setter=Setter: doclet.SerialData=Serial Data: doclet.Services=Services doclet.Since=Since: @@ -137,6 +142,7 @@ doclet.Enum_Constant_Summary=Enum Constant Summary doclet.Constructor_Summary=Constructor Summary doclet.Method_Summary=Method Summary +doclet.Record_Summary=Record Summary doclet.Interfaces=Interfaces doclet.Enums=Enums doclet.AnnotationTypes=Annotation Types @@ -160,6 +166,7 @@ doclet.interfaces=interfaces doclet.class=class doclet.classes=classes +doclet.Record=Record doclet.Error=Error doclet.error=error doclet.errors=errors @@ -268,3 +275,58 @@ doclet.enum_valueof_doc.throws_npe=\ if the argument is null + + +#Documentation for records +doclet.record_constructor_doc.fullbody=\ + Creates an instance of a {0} record. + +doclet.record_constructor_doc.param_name=\ + the value for the {0} record component + +doclet.record_equals_doc.fullbody.head=\ + Indicates whether some other object is "equal to" this one. \ + The objects are equal if the other object is of the same class \ + and if all the record components are equal. + +doclet.record_equals_doc.fullbody.tail.both=\ + Reference components are compared with \ + {@link java.util.Objects#equals(Object,Object) Objects::equals(Object,Object)}; \ + primitive components are compared with '=='. + +doclet.record_equals_doc.fullbody.tail.primitive=\ + All components in this record are compared with '=='. + +doclet.record_equals_doc.fullbody.tail.reference=\ + All components in this record are compared with \ + {@link java.util.Objects#equals(Object,Object) Objects::equals(Object,Object)}. + +doclet.record_equals_doc.param_name=\ + the object with which to compare + +doclet.record_equals_doc.return=\ + <code>true</code> if this object is the same as the {0} argument; <code>false</code> otherwise. + +doclet.record_hashCode_doc.fullbody=\ + Returns a hash code value for this object. \ + The value is derived from the hash code of each of the record components. + +doclet.record_hashCode_doc.return=\ + a hash code value for this object + +doclet.record_toString_doc.fullbody=\ + Returns a string representation of this record. \ + The representation contains the name of the type, followed by \ + the name and value of each of the record components. + +doclet.record_toString_doc.return=\ + a string representation of this object + +doclet.record_accessor_doc.fullbody=\ + Returns the value of the {0} record component. + +doclet.record_accessor_doc.return=\ + the value of the {0} record component + +doclet.record_field_doc.fullbody=\ + The field for the {0} record component.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/AccessorTaglet.java Wed Oct 16 17:02:29 2019 -0400 @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.javadoc.internal.doclets.toolkit.taglets; + +import java.util.EnumSet; + +import com.sun.source.doctree.DocTree; +import jdk.javadoc.internal.doclets.toolkit.Content; +import jdk.javadoc.internal.doclets.toolkit.util.Utils; + +import javax.lang.model.element.Element; +import javax.lang.model.element.VariableElement; +import java.util.List; + +/** + * A taglet that represents the @param tag. + * + * <p><b>This is NOT part of any supported API. + * If you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice.</b> + * + * @author Jamie Ho + */ +public class AccessorTaglet extends BaseTaglet { + + DocTree.Kind kind; + + /** + * Construct a ParamTaglet. + */ + public AccessorTaglet(DocTree.Kind kind) { + super(kind.tagName, false, EnumSet.of(Site.FIELD)); + this.kind = kind; + } + + /** + * Given an array of <code>ParamTag</code>s,return its string representation. + * @param holder the member that holds the param tags. + * @param writer the TagletWriter that will write this tag. + * @return the TagletOutput representation of these <code>ParamTag</code>s. + */ + public Content getTagletOutput(Element holder, TagletWriter writer) { + Utils utils = writer.configuration().utils; + return writer.accessorTagOutput(holder, utils.getBlockTags(holder, kind)); + } +} +
--- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/ParamTaglet.java Wed Oct 16 17:00:39 2019 -0400 +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/taglets/ParamTaglet.java Wed Oct 16 17:02:29 2019 -0400 @@ -29,6 +29,7 @@ import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Name; import javax.lang.model.element.TypeElement; import com.sun.source.doctree.DocTree; @@ -53,6 +54,14 @@ * @author Jamie Ho */ public class ParamTaglet extends BaseTaglet implements InheritableTaglet { + private enum ParamKind { + /** Parameter of an executable element. */ + PARAMETER, + /** State components of a record. */ + RECORD_COMPONENT, + /** Type parameters of an executable element or type element. */ + TYPE_PARAMETER + } /** * Construct a ParamTaglet. @@ -101,7 +110,7 @@ String pname = input.isTypeVariableParamTag ? utils.getTypeName(e.asType(), false) : utils.getSimpleName(e); - if (pname.equals(target)) { + if (pname.contentEquals(target)) { input.tagId = String.valueOf(i); break; } @@ -132,15 +141,18 @@ Utils utils = writer.configuration().utils; if (utils.isExecutableElement(holder)) { ExecutableElement member = (ExecutableElement) holder; - Content output = getTagletOutput(false, member, writer, + Content output = getTagletOutput(ParamKind.TYPE_PARAMETER, member, writer, member.getTypeParameters(), utils.getTypeParamTrees(member)); - output.add(getTagletOutput(true, member, writer, + output.add(getTagletOutput(ParamKind.PARAMETER, member, writer, member.getParameters(), utils.getParamTrees(member))); return output; } else { TypeElement typeElement = (TypeElement) holder; - return getTagletOutput(false, typeElement, writer, + Content output = getTagletOutput(ParamKind.TYPE_PARAMETER, typeElement, writer, typeElement.getTypeParameters(), utils.getTypeParamTrees(typeElement)); + output.add(getTagletOutput(ParamKind.RECORD_COMPONENT, typeElement, writer, + typeElement.getRecordComponents(), utils.getParamTrees(typeElement))); + return output; } } @@ -150,25 +162,25 @@ * * @param holder the element that holds the param tags. * @param writer the TagletWriter that will write this tag. - * @param formalParameters The array of parmeters (from type or executable + * @param formalParameters The array of parameters (from type or executable * member) to check. * * @return the content representation of these {@code @param DocTree}s. */ - private Content getTagletOutput(boolean isParameters, Element holder, + private Content getTagletOutput(ParamKind kind, Element holder, TagletWriter writer, List<? extends Element> formalParameters, List<? extends DocTree> paramTags) { Content result = writer.getOutputInstance(); Set<String> alreadyDocumented = new HashSet<>(); if (!paramTags.isEmpty()) { result.add( - processParamTags(holder, isParameters, paramTags, + processParamTags(holder, kind, paramTags, getRankMap(writer.configuration().utils, formalParameters), writer, alreadyDocumented) ); } if (alreadyDocumented.size() != formalParameters.size()) { //Some parameters are missing corresponding @param tags. //Try to inherit them. - result.add(getInheritedTagletOutput(isParameters, holder, + result.add(getInheritedTagletOutput(kind, holder, writer, formalParameters, alreadyDocumented)); } return result; @@ -178,7 +190,7 @@ * Loop through each individual parameter, despite not having a * corresponding param tag, try to inherit it. */ - private Content getInheritedTagletOutput(boolean isParameters, Element holder, + private Content getInheritedTagletOutput(ParamKind kind, Element holder, TagletWriter writer, List<? extends Element> formalParameters, Set<String> alreadyDocumented) { Utils utils = writer.configuration().utils; @@ -191,16 +203,16 @@ // This parameter does not have any @param documentation. // Try to inherit it. Input input = new DocFinder.Input(writer.configuration().utils, holder, this, - Integer.toString(i), !isParameters); + Integer.toString(i), kind == ParamKind.TYPE_PARAMETER); DocFinder.Output inheritedDoc = DocFinder.search(writer.configuration(), input); if (inheritedDoc.inlineTags != null && !inheritedDoc.inlineTags.isEmpty()) { Element e = formalParameters.get(i); - String lname = isParameters + String lname = kind != ParamKind.TYPE_PARAMETER ? utils.getSimpleName(e) : utils.getTypeName(e.asType(), false); CommentHelper ch = utils.getCommentHelper(holder); ch.setOverrideElement(inheritedDoc.holder); - Content content = processParamTag(holder, isParameters, writer, + Content content = processParamTag(holder, kind, writer, inheritedDoc.holderTag, lname, alreadyDocumented.isEmpty()); @@ -230,7 +242,7 @@ when parameter documentation is inherited. * @return the Content representation of this {@code @param DocTree}. */ - private Content processParamTags(Element e, boolean isParams, + private Content processParamTags(Element e, ParamKind kind, List<? extends DocTree> paramTags, Map<String, String> rankMap, TagletWriter writer, Set<String> alreadyDocumented) { Messages messages = writer.configuration().getMessages(); @@ -238,26 +250,33 @@ if (!paramTags.isEmpty()) { CommentHelper ch = writer.configuration().utils.getCommentHelper(e); for (DocTree dt : paramTags) { - String paramName = isParams - ? ch.getParameterName(dt) - : "<" + ch.getParameterName(dt) + ">"; - if (!rankMap.containsKey(ch.getParameterName(dt))) { - messages.warning(ch.getDocTreePath(dt), - isParams - ? "doclet.Parameters_warn" - : "doclet.Type_Parameters_warn", - paramName); + String name = ch.getParameterName(dt); + String paramName = kind != ParamKind.TYPE_PARAMETER + ? name.toString() + : "<" + name + ">"; + if (!rankMap.containsKey(name)) { + String key; + switch (kind) { + case PARAMETER: key = "doclet.Parameters_warn" ; break; + case TYPE_PARAMETER: key = "doclet.TypeParameters_warn" ; break; + case RECORD_COMPONENT: key = "doclet.RecordComponents_warn" ; break; + default: throw new IllegalArgumentException(kind.toString()); + } + messages.warning(ch.getDocTreePath(dt), key, paramName); } - String rank = rankMap.get(ch.getParameterName(dt)); + String rank = rankMap.get(name);